如何在jgraphx图中制作所需forms的边缘?

我的图形包含具有单个外出边缘的矩形顶点和菱形形状的顶点以及两个外出边缘。

我正在使用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 completingNodesToViews = new HashMap(); private final PtNode node; protected mxCell[] labels; private List successors = new ArrayList( 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(), text, 1, 0, // ".selectRef { " + // "font-size:9px;font-weight:normal; }" ); label.setValue( text ); mxRectangle b = mxUtils.getLabelSize( text, new HashMap(), 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. * 

* This constructor sets the component's locale property to the value * returned by JComponent.getDefaultLocale. * * @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 style = new Hashtable(); 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> styles = ss.getStyles(); for ( String key : styles.keySet() ) { Map _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(); } } }

我找到了答案。

通常,如果您未能使用边缘样式或其他东西来实现结果,那么您应该编写自己的布局。

它可以是从头开始编写的布局,也可以是扩展或组合现有布局实现的function的布局。

关于我的问题,我扩展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 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 ); } }