In this topic we will see how to force diagram edeges to be connected to their nodes through punctual locations.
This can become necesary for example when a figure is not rectangular (ex : a circle). As you can see above, if the edges are randomy positionned :
This tutorial is based on the model and diagram editor built in the fifth tutorial.
We will modify it in order to have :
Full code is availabale through :
This tutorial has been built with :
First we have to create a custom java component that will manage the “fixed anchors” on the node figure.
If you take a look at the code that is generated by GMF, you will see that the figure nodes are represented by a DefaultSizeNodeFigure
. These components include all the logic allowing to anchor the edges anywhere on a rectangle representing the node (or a custom figure if you decide to override the default polygon description in the getPolygonPoints
method (as shown in the fourth tutorial).
We will create a new component which extends this class but which changes its behaviour in order to have a few punctual anchors.
In the jfb.examples.gmf.math.diagram.edit.parts.custom
package, create a java class named DefaultSizeNodeFigureWithFixedAnchors
and paste the following java code into it.
package jfb.examples.gmf.math.diagram.edit.parts.custom; import java.util.HashMap; import java.util.Iterator; import org.eclipse.draw2d.ConnectionAnchor; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PrecisionPoint; import org.eclipse.gmf.runtime.gef.ui.figures.DefaultSizeNodeFigure; public class DefaultSizeNodeFigureWithFixedAnchors extends DefaultSizeNodeFigure { private HashMap<String, FixedConnectionAnchor> anchors = new HashMap<String, FixedConnectionAnchor>(); public DefaultSizeNodeFigureWithFixedAnchors(Dimension defSize, HashMap<String, PrecisionPoint> anchorLocations) { this(defSize.width, defSize.height, anchorLocations); } public DefaultSizeNodeFigureWithFixedAnchors(int width, int height, HashMap<String, PrecisionPoint> anchorLocations) { super(width, height); if (anchorLocations.size()==0) throw new IllegalArgumentException("At least one fixed anchor must be specified"); Iterator<String> terminals = anchorLocations.keySet().iterator(); while (terminals.hasNext()) { String terminal = terminals.next(); PrecisionPoint anchorLocation = anchorLocations.get(terminal); anchors.put(terminal, new FixedConnectionAnchor(this, anchorLocation)); } } @Override public ConnectionAnchor getSourceConnectionAnchorAt(Point p) { return findNearestAnchorFrom(p); } @Override public ConnectionAnchor getTargetConnectionAnchorAt(Point p) { return findNearestAnchorFrom(p); } @Override public ConnectionAnchor getConnectionAnchor(String terminal) { return anchors.get(terminal); } @Override public String getConnectionAnchorTerminal(ConnectionAnchor c) { String selectedTerminal = null; Iterator<String> terminals = anchors.keySet().iterator(); while (terminals.hasNext() && selectedTerminal==null) { String terminal = terminals.next(); FixedConnectionAnchor anchor = anchors.get(terminal); if (anchor == c) { selectedTerminal = terminal; } } return selectedTerminal; } private ConnectionAnchor findNearestAnchorFrom(Point point) { ConnectionAnchor result = null; if (point == null || anchors.size()==1) { result = anchors.values().iterator().next(); } else { double minDistance = Double.MAX_VALUE; String nearestTerminal = null; Iterator<String> terminals = anchors.keySet().iterator(); while (terminals.hasNext()) { String terminal = terminals.next(); FixedConnectionAnchor anchor = anchors.get(terminal); Point anchorPosition = anchor.getLocation(); double distance = point.getDistance(anchorPosition); if (distance < minDistance) { minDistance = distance; nearestTerminal = terminal; } } result = anchors.get(nearestTerminal); } return result; } }
As you can see above, this class manages a list of “fixed connecion anchors” that are created in the conctructor. This class is described below. These “fixed connection anchors” are caracterized by a PrecisionPoint
which is not a real location. Instead x
and y
are comprised between 0 and 1. The anchors are then automatically positionned among the x
and y
axis with the width and height of the figure. That way, we get a component whose behaviour is compatible with figure scaling.
To create the FixedConnectionAcnhor
component, in the jfb.examples.gmf.math.diagram.edit.parts.custom
package, create a java class named FixedConnectionAnchor
and paste the following java code into it.
package jfb.examples.gmf.math.diagram.edit.parts.custom; import org.eclipse.draw2d.AbstractConnectionAnchor; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PrecisionPoint; import org.eclipse.draw2d.geometry.Rectangle; public class FixedConnectionAnchor extends AbstractConnectionAnchor { private double xOffset; private double yOffset; public FixedConnectionAnchor(IFigure owner, PrecisionPoint offset) { this(owner, offset.preciseX, offset.preciseY); } public FixedConnectionAnchor(IFigure owner, double xOffset, double yOffset) { super(owner); this.xOffset = xOffset; this.yOffset = yOffset; } @Override public Point getLocation(Point point) { return getLocation(); } public Point getLocation() { Rectangle r = getOwner().getBounds(); Point p = new PrecisionPoint(r.x + r.width*xOffset, r.y + r.height*yOffset); getOwner().translateToAbsolute(p); return p; } }
As we told in the tutorial's overview, we want to have :
In the OperatorInputEditPart
class, localize the createNodePlate()
method, and replace DefaultSizeNodeFigure result = new DefaultSizeNodeFigure(40, 40);
by the foolowing java code (as you can see a single anchor is created approximatly in the center of the node) :
/** * @generated NOT */ protected NodeFigure createNodePlate() { HashMap<String, PrecisionPoint> anchorLocations = new HashMap<String, PrecisionPoint>(); // The anchor's location is a little bit on the left in order to be sure // that the edges will be horizontally oriented anchorLocations.put("CENTER", new PrecisionPoint(0.4d, 0.5d)); DefaultSizeNodeFigureWithFixedAnchors result = new DefaultSizeNodeFigureWithFixedAnchors(40, 40, anchorLocations); return result; }
The same way, in the OperatorOutputEditPart
class, localize the createNodePlate()
modify it like this :
/** * @generated NOT */ protected NodeFigure createNodePlate() { HashMap<String, PrecisionPoint> anchorLocations = new HashMap<String, PrecisionPoint>(); // The anchor's location is a little bit on the right in order to be sure // that the edges will be horizontally oriented anchorLocations.put("CENTER", new PrecisionPoint(0.6d, 0.5d)); DefaultSizeNodeFigureWithFixedAnchors result = new DefaultSizeNodeFigureWithFixedAnchors(40, 40, anchorLocations); return result; }
The same way, in the ResultEditPart
class, localize the createNodePlate()
modify it like this (as you can see 4 anchors are created, one for each cardinal direction) :
/** * @generated NOT */ protected NodeFigure createNodePlate() { HashMap<String, PrecisionPoint> anchorLocations = new HashMap<String, PrecisionPoint>(); anchorLocations.put("WEST", new PrecisionPoint(0, 0.5d)); anchorLocations.put("EAST", new PrecisionPoint(1d, 0.5d)); anchorLocations.put("NORTH", new PrecisionPoint(0.5d, 0)); anchorLocations.put("SOUTH", new PrecisionPoint(0.5d, 1d)); DefaultSizeNodeFigureWithFixedAnchors result = new DefaultSizeNodeFigureWithFixedAnchors(40, 40, anchorLocations); return result; }
If you restart your diagram editor you should get this result :
I hope that this material will be helpful for you. If you want to support it, your help is welcome :
Discussion
Hello my friend!
Thank you very much for your tutorials. You have no idea how much I learned from them!!
Can I ask you a question?
For this specific tutorial, everything works perfectly except for a weird problem. You know the connection handlers? When I click in a connection handler in the source element, a contextual menu appears for me to create a connection to a new or existing element. The problem is that every connection I create is being attached to the WEST.
I can re-attach the connection to the right place if I want and connecting two existing elements using the connection creation tool from the palette also works perfectly.
This problem only happens when I create a connection using the contextual menu… Do you have any idea what is going on?
THANK YOU!!!
Hello dear friend !
I'm trying to reproduce this issue but I can't manage to !!!! I can't find the contextual menu you mention !!
Could you be more precise ????
Thank you,
JFB
Hi,
First of all, great Tutorials! They're all helping me a lot.
Is there a way of choosing the anchor points that we want when creating a link(by code) between two nodes?
Thank you!!!