如何在 jgraphx 的图形中制作所需形式的边缘?
How to make the edges of needed form in jgraphx's graph?
我的图包含具有单个出边的矩形顶点和具有两个出边的菱形顶点。
我正在使用 mxCompactTreeLayout
并且大多数情况下一切正常,除了第二种类型的顶点边缘对我来说看起来不对。
我希望他们看起来像图片中的样子
相反,边缘连接到菱形的底部部分。
我应该怎么做才能让边缘看起来像我想要的那样。
编辑:添加了源代码。
PtNodeVertex.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import org.jsc.core.ast.IResult;
import org.jsc.core.ptree.INodeCompleter;
import org.jsc.core.ptree.PtNode;
import org.jsc.core.term.ITerm;
import org.jsc.core.term.MethodInvocationTerm;
import org.jsc.core.term.expressions.ConditionalExpression;
import org.jsc.core.term.expressions.InstanceCreationExpression;
import org.jsc.core.term.expressions.VariableDeclarationTerm;
import org.jsc.core.term.statement.BlockTerm;
import org.jsc.core.term.statement.IfElse;
import org.jsc.core.term.statement.SwitchStatement;
import org.jsc.core.visualization.jgraphx.Edge.EdgeType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.Math.max;
import static java.lang.String.format;
class PtNodeVertex extends mxCell
{
private static Map<PtNode, PtNodeVertex> completingNodesToViews = new HashMap<PtNode, PtNodeVertex>();
private final PtNode node;
protected mxCell[] labels;
private List<PtNodeVertex> successors = new ArrayList<PtNodeVertex>( 1 );
private PtNodeVertex predecessor;
protected Edge[] edgeArray;
private int index;
PtNodeVertex( Object parent, PtNode value, String style )
{
super( parent, null, style );
node = value;
setVertex( true );
setVisible( true );
setGeometry( new mxGeometry() );
setStyle( "defaultVertex;fillColor=none;strokeColor=black;strokeWidth=2.5" );
labels = new mxCell[ getLabelsCount() ];
createLabels();
calcBounds();
createEdges();
}
protected Edge[] createEdges()
{
int n = getMaxSuccessorsCount();
if ( n == 0 )
{
return new Edge[ 0 ];
}
edgeArray = new Edge[ n ];
Edge edge = new Edge( null, this );
edgeArray[ 0 ] = edge;
return edgeArray;
}
int getLabelsCount()
{
return 4;
}
int getMaxSuccessorsCount()
{
return 1;
}
final PtNode getNode()
{
return node;
}
protected void createLabels()
{
ITerm t = node.getTerm();
IResult tv = node.getTermValue();
labels[ 0 ] = createTextLabel( format( "Term [%s]:", t.getClass().getSimpleName() ) );
labels[ 1 ] = createTextLabel( node.getTerm().toString(), true );
labels[ 2 ] = createTextLabel( format( "Term value: %s", tv == null ? "n/a" : tv ) );
labels[ 3 ] = createTextLabel( format( "State: %s", node.getState() ) );
}
protected void calcBounds()
{
mxGeometry b0 = labels[ 0 ].getGeometry();
mxGeometry b1 = labels[ 1 ].getGeometry();
mxGeometry b2 = labels[ 2 ].getGeometry();
mxGeometry b3 = labels[ 3 ].getGeometry();
double w = Math.max( b0.getWidth(), Math.max( b1.getWidth(), Math.max( b2.getWidth(), b3.getWidth() ) ) );
double h = b0.getHeight() + b1.getHeight() + b2.getHeight() + b3.getHeight();
mxGeometry b = getGeometry();
double x = b.getX() + 5;
double y = b.getY() + 5;
double x2 = x;//+ 5 + w01 + 5;
double y2 = y;
double x3 = x2;
double y3 = y2 + b2.getHeight();
double x0 = x;
double y0 = y3 + b3.getHeight();
double x1 = x0;
double y1 = y0 + b0.getHeight();
b.setWidth( w + 10 );
b.setHeight( h + 10 );
b0.setX( x0 );
b0.setY( y0 );
b1.setX( x1 );
b1.setY( y1 );
b2.setX( x2 );
b2.setY( y2 );
b3.setX( x3 );
b3.setY( y3 );
}
private static String prepareText( String s )
{
s = adjustNL( s );
// s = StringEscapeUtils.unescapeHtml4( s );
return s;
}
private static String adjustNL( String s )
{
s = s.replaceAll( "\r\n", "\n" );
s = s.replaceAll( "\r", "\n" );
return s;
}
protected mxCell createTextLabel( String text )
{
return createTextLabel( text, false );
}
protected mxCell createTextLabel( String text, boolean framed )
{
mxCell label = new mxCell();
// System.out.print( text );
text = prepareText( text );
// text = mxUtils.createHtmlDocument( new HashMap<String, Object>(), text, 1, 0,
// "<style type=\"text/css\">.selectRef { " +
// "font-size:9px;font-weight:normal; }</style>" );
label.setValue( text );
mxRectangle b = mxUtils.getLabelSize( text, new HashMap<String, Object>(), false, 1.0 );
mxGeometry geometry = new mxGeometry( b.getX(), b.getY(), b.getWidth(), b.getHeight() );
label.setVertex( true );
label.setGeometry( geometry );
label.setVisible( true );
label.setParent( this );
this.insert( label );
label.setConnectable( false );
label.setStyle( format( "defaultVertex;fillColor=none%s", ( framed ? ";strokeColor=blue" : ";strokeColor=none" ) ) );
return label;
}
public static mxCell create( Object parent, PtNode node, String style )
{
PtNodeVertex vertex;
if ( isCompletingNode( node ) )
{
vertex = new PtNodeVertex( parent, node, style );
completingNodesToViews.put( node, vertex );
}
else if ( node instanceof INodeCompleter )
{
vertex = new NodeCompleterVertex( parent, node, style );
completingNodesToViews.put( node, vertex );
}
else if ( node.getTerm() instanceof IfElse )
{
vertex = new IfElseNodeVertex( parent, node, style );
}
else if ( node.getTerm() instanceof ConditionalExpression )
{
vertex = new ConditionalNodeVertex( parent, node, style );
}
else if ( node.getTerm() instanceof SwitchStatement )
{
vertex = new SwitchNodeVertex( parent, node, style );
}
else
{
vertex = new PtNodeVertex( parent, node, style );
}
return vertex;
}
private static boolean isCompletingNode( PtNode node )
{
return node.getTerm() instanceof BlockTerm ||
node.getTerm() instanceof VariableDeclarationTerm ||
node.getTerm() instanceof InstanceCreationExpression ||
node.getTerm() instanceof MethodInvocationTerm;
}
Object getLabel( int i )
{
return labels[ i ];
}
final int getSuccessorCount()
{
return successors.size();
}
void addSuccessor( PtNodeVertex successor )
{
successors.add( successor );
successor.predecessor = this;
}
final PtNodeVertex getSuccessor( int i )
{
return successors.get( i );
}
final Edge getEdge( int i )
{
return edgeArray[ i ];
}
protected EdgeType getDefaultEdgeType()
{
return EdgeType.TYPE_1;
}
public void setIndex( int index )
{
this.index = index;
}
public int getIndex()
{
return index;
}
}
`
IfElseNodeVertex.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxPoint;
import org.jsc.core.ptree.PtNode;
import org.jsc.core.term.statement.IfElse;
import org.jsc.core.visualization.jgraphx.Edge.EdgeType;
import static java.lang.Math.max;
import static java.lang.String.format;
import static org.jsc.core.visualization.jgraphx.Direction.LEFT;
import static org.jsc.core.visualization.jgraphx.Direction.RIGHT;
class IfElseNodeVertex extends PtNodeVertex
{
private Edge thenEdge;
private Edge elseEdge;
IfElseNodeVertex( Object parent, PtNode value, String style )
{
super( parent, value, style );
if ( value.getTerm().getClass() != IfElse.class )
{
throw new Error( "IfElse term expected" );
}
if ( value.getOuts().size() != 2 )
{
throw new Error( "IfElse must have 2 successors\n" + value.getTerm() ); //or mat have 1 (without else
}
setStyle( "vRhombus;`fillColor=none;strokeColor=green" );
}
@Override
protected void createLabels()
{
IfElse ifElse = ( IfElse ) getNode().getTerm();
String termString = format( "if( %s )", ifElse.getCondition() );
labels[ 0 ] = createTextLabel( termString );
labels[ 0 ].setStyle( "\"vRhombus;shape=rhombus;fillColor=none;strokeColor=none" );
labels[ 0 ].setGeometry( new mxGeometry( 0, 0, 300, 150 ) );
}
@Override
protected void calcBounds()
{
mxGeometry b0 = labels[ 0 ].getGeometry();
double w = 50 + b0.getWidth() + 50;
double h = max( b0.getHeight(), w * 0.618 );
mxGeometry b = getGeometry();
double x = b.getX();
double y = b.getY();
double x0 = b0.getCenterX() - b0.getWidth() / 2;
double y0 = b0.getCenterY();
b.setWidth( 300 );
b.setHeight( 150 );
}
@Override
public int getLabelsCount()
{
return 1;
}
protected Edge[] createEdges()
{
int n = getMaxSuccessorsCount();
if ( n == 0 )
{
return new Edge[ 0 ];
}
edgeArray = new Edge[ n ];
thenEdge = new IfElseEdge( null, this, "then", EdgeType.TYPE_2, LEFT );
elseEdge = new IfElseEdge( null, this, "else", EdgeType.TYPE_2, RIGHT );
edgeArray[ 0 ] = thenEdge;
edgeArray[ 1 ] = elseEdge;
return edgeArray;
}
@Override
public int getMaxSuccessorsCount()
{
return 2;
}
protected EdgeType getDefaultEdgeType()
{
return EdgeType.TYPE_2;
}
}
PtGraph.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import org.jsc.core.ptree.PtEdge;
import org.jsc.core.ptree.PtNode;
public class PtGraph extends mxGraph
{
private mxCompactTreeLayout layout;
@Override
public Object insertVertex( Object parent,
String id,
Object value,
double x,
double y,
double width,
double height,
String style,
boolean relative )
{
Object v = super.insertVertex( parent, id, value, x, y, width, height, style, relative );
if ( v instanceof PtNodeVertex )
{
insertLabels( ( PtNodeVertex ) v );
}
return v;
}
protected void insertLabels( PtNodeVertex v )
{
for ( int i = 0; i < v.getLabelsCount(); i++ )
{
addCell( v.getLabel( i ), v );
}
}
@Override
public Object createVertex( Object parent,
String id,
Object value,
double x,
double y,
double width,
double height,
String style,
boolean relative
)
{
if ( !( value instanceof PtNode ) )
{
return super.createVertex( parent, id, value, x, y, width, height, style, relative );
}
mxCell vertex;
PtNode node = ( PtNode ) value;
if ( node == ProcessTreeSVGView.dummyNode )
{
vertex = new HaltVertex( parent );
}
else
{
vertex = PtNodeVertex.create( parent, node, null );
}
vertex.setId( id );
vertex.setVertex( true );
vertex.setConnectable( true );
mxGeometry geometry = new mxGeometry( x, y, width, height );
vertex.setGeometry( geometry );
return vertex;
}
@Override
public Object createEdge( Object parent,
String id,
Object value,
Object source,
Object target,
String style )
{
if ( !( value instanceof PtEdge ) )
{
return super.createEdge( parent, id, value, source, target, style );
}
PtEdge ptEdge = ( PtEdge ) value;
Edge edge = ( Edge ) Edge.create( parent, ( PtNodeVertex ) source, (PtNodeVertex)target, ptEdge.getContext(), null, layout );
edge.setId( id );
edge.setConnectable( false );
return edge;
}
@Override
public void drawState( mxICanvas canvas, mxCellState state, boolean drawLabel )
{
Object cell = state.getCell();
drawLabel = !( cell instanceof PtNodeVertex ) && drawLabel;
super.drawState( canvas, state, drawLabel );
}
public void setLayout( mxCompactTreeLayout layout )
{
this.layout = layout;
}
}
Edge.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import org.jsc.core.DriveContext;
class Edge extends mxCell
{
enum EdgeType
{
TYPE_1,
TYPE_2
}
static mxCell create( Object parent,
PtNodeVertex source,
PtNodeVertex target,
DriveContext context,
String style,
mxCompactTreeLayout layout )
{
int index = target.getIndex();
Edge edge = source.getEdge( index );
edge.setValue( context );
edge.setEdge( true );
edge.setLayoutParamsForEdge( layout );
return edge;
}
protected void setLayoutParamsForEdge( mxCompactTreeLayout layout )
{
}
Edge( Object parent, PtNodeVertex source )
{
mxGeometry geo = new mxGeometry();
setEdge( true );
setGeometry( geo );
}
}
class IfElseEdge extends Edge
{
EdgeType type;
Direction direction;
IfElseEdge( Object parent, IfElseNodeVertex source, String text, EdgeType type, Direction direction )
{
super( parent, source );
this.type = type;
this.direction = direction;
mxGeometry geo = new mxGeometry( 0, 0, 0, 0 );
//geo.setRelative( true );
setGeometry( geo );
setStyle( "rhombusEdge" );
mxCell sourceLabel = new mxCell( text, new mxGeometry( -1, 0, 0, 0 ),
direction == Direction.RIGHT ?
"resizable=0;align=left;verticalAlign=top;" :
"resizable=0;align=right;verticalAlign=top;"
);
sourceLabel.getGeometry().setRelative( true );
sourceLabel.setConnectable( false );
sourceLabel.setVertex( true );
insert( sourceLabel );
}
@Override
protected void setLayoutParamsForEdge( mxCompactTreeLayout layout )
{
layout.setOrthogonalEdge( this, true );
layout.setEdgeStyleEnabled( this, true );
}
}
ProcessTreeSVGView.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxPerimeter;
import com.mxgraph.view.mxStylesheet;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGraphics2DIOException;
import org.jsc.core.ptree.ProcessTree;
import org.jsc.core.ptree.PtEdge;
import org.jsc.core.ptree.PtNode;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGDocument;
import java.awt.*;
import java.io.*;
import java.util.Hashtable;
import java.util.Map;
public class ProcessTreeSVGView
{
private static final String SVG_FILE_NAME = "C:\users\anthony\g.svg";
static SVGGraphics2D g;
private final mxGraph graph;
private final mxGraphComponent graphComponent;
private String style;
public final static PtNode dummyNode = new PtNode();
public final static PtEdge dummyEdge = new PtEdge();
/**
* Constructs a new frame that is initially invisible.
* <p>
* This constructor sets the component's locale property to the value
* returned by <code>JComponent.getDefaultLocale</code>.
*
* @exception java.awt.HeadlessException if GraphicsEnvironment.isHeadless()
* returns true.
* @see java.awt.GraphicsEnvironment#isHeadless
* @see java.awt.Component#setSize
* @see java.awt.Component#setVisible
* @see javax.swing.JComponent#getDefaultLocale
*/
public ProcessTreeSVGView( ProcessTree pTree )
throws HeadlessException, SVGGraphics2DIOException, FileNotFoundException, UnsupportedEncodingException
{
System.out.printf( "\nSaving Process tree(s) in '%s' ... ", SVG_FILE_NAME );
// Create an SVG document.
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
SVGDocument doc = ( SVGDocument ) impl.createDocument( svgNS, "svg", null );
// Create a converter for this document.
g = new SVGGraphics2D( doc );
graph = new PtGraph();
graphComponent = new mxGraphComponent( graph );
//First draw Graph to the SVGGraphics2D object using graph component objects draw method
drawTree( pTree );
// Do some drawing.
graphComponent.getGraphControl().drawGraph( g, true );
// Populate the document root with the generated SVG content.
Element root = doc.getDocumentElement();
g.getRoot( root );
//Once every thing is drawn on graphics find root element and update this by adding additional values for the required fields.
// Element root = g.getRoot();
Dimension size = graphComponent.getGraphControl().getPreferredSize();
root.setAttributeNS( null, "width", size.width + "" );
root.setAttributeNS( null, "height", size.height + "" );
root.setAttributeNS( null, "viewBox", "0 0 " + size.width + " " + size.height );
OutputStream outStream = new FileOutputStream( SVG_FILE_NAME );
Writer out = new OutputStreamWriter( outStream, "UTF-8" );
g.stream( root, out );
System.out.println( "done." );
runSVGViewer( SVG_FILE_NAME );
}
private void runSVGViewer( String svgFileName )
{
System.out.print( "\nLoading SVG viewer ... " );
ProcessBuilder builder = new ProcessBuilder( "C:\Program Files (x86)\Free Picture Solutions\Free Svg Viewer\SvgViewer.exe ", svgFileName );
try
{
builder.start();
}
catch ( IOException e )
{
e.printStackTrace();
throw new Error();
}
System.out.println( "done." );
}
//==================================================================================================================
private void drawTree( ProcessTree pTree )
{
Object parent = graph.getDefaultParent();
registerRhombusVertexStyle( graph );
mxCompactTreeLayout layout = setupLayout( graph );
graph.getModel().beginUpdate();
mxCell v;
try
{
v = build( pTree.getRoot(), ( mxCell ) parent, 0 );
}
finally
{
graph.getModel().endUpdate();
}
layout.execute( parent, v );
}
private mxCompactTreeLayout setupLayout( mxGraph graph )
{
mxCompactTreeLayout layout = new mxCompactTreeLayout( graph, false );
layout.setEdgeRouting( true );
layout.setHorizontal( false );
layout.setLevelDistance( 100 );
layout.setNodeDistance( 50 );
layout.setUseBoundingBox( true );
( ( PtGraph ) graph ).setLayout( layout );
return layout;
}
private PtNodeVertex build( PtNode node, mxCell parent, int index )
{
PtNodeVertex source = insertVertex( node, parent, index );
if ( node.getChildrenCount() == 0 )
{
HaltVertex target = ( HaltVertex ) insertHaltVertex( parent );
source.addSuccessor( target );
insertEdge( parent, dummyEdge, source, target );
return source;
}
for ( int i = 0; i < node.getChildrenCount(); i++ )
{
PtNode node1 = node.getChild( i );
PtNodeVertex target = build( node1, parent, i );
source.addSuccessor( target );
insertEdge( parent, node1.getIn(), source, target );
}
return source;
}
PtNodeVertex insertVertex( PtNode node, mxCell parent, int index )
{
PtNodeVertex v = ( PtNodeVertex ) graph.insertVertex( parent, null, node, 0, 0, 0, 0, style );
v.setIndex( index );
return v;
}
private mxCell insertHaltVertex( mxCell parent )
{
mxCell v = ( mxCell ) graph.insertVertex( parent, null, dummyNode, 0, 0, 0, 0, style );
return v;
}
private void insertEdge( Object parent, PtEdge ptEdge, mxCell source, mxCell target )
{
if ( !( source instanceof HaltVertex ) )
{
graph.insertEdge( parent, null, ptEdge, source, target );
}
}
private static void registerRhombusVertexStyle( mxGraph graph )
{
mxStylesheet ss = graph.getStylesheet();
Map<String, Object> style = new Hashtable<String, Object>();
style.put( mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RHOMBUS );
style.put( mxConstants.STYLE_PERIMETER, mxPerimeter.RhombusPerimeter );
style.put( mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE );
style.put( mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER );
style.put( mxConstants.STYLE_FILLCOLOR, "#C3D9FF" );
style.put( mxConstants.STYLE_STROKECOLOR, "#6482B9" );
style.put( mxConstants.STYLE_FONTCOLOR, "#774400" );
ss.putCellStyle( "vRhombus", style );
style = ss.getDefaultEdgeStyle();
style.put( mxConstants.STYLE_EDGE, mxConstants.EDGESTYLE_ELBOW );
style.put( mxConstants.STYLE_ROUTING_CENTER_Y, 0.0 );
ss.putCellStyle( "rhombusEdge", style );
////////////////////////////////////////////////////////////////////////////////////
Map<String, Map<String, Object>> styles = ss.getStyles();
for ( String key : styles.keySet() )
{
Map<String, Object> _style = styles.get( key );
System.out.printf( "\n%s =\n", key );
System.out.println( "---------------------" );
for ( String key1 : _style.keySet() )
{
System.out.printf( "\n%s = %s", key1, _style.get( key1 ) );
}
System.out.println();
}
}
}
我找到了答案。
一般来说,如果您使用边缘样式或其他方式无法实现结果,那么您应该编写自己的布局。
可以是从头编写的布局,也可以是扩展或合并现有布局实现功能的布局。
关于我的问题,我扩展 mxCompactTreeLayout
覆盖 setEdgePoints
方法。
源代码:
PtCompactTreeLayout.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxGraph;
import java.util.Collections;
import java.util.List;
public class PtCompactTreeLayout extends mxCompactTreeLayout
{
public PtCompactTreeLayout( mxGraph graph )
{
super( graph, false );
}
@Override
public void setEdgePoints( Object edge, List<mxPoint> points )
{
if ( !( edge instanceof IfElseEdge ) )
{
super.setEdgePoints( edge, points );
return;
}
IfElseEdge ifElseEdge = ( IfElseEdge ) edge;
IfElseNodeVertex v = ( IfElseNodeVertex ) ifElseEdge.getSource();
mxGeometry geo = v.getGeometry();
mxGeometry vb = ifElseEdge.getGeometry();
if ( vb == null )
{
super.setEdgePoints( edge, points );
return;
}
double xc = geo.getCenterX();
double yc = geo.getCenterY();
double w = geo.getWidth();
double h = geo.getHeight();
double xt;
double yt;
int i = ( ifElseEdge == v.getEdge( 0 ) ? 0 : 1 );
PtNodeVertex vs = v.getSuccessor( i );
mxGeometry sgeo0 = v.getSuccessor( 0 ).getGeometry();
mxGeometry sgeo1 = v.getSuccessor( 1 ).getGeometry();
double ws0 = sgeo0.getWidth();
xt = ( i == 0 ? sgeo0.getCenterX() : sgeo1.getCenterX());
yt = ( i == 0 ? sgeo0.getY() : sgeo1.getY() );
vb.setTargetPoint( new mxPoint( xt, yt ) );
double xm = xt;
mxPoint mp = new mxPoint( xm, yc );
vb.setPoints( Collections.singletonList( mp ) );
vb.setSourcePoint( calcSourcePoint( v, i ) );
}
private mxPoint calcSourcePoint( PtNodeVertex v, int i )
{
mxGeometry geom = v.getGeometry();
double w = geom.getWidth();
double xs = ( i == 0 ? geom.getX() : geom.getX() + geom.getWidth() );
double ys = geom.getCenterY();
return new mxPoint( xs, ys );
}
}
我的图包含具有单个出边的矩形顶点和具有两个出边的菱形顶点。
我正在使用 mxCompactTreeLayout
并且大多数情况下一切正常,除了第二种类型的顶点边缘对我来说看起来不对。
我希望他们看起来像图片中的样子
相反,边缘连接到菱形的底部部分。
我应该怎么做才能让边缘看起来像我想要的那样。
编辑:添加了源代码。
PtNodeVertex.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import org.jsc.core.ast.IResult;
import org.jsc.core.ptree.INodeCompleter;
import org.jsc.core.ptree.PtNode;
import org.jsc.core.term.ITerm;
import org.jsc.core.term.MethodInvocationTerm;
import org.jsc.core.term.expressions.ConditionalExpression;
import org.jsc.core.term.expressions.InstanceCreationExpression;
import org.jsc.core.term.expressions.VariableDeclarationTerm;
import org.jsc.core.term.statement.BlockTerm;
import org.jsc.core.term.statement.IfElse;
import org.jsc.core.term.statement.SwitchStatement;
import org.jsc.core.visualization.jgraphx.Edge.EdgeType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.Math.max;
import static java.lang.String.format;
class PtNodeVertex extends mxCell
{
private static Map<PtNode, PtNodeVertex> completingNodesToViews = new HashMap<PtNode, PtNodeVertex>();
private final PtNode node;
protected mxCell[] labels;
private List<PtNodeVertex> successors = new ArrayList<PtNodeVertex>( 1 );
private PtNodeVertex predecessor;
protected Edge[] edgeArray;
private int index;
PtNodeVertex( Object parent, PtNode value, String style )
{
super( parent, null, style );
node = value;
setVertex( true );
setVisible( true );
setGeometry( new mxGeometry() );
setStyle( "defaultVertex;fillColor=none;strokeColor=black;strokeWidth=2.5" );
labels = new mxCell[ getLabelsCount() ];
createLabels();
calcBounds();
createEdges();
}
protected Edge[] createEdges()
{
int n = getMaxSuccessorsCount();
if ( n == 0 )
{
return new Edge[ 0 ];
}
edgeArray = new Edge[ n ];
Edge edge = new Edge( null, this );
edgeArray[ 0 ] = edge;
return edgeArray;
}
int getLabelsCount()
{
return 4;
}
int getMaxSuccessorsCount()
{
return 1;
}
final PtNode getNode()
{
return node;
}
protected void createLabels()
{
ITerm t = node.getTerm();
IResult tv = node.getTermValue();
labels[ 0 ] = createTextLabel( format( "Term [%s]:", t.getClass().getSimpleName() ) );
labels[ 1 ] = createTextLabel( node.getTerm().toString(), true );
labels[ 2 ] = createTextLabel( format( "Term value: %s", tv == null ? "n/a" : tv ) );
labels[ 3 ] = createTextLabel( format( "State: %s", node.getState() ) );
}
protected void calcBounds()
{
mxGeometry b0 = labels[ 0 ].getGeometry();
mxGeometry b1 = labels[ 1 ].getGeometry();
mxGeometry b2 = labels[ 2 ].getGeometry();
mxGeometry b3 = labels[ 3 ].getGeometry();
double w = Math.max( b0.getWidth(), Math.max( b1.getWidth(), Math.max( b2.getWidth(), b3.getWidth() ) ) );
double h = b0.getHeight() + b1.getHeight() + b2.getHeight() + b3.getHeight();
mxGeometry b = getGeometry();
double x = b.getX() + 5;
double y = b.getY() + 5;
double x2 = x;//+ 5 + w01 + 5;
double y2 = y;
double x3 = x2;
double y3 = y2 + b2.getHeight();
double x0 = x;
double y0 = y3 + b3.getHeight();
double x1 = x0;
double y1 = y0 + b0.getHeight();
b.setWidth( w + 10 );
b.setHeight( h + 10 );
b0.setX( x0 );
b0.setY( y0 );
b1.setX( x1 );
b1.setY( y1 );
b2.setX( x2 );
b2.setY( y2 );
b3.setX( x3 );
b3.setY( y3 );
}
private static String prepareText( String s )
{
s = adjustNL( s );
// s = StringEscapeUtils.unescapeHtml4( s );
return s;
}
private static String adjustNL( String s )
{
s = s.replaceAll( "\r\n", "\n" );
s = s.replaceAll( "\r", "\n" );
return s;
}
protected mxCell createTextLabel( String text )
{
return createTextLabel( text, false );
}
protected mxCell createTextLabel( String text, boolean framed )
{
mxCell label = new mxCell();
// System.out.print( text );
text = prepareText( text );
// text = mxUtils.createHtmlDocument( new HashMap<String, Object>(), text, 1, 0,
// "<style type=\"text/css\">.selectRef { " +
// "font-size:9px;font-weight:normal; }</style>" );
label.setValue( text );
mxRectangle b = mxUtils.getLabelSize( text, new HashMap<String, Object>(), false, 1.0 );
mxGeometry geometry = new mxGeometry( b.getX(), b.getY(), b.getWidth(), b.getHeight() );
label.setVertex( true );
label.setGeometry( geometry );
label.setVisible( true );
label.setParent( this );
this.insert( label );
label.setConnectable( false );
label.setStyle( format( "defaultVertex;fillColor=none%s", ( framed ? ";strokeColor=blue" : ";strokeColor=none" ) ) );
return label;
}
public static mxCell create( Object parent, PtNode node, String style )
{
PtNodeVertex vertex;
if ( isCompletingNode( node ) )
{
vertex = new PtNodeVertex( parent, node, style );
completingNodesToViews.put( node, vertex );
}
else if ( node instanceof INodeCompleter )
{
vertex = new NodeCompleterVertex( parent, node, style );
completingNodesToViews.put( node, vertex );
}
else if ( node.getTerm() instanceof IfElse )
{
vertex = new IfElseNodeVertex( parent, node, style );
}
else if ( node.getTerm() instanceof ConditionalExpression )
{
vertex = new ConditionalNodeVertex( parent, node, style );
}
else if ( node.getTerm() instanceof SwitchStatement )
{
vertex = new SwitchNodeVertex( parent, node, style );
}
else
{
vertex = new PtNodeVertex( parent, node, style );
}
return vertex;
}
private static boolean isCompletingNode( PtNode node )
{
return node.getTerm() instanceof BlockTerm ||
node.getTerm() instanceof VariableDeclarationTerm ||
node.getTerm() instanceof InstanceCreationExpression ||
node.getTerm() instanceof MethodInvocationTerm;
}
Object getLabel( int i )
{
return labels[ i ];
}
final int getSuccessorCount()
{
return successors.size();
}
void addSuccessor( PtNodeVertex successor )
{
successors.add( successor );
successor.predecessor = this;
}
final PtNodeVertex getSuccessor( int i )
{
return successors.get( i );
}
final Edge getEdge( int i )
{
return edgeArray[ i ];
}
protected EdgeType getDefaultEdgeType()
{
return EdgeType.TYPE_1;
}
public void setIndex( int index )
{
this.index = index;
}
public int getIndex()
{
return index;
}
}
`
IfElseNodeVertex.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxPoint;
import org.jsc.core.ptree.PtNode;
import org.jsc.core.term.statement.IfElse;
import org.jsc.core.visualization.jgraphx.Edge.EdgeType;
import static java.lang.Math.max;
import static java.lang.String.format;
import static org.jsc.core.visualization.jgraphx.Direction.LEFT;
import static org.jsc.core.visualization.jgraphx.Direction.RIGHT;
class IfElseNodeVertex extends PtNodeVertex
{
private Edge thenEdge;
private Edge elseEdge;
IfElseNodeVertex( Object parent, PtNode value, String style )
{
super( parent, value, style );
if ( value.getTerm().getClass() != IfElse.class )
{
throw new Error( "IfElse term expected" );
}
if ( value.getOuts().size() != 2 )
{
throw new Error( "IfElse must have 2 successors\n" + value.getTerm() ); //or mat have 1 (without else
}
setStyle( "vRhombus;`fillColor=none;strokeColor=green" );
}
@Override
protected void createLabels()
{
IfElse ifElse = ( IfElse ) getNode().getTerm();
String termString = format( "if( %s )", ifElse.getCondition() );
labels[ 0 ] = createTextLabel( termString );
labels[ 0 ].setStyle( "\"vRhombus;shape=rhombus;fillColor=none;strokeColor=none" );
labels[ 0 ].setGeometry( new mxGeometry( 0, 0, 300, 150 ) );
}
@Override
protected void calcBounds()
{
mxGeometry b0 = labels[ 0 ].getGeometry();
double w = 50 + b0.getWidth() + 50;
double h = max( b0.getHeight(), w * 0.618 );
mxGeometry b = getGeometry();
double x = b.getX();
double y = b.getY();
double x0 = b0.getCenterX() - b0.getWidth() / 2;
double y0 = b0.getCenterY();
b.setWidth( 300 );
b.setHeight( 150 );
}
@Override
public int getLabelsCount()
{
return 1;
}
protected Edge[] createEdges()
{
int n = getMaxSuccessorsCount();
if ( n == 0 )
{
return new Edge[ 0 ];
}
edgeArray = new Edge[ n ];
thenEdge = new IfElseEdge( null, this, "then", EdgeType.TYPE_2, LEFT );
elseEdge = new IfElseEdge( null, this, "else", EdgeType.TYPE_2, RIGHT );
edgeArray[ 0 ] = thenEdge;
edgeArray[ 1 ] = elseEdge;
return edgeArray;
}
@Override
public int getMaxSuccessorsCount()
{
return 2;
}
protected EdgeType getDefaultEdgeType()
{
return EdgeType.TYPE_2;
}
}
PtGraph.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.canvas.mxICanvas;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import org.jsc.core.ptree.PtEdge;
import org.jsc.core.ptree.PtNode;
public class PtGraph extends mxGraph
{
private mxCompactTreeLayout layout;
@Override
public Object insertVertex( Object parent,
String id,
Object value,
double x,
double y,
double width,
double height,
String style,
boolean relative )
{
Object v = super.insertVertex( parent, id, value, x, y, width, height, style, relative );
if ( v instanceof PtNodeVertex )
{
insertLabels( ( PtNodeVertex ) v );
}
return v;
}
protected void insertLabels( PtNodeVertex v )
{
for ( int i = 0; i < v.getLabelsCount(); i++ )
{
addCell( v.getLabel( i ), v );
}
}
@Override
public Object createVertex( Object parent,
String id,
Object value,
double x,
double y,
double width,
double height,
String style,
boolean relative
)
{
if ( !( value instanceof PtNode ) )
{
return super.createVertex( parent, id, value, x, y, width, height, style, relative );
}
mxCell vertex;
PtNode node = ( PtNode ) value;
if ( node == ProcessTreeSVGView.dummyNode )
{
vertex = new HaltVertex( parent );
}
else
{
vertex = PtNodeVertex.create( parent, node, null );
}
vertex.setId( id );
vertex.setVertex( true );
vertex.setConnectable( true );
mxGeometry geometry = new mxGeometry( x, y, width, height );
vertex.setGeometry( geometry );
return vertex;
}
@Override
public Object createEdge( Object parent,
String id,
Object value,
Object source,
Object target,
String style )
{
if ( !( value instanceof PtEdge ) )
{
return super.createEdge( parent, id, value, source, target, style );
}
PtEdge ptEdge = ( PtEdge ) value;
Edge edge = ( Edge ) Edge.create( parent, ( PtNodeVertex ) source, (PtNodeVertex)target, ptEdge.getContext(), null, layout );
edge.setId( id );
edge.setConnectable( false );
return edge;
}
@Override
public void drawState( mxICanvas canvas, mxCellState state, boolean drawLabel )
{
Object cell = state.getCell();
drawLabel = !( cell instanceof PtNodeVertex ) && drawLabel;
super.drawState( canvas, state, drawLabel );
}
public void setLayout( mxCompactTreeLayout layout )
{
this.layout = layout;
}
}
Edge.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import org.jsc.core.DriveContext;
class Edge extends mxCell
{
enum EdgeType
{
TYPE_1,
TYPE_2
}
static mxCell create( Object parent,
PtNodeVertex source,
PtNodeVertex target,
DriveContext context,
String style,
mxCompactTreeLayout layout )
{
int index = target.getIndex();
Edge edge = source.getEdge( index );
edge.setValue( context );
edge.setEdge( true );
edge.setLayoutParamsForEdge( layout );
return edge;
}
protected void setLayoutParamsForEdge( mxCompactTreeLayout layout )
{
}
Edge( Object parent, PtNodeVertex source )
{
mxGeometry geo = new mxGeometry();
setEdge( true );
setGeometry( geo );
}
}
class IfElseEdge extends Edge
{
EdgeType type;
Direction direction;
IfElseEdge( Object parent, IfElseNodeVertex source, String text, EdgeType type, Direction direction )
{
super( parent, source );
this.type = type;
this.direction = direction;
mxGeometry geo = new mxGeometry( 0, 0, 0, 0 );
//geo.setRelative( true );
setGeometry( geo );
setStyle( "rhombusEdge" );
mxCell sourceLabel = new mxCell( text, new mxGeometry( -1, 0, 0, 0 ),
direction == Direction.RIGHT ?
"resizable=0;align=left;verticalAlign=top;" :
"resizable=0;align=right;verticalAlign=top;"
);
sourceLabel.getGeometry().setRelative( true );
sourceLabel.setConnectable( false );
sourceLabel.setVertex( true );
insert( sourceLabel );
}
@Override
protected void setLayoutParamsForEdge( mxCompactTreeLayout layout )
{
layout.setOrthogonalEdge( this, true );
layout.setEdgeStyleEnabled( this, true );
}
}
ProcessTreeSVGView.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxPerimeter;
import com.mxgraph.view.mxStylesheet;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.apache.batik.svggen.SVGGraphics2DIOException;
import org.jsc.core.ptree.ProcessTree;
import org.jsc.core.ptree.PtEdge;
import org.jsc.core.ptree.PtNode;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGDocument;
import java.awt.*;
import java.io.*;
import java.util.Hashtable;
import java.util.Map;
public class ProcessTreeSVGView
{
private static final String SVG_FILE_NAME = "C:\users\anthony\g.svg";
static SVGGraphics2D g;
private final mxGraph graph;
private final mxGraphComponent graphComponent;
private String style;
public final static PtNode dummyNode = new PtNode();
public final static PtEdge dummyEdge = new PtEdge();
/**
* Constructs a new frame that is initially invisible.
* <p>
* This constructor sets the component's locale property to the value
* returned by <code>JComponent.getDefaultLocale</code>.
*
* @exception java.awt.HeadlessException if GraphicsEnvironment.isHeadless()
* returns true.
* @see java.awt.GraphicsEnvironment#isHeadless
* @see java.awt.Component#setSize
* @see java.awt.Component#setVisible
* @see javax.swing.JComponent#getDefaultLocale
*/
public ProcessTreeSVGView( ProcessTree pTree )
throws HeadlessException, SVGGraphics2DIOException, FileNotFoundException, UnsupportedEncodingException
{
System.out.printf( "\nSaving Process tree(s) in '%s' ... ", SVG_FILE_NAME );
// Create an SVG document.
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
SVGDocument doc = ( SVGDocument ) impl.createDocument( svgNS, "svg", null );
// Create a converter for this document.
g = new SVGGraphics2D( doc );
graph = new PtGraph();
graphComponent = new mxGraphComponent( graph );
//First draw Graph to the SVGGraphics2D object using graph component objects draw method
drawTree( pTree );
// Do some drawing.
graphComponent.getGraphControl().drawGraph( g, true );
// Populate the document root with the generated SVG content.
Element root = doc.getDocumentElement();
g.getRoot( root );
//Once every thing is drawn on graphics find root element and update this by adding additional values for the required fields.
// Element root = g.getRoot();
Dimension size = graphComponent.getGraphControl().getPreferredSize();
root.setAttributeNS( null, "width", size.width + "" );
root.setAttributeNS( null, "height", size.height + "" );
root.setAttributeNS( null, "viewBox", "0 0 " + size.width + " " + size.height );
OutputStream outStream = new FileOutputStream( SVG_FILE_NAME );
Writer out = new OutputStreamWriter( outStream, "UTF-8" );
g.stream( root, out );
System.out.println( "done." );
runSVGViewer( SVG_FILE_NAME );
}
private void runSVGViewer( String svgFileName )
{
System.out.print( "\nLoading SVG viewer ... " );
ProcessBuilder builder = new ProcessBuilder( "C:\Program Files (x86)\Free Picture Solutions\Free Svg Viewer\SvgViewer.exe ", svgFileName );
try
{
builder.start();
}
catch ( IOException e )
{
e.printStackTrace();
throw new Error();
}
System.out.println( "done." );
}
//==================================================================================================================
private void drawTree( ProcessTree pTree )
{
Object parent = graph.getDefaultParent();
registerRhombusVertexStyle( graph );
mxCompactTreeLayout layout = setupLayout( graph );
graph.getModel().beginUpdate();
mxCell v;
try
{
v = build( pTree.getRoot(), ( mxCell ) parent, 0 );
}
finally
{
graph.getModel().endUpdate();
}
layout.execute( parent, v );
}
private mxCompactTreeLayout setupLayout( mxGraph graph )
{
mxCompactTreeLayout layout = new mxCompactTreeLayout( graph, false );
layout.setEdgeRouting( true );
layout.setHorizontal( false );
layout.setLevelDistance( 100 );
layout.setNodeDistance( 50 );
layout.setUseBoundingBox( true );
( ( PtGraph ) graph ).setLayout( layout );
return layout;
}
private PtNodeVertex build( PtNode node, mxCell parent, int index )
{
PtNodeVertex source = insertVertex( node, parent, index );
if ( node.getChildrenCount() == 0 )
{
HaltVertex target = ( HaltVertex ) insertHaltVertex( parent );
source.addSuccessor( target );
insertEdge( parent, dummyEdge, source, target );
return source;
}
for ( int i = 0; i < node.getChildrenCount(); i++ )
{
PtNode node1 = node.getChild( i );
PtNodeVertex target = build( node1, parent, i );
source.addSuccessor( target );
insertEdge( parent, node1.getIn(), source, target );
}
return source;
}
PtNodeVertex insertVertex( PtNode node, mxCell parent, int index )
{
PtNodeVertex v = ( PtNodeVertex ) graph.insertVertex( parent, null, node, 0, 0, 0, 0, style );
v.setIndex( index );
return v;
}
private mxCell insertHaltVertex( mxCell parent )
{
mxCell v = ( mxCell ) graph.insertVertex( parent, null, dummyNode, 0, 0, 0, 0, style );
return v;
}
private void insertEdge( Object parent, PtEdge ptEdge, mxCell source, mxCell target )
{
if ( !( source instanceof HaltVertex ) )
{
graph.insertEdge( parent, null, ptEdge, source, target );
}
}
private static void registerRhombusVertexStyle( mxGraph graph )
{
mxStylesheet ss = graph.getStylesheet();
Map<String, Object> style = new Hashtable<String, Object>();
style.put( mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RHOMBUS );
style.put( mxConstants.STYLE_PERIMETER, mxPerimeter.RhombusPerimeter );
style.put( mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE );
style.put( mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER );
style.put( mxConstants.STYLE_FILLCOLOR, "#C3D9FF" );
style.put( mxConstants.STYLE_STROKECOLOR, "#6482B9" );
style.put( mxConstants.STYLE_FONTCOLOR, "#774400" );
ss.putCellStyle( "vRhombus", style );
style = ss.getDefaultEdgeStyle();
style.put( mxConstants.STYLE_EDGE, mxConstants.EDGESTYLE_ELBOW );
style.put( mxConstants.STYLE_ROUTING_CENTER_Y, 0.0 );
ss.putCellStyle( "rhombusEdge", style );
////////////////////////////////////////////////////////////////////////////////////
Map<String, Map<String, Object>> styles = ss.getStyles();
for ( String key : styles.keySet() )
{
Map<String, Object> _style = styles.get( key );
System.out.printf( "\n%s =\n", key );
System.out.println( "---------------------" );
for ( String key1 : _style.keySet() )
{
System.out.printf( "\n%s = %s", key1, _style.get( key1 ) );
}
System.out.println();
}
}
}
我找到了答案。
一般来说,如果您使用边缘样式或其他方式无法实现结果,那么您应该编写自己的布局。
可以是从头编写的布局,也可以是扩展或合并现有布局实现功能的布局。
关于我的问题,我扩展 mxCompactTreeLayout
覆盖 setEdgePoints
方法。
源代码:
PtCompactTreeLayout.java
package org.jsc.core.visualization.jgraphx;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxGraph;
import java.util.Collections;
import java.util.List;
public class PtCompactTreeLayout extends mxCompactTreeLayout
{
public PtCompactTreeLayout( mxGraph graph )
{
super( graph, false );
}
@Override
public void setEdgePoints( Object edge, List<mxPoint> points )
{
if ( !( edge instanceof IfElseEdge ) )
{
super.setEdgePoints( edge, points );
return;
}
IfElseEdge ifElseEdge = ( IfElseEdge ) edge;
IfElseNodeVertex v = ( IfElseNodeVertex ) ifElseEdge.getSource();
mxGeometry geo = v.getGeometry();
mxGeometry vb = ifElseEdge.getGeometry();
if ( vb == null )
{
super.setEdgePoints( edge, points );
return;
}
double xc = geo.getCenterX();
double yc = geo.getCenterY();
double w = geo.getWidth();
double h = geo.getHeight();
double xt;
double yt;
int i = ( ifElseEdge == v.getEdge( 0 ) ? 0 : 1 );
PtNodeVertex vs = v.getSuccessor( i );
mxGeometry sgeo0 = v.getSuccessor( 0 ).getGeometry();
mxGeometry sgeo1 = v.getSuccessor( 1 ).getGeometry();
double ws0 = sgeo0.getWidth();
xt = ( i == 0 ? sgeo0.getCenterX() : sgeo1.getCenterX());
yt = ( i == 0 ? sgeo0.getY() : sgeo1.getY() );
vb.setTargetPoint( new mxPoint( xt, yt ) );
double xm = xt;
mxPoint mp = new mxPoint( xm, yc );
vb.setPoints( Collections.singletonList( mp ) );
vb.setSourcePoint( calcSourcePoint( v, i ) );
}
private mxPoint calcSourcePoint( PtNodeVertex v, int i )
{
mxGeometry geom = v.getGeometry();
double w = geom.getWidth();
double xs = ( i == 0 ? geom.getX() : geom.getX() + geom.getWidth() );
double ys = geom.getCenterY();
return new mxPoint( xs, ys );
}
}