如何在 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 );
    }
}