My graph contains rectangular vertices with single outgoing edge and the vertices of rhombus shape with two outgoing edges.
I'm using mxCompactTreeLayout
and mostly everything is ok except that edges of the vertices of the second type look wrong for me.
I want them to look like in the picture
Instead, edges connected to the bottom segments of the rhombus.
What should I do to get edges look as desired.
EDIT: Added source code.
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();
}
}
}
I've found an answer.
In general, if you failed to achieve the result using edge styles or something else, then you should write your own layout.
It can be the layout written from scratch, or the layout extending or combining functionality of existing layout implementations.
Regarding my problem, I extend
mxCompactTreeLayout
overridingsetEdgePoints
method.Source code:
PtCompactTreeLayout.java