gmf_tutorial6 [GMF Samples And Tutorials]
 

Overview

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 :

  • the circle connections are not rightly connected to it
  • the operator input and output connections are not very clean

This tutorial is based on the model and diagram editor built in the fifth tutorial.

We will modify it in order to have :

  • centered anchor in the operator output and input nodes
  • north/south/east/west anchors in the result nodes (represented by a circle)

Full code is availabale through :

This tutorial has been built with :

  • Eclipse 3.5.1
  • Graphical Modeling Framework SDK 2.2.1
  • Eclipse Modeling Framework 2.5.0

Custom java components creation

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;
	}
 
}

GMF code modification

As we told in the tutorial's overview, we want to have :

  • centered anchor in the operator output and input nodes
  • north/south/east/west anchors in the result nodes (represented by a circle)

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 :

Thank you

I hope that this material will be helpful for you. If you want to support it, your help is welcome :

Discussion

Daniel Ferreira Jorge, 2011/09/27 18:46

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!!!

Jean-François Brazeau, 2011/12/08 20:52

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

António Silva, 2013/08/20 14:27

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!!!

 
gmf_tutorial6.txt · Last modified: 2011/08/08 20:07 by jfbraz
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki