You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 11:22:51 UTC

[28/51] [partial] incubator-taverna-workbench git commit: Revert "temporarily empty repository"

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java
new file mode 100644
index 0000000..3f3f85f
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphNode.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A node of a graph that can optionally contain other graphs.
+ * 
+ * @author David Withers
+ */
+public class GraphNode extends GraphShapeElement {
+	private List<GraphNode> sourceNodes = new ArrayList<>();
+	private List<GraphNode> sinkNodes = new ArrayList<>();
+	private Graph graph;
+	private boolean expanded;
+
+	/**
+	 * Constructs a new instance of Node.
+	 * 
+	 */
+	public GraphNode(GraphController graphController) {
+		super(graphController);
+	}
+
+	/**
+	 * Adds a sink node.
+	 * 
+	 * @param sinkNode
+	 *            the sink node to add
+	 */
+	public void addSinkNode(GraphNode sinkNode) {
+		sinkNode.setParent(this);
+		sinkNodes.add(sinkNode);
+	}
+
+	/**
+	 * Adds a source node.
+	 * 
+	 * @param sourceNode
+	 *            the source node to add
+	 */
+	public void addSourceNode(GraphNode sourceNode) {
+		sourceNode.setParent(this);
+		sourceNodes.add(sourceNode);
+	}
+
+	/**
+	 * Returns the graph that this node contains.
+	 * 
+	 * @return the graph that this node contains
+	 */
+	public Graph getGraph() {
+		return graph;
+	}
+
+	/**
+	 * Returns the sinkNodes.
+	 * 
+	 * @return the sinkNodes
+	 */
+	public List<GraphNode> getSinkNodes() {
+		return sinkNodes;
+	}
+
+	/**
+	 * Returns the sourceNodes.
+	 * 
+	 * @return the sourceNodes
+	 */
+	public List<GraphNode> getSourceNodes() {
+		return sourceNodes;
+	}
+
+	/**
+	 * Returns true if this node is expanded to show the contained graph.
+	 * 
+	 * @return true if this node is expanded
+	 */
+	public boolean isExpanded() {
+		return expanded;
+	}
+
+	/**
+	 * Removes a sink node.
+	 * 
+	 * @param sinkNode
+	 *            the node to remove
+	 * @return true if the node was removed, false otherwise
+	 */
+	public boolean removeSinkNode(GraphNode sinkNode) {
+		return sinkNodes.remove(sinkNode);
+	}
+
+	/**
+	 * Removes a source node.
+	 * 
+	 * @param sourceNode
+	 *            the node to remove
+	 * @return true if the node was removed, false otherwise
+	 */
+	public boolean removeSourceNode(GraphNode sourceNode) {
+		return sourceNodes.remove(sourceNode);
+	}
+
+	/**
+	 * Sets whether this node is expanded to show the contained graph.
+	 * 
+	 * @param expanded
+	 *            true if this node is expanded
+	 */
+	public void setExpanded(boolean expanded) {
+		this.expanded = expanded;
+	}
+
+	/**
+	 * Sets the graph that this node contains.
+	 * 
+	 * @param graph
+	 *            the new graph
+	 */
+	public void setGraph(Graph graph) {
+		if (graph != null)
+			graph.setParent(this);
+		this.graph = graph;
+	}
+
+	@Override
+	public void setSelected(boolean selected) {
+		super.setSelected(selected);
+		if (isExpanded())
+			getGraph().setSelected(selected);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java
new file mode 100644
index 0000000..1bb8b6d
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/GraphShapeElement.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (C) 2008 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph;
+
+import java.awt.Dimension;
+import java.awt.Point;
+
+/**
+ * A Graph element that has shape, size and position properties.
+ * 
+ * @author David Withers
+ */
+public class GraphShapeElement extends GraphElement {
+	public enum Shape {
+		BOX, RECORD, HOUSE, INVHOUSE, DOT, CIRCLE, TRIANGLE, INVTRIANGLE
+	}
+
+	private Shape shape;
+	private int x, y, width, height;
+
+	public GraphShapeElement(GraphController graphController) {
+		super(graphController);
+	}
+
+	/**
+	 * Returns the height.
+	 * 
+	 * @return the height
+	 */
+	public int getHeight() {
+		return height;
+	}
+
+	/**
+	 * Returns the position.
+	 * 
+	 * @return the position
+	 */
+	public Point getPosition() {
+		return new Point(x, y);
+	}
+
+	/**
+	 * Returns the shape of the element.
+	 * 
+	 * @return the shape of the element
+	 */
+	public Shape getShape() {
+		return shape;
+	}
+
+	/**
+	 * Returns the width.
+	 * 
+	 * @return the width
+	 */
+	public int getWidth() {
+		return width;
+	}
+
+	/**
+	 * Sets the position.
+	 * 
+	 * @param position
+	 *            the new position
+	 */
+	public void setPosition(Point position) {
+		x = position.x;
+		y = position.y;
+	}
+
+	/**
+	 * Sets the shape of the element.
+	 * 
+	 * @param shape
+	 *            the new shape of the element
+	 */
+	public void setShape(Shape shape) {
+		this.shape = shape;
+	}
+
+	/**
+	 * Returns the size of the element.
+	 * 
+	 * @return the size of the element
+	 */
+	public Dimension getSize() {
+		return new Dimension(width, height);
+	}
+
+	/**
+	 * Sets the size of the element.
+	 * 
+	 * @param size
+	 *            the new size of the node
+	 */
+	public void setSize(Dimension size) {
+		width = size.width;
+		height = size.height;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java
new file mode 100644
index 0000000..1205953
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/dot/GraphLayout.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * Copyright (C) 2008 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph.dot;
+
+import static java.lang.Float.parseFloat;
+import static net.sf.taverna.t2.workbench.models.graph.Graph.Alignment.HORIZONTAL;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Lays out a graph from a DOT layout.
+ * 
+ * @author David Withers
+ */
+public class GraphLayout implements DOTParserVisitor {
+	private static final Logger logger = Logger.getLogger(GraphLayout.class);
+	private static final int BORDER = 10;
+
+	private Rectangle bounds;
+	private Rectangle requiredBounds;
+	private GraphController graphController;
+	private int xOffset;
+	private int yOffset;
+
+	public Rectangle layoutGraph(GraphController graphController, Graph graph,
+			String laidOutDot, Rectangle requiredBounds) throws ParseException {
+		this.graphController = graphController;
+		this.requiredBounds = requiredBounds;
+
+		bounds = null;
+		xOffset = 0;
+		yOffset = 0;
+
+		logger.debug(laidOutDot);
+		DOTParser parser = new DOTParser(new StringReader(laidOutDot));
+		parser.parse().jjtAccept(this, graph);
+
+		// int xOffset = (bounds.width - bounds.width) / 2;
+		// int yOffset = (bounds.height - bounds.height) / 2;
+
+		return new Rectangle(xOffset, yOffset, bounds.width, bounds.height);
+	}
+
+	@Override
+	public Object visit(SimpleNode node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTParse node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTGraph node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTStatementList node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTStatement node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTAttributeStatement node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTNodeStatement node, Object data) {
+		GraphElement element = graphController.getElement(removeQuotes(node
+				.getName()));
+		if (element != null)
+			return node.childrenAccept(this, element);
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTNodeId node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTPort node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTEdgeStatement node, Object data) {
+		StringBuilder id = new StringBuilder();
+		id.append(removeQuotes(node.getName()));
+		if (node.getPort() != null) {
+			id.append(":");
+			id.append(removeQuotes(node.getPort()));
+		}
+		if (node.children != null)
+			for (Node child : node.children)
+				if (child instanceof ASTEdgeRHS) {
+					NamedNode rhsNode = (NamedNode) child.jjtAccept(this, data);
+					id.append("->");
+					id.append(removeQuotes(rhsNode.getName()));
+					if (rhsNode.getPort() != null) {
+						id.append(":");
+						id.append(removeQuotes(rhsNode.getPort()));
+					}
+				}
+		GraphElement element = graphController.getElement(id.toString());
+		if (element != null)
+			return node.childrenAccept(this, element);
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTSubgraph node, Object data) {
+		GraphElement element = graphController.getElement(removeQuotes(
+				node.getName()).substring("cluster_".length()));
+		if (element != null)
+			return node.childrenAccept(this, element);
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTEdgeRHS node, Object data) {
+		return node;
+	}
+
+	@Override
+	public Object visit(ASTAttributeList node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTAList node, Object data) {
+		if (data instanceof Graph) {
+			Graph graph = (Graph) data;
+			if ("bb".equalsIgnoreCase(node.getName())) {
+				Rectangle rect = getRectangle(node.getValue());
+				if (rect.width == 0 && rect.height == 0) {
+					rect.width = 500;
+					rect.height = 500;
+				}
+				if (bounds == null) {
+					bounds = calculateBounds(rect);
+					rect = bounds;
+				}
+				graph.setSize(rect.getSize());
+				graph.setPosition(rect.getLocation());
+			} else if ("lp".equalsIgnoreCase(node.getName())) {
+				if (bounds != null)
+					graph.setLabelPosition(getPoint(node.getValue()));
+			}
+		} else if (data instanceof GraphNode) {
+			GraphNode graphNode = (GraphNode) data;
+			if ("width".equalsIgnoreCase(node.getName()))
+				graphNode.setSize(new Dimension(getSize(node.getValue()),
+						graphNode.getHeight()));
+			else if ("height".equalsIgnoreCase(node.getName()))
+				graphNode.setSize(new Dimension(graphNode.getWidth(),
+						getSize(node.getValue())));
+			else if ("pos".equalsIgnoreCase(node.getName())) {
+				Point position = getPoint(node.getValue());
+				position.x = position.x - (graphNode.getWidth() / 2);
+				position.y = position.y - (graphNode.getHeight() / 2);
+				graphNode.setPosition(position);
+			} else if ("rects".equalsIgnoreCase(node.getName())) {
+				List<Rectangle> rectangles = getRectangles(node.getValue());
+				List<GraphNode> sinkNodes = graphNode.getSinkNodes();
+				if (graphController.getAlignment().equals(HORIZONTAL)) {
+					Rectangle rect = rectangles.remove(0);
+					graphNode.setSize(rect.getSize());
+					graphNode.setPosition(rect.getLocation());
+				} else {
+					Rectangle rect = rectangles.remove(sinkNodes.size());
+					graphNode.setSize(rect.getSize());
+					graphNode.setPosition(rect.getLocation());
+				}
+				Point origin = graphNode.getPosition();
+				for (GraphNode sinkNode : sinkNodes) {
+					Rectangle rect = rectangles.remove(0);
+					rect.setLocation(rect.x - origin.x, rect.y - origin.y);
+					sinkNode.setSize(rect.getSize());
+					sinkNode.setPosition(rect.getLocation());
+				}
+				for (GraphNode sourceNode : graphNode.getSourceNodes()) {
+					Rectangle rect = rectangles.remove(0);
+					rect.setLocation(rect.x - origin.x, rect.y - origin.y);
+					sourceNode.setSize(rect.getSize());
+					sourceNode.setPosition(rect.getLocation());
+				}
+			}
+		} else if (data instanceof GraphEdge) {
+			GraphEdge graphEdge = (GraphEdge) data;
+			if ("pos".equalsIgnoreCase(node.getName()))
+				graphEdge.setPath(getPath(node.getValue()));
+		}
+		return node.childrenAccept(this, data);
+	}
+
+	private Rectangle calculateBounds(Rectangle bounds) {
+		bounds = new Rectangle(bounds);
+		bounds.width += BORDER;
+		bounds.height += BORDER;
+		Rectangle newBounds = new Rectangle(bounds);
+		double ratio = bounds.width / (float) bounds.height;
+		double requiredRatio = requiredBounds.width
+				/ (float) requiredBounds.height;
+		// adjust the bounds so they match the aspect ration of the required bounds
+		if (ratio > requiredRatio)
+			newBounds.height = (int) (ratio / requiredRatio * bounds.height);
+		else if (ratio < requiredRatio)
+			newBounds.width = (int) (requiredRatio / ratio * bounds.width);
+
+		xOffset = (newBounds.width - bounds.width) / 2;
+		yOffset = (newBounds.height - bounds.height) / 2;
+		// adjust the bounds and so they are not less than the required bounds
+		if (newBounds.width < requiredBounds.width) {
+			xOffset += (requiredBounds.width - newBounds.width) / 2;
+			newBounds.width = requiredBounds.width;
+		}
+		if (newBounds.height < requiredBounds.height) {
+			yOffset += (requiredBounds.height - newBounds.height) / 2;
+			newBounds.height = requiredBounds.height;
+		}
+		// adjust the offset for the border
+		xOffset += BORDER / 2;
+		yOffset += BORDER / 2;
+		return newBounds;
+	}
+
+	private List<Point> getPath(String value) {
+		List<Point> path = new ArrayList<>();
+		for (String point : removeQuotes(value).split(" ")) {
+			String[] coords = point.split(",");
+			if (coords.length == 2) {
+				int x = (int) parseFloat(coords[0]) + xOffset;
+				int y = (int) parseFloat(coords[1]) + yOffset;
+				path.add(new Point(x, flipY(y)));
+			}
+		}
+		return path;
+	}
+
+	private int flipY(int y) {
+		return bounds.height - y;
+	}
+
+	private List<Rectangle> getRectangles(String value) {
+		List<Rectangle> rectangles = new ArrayList<>();
+		String[] rects = value.split(" ");
+		for (String rectangle : rects)
+			rectangles.add(getRectangle(rectangle));
+		return rectangles;
+	}
+
+	private Rectangle getRectangle(String value) {
+		String[] coords = removeQuotes(value).split(",");
+		Rectangle rectangle = new Rectangle();
+		rectangle.x = (int) parseFloat(coords[0]);
+		rectangle.y = (int) parseFloat(coords[3]);
+		rectangle.width = (int) parseFloat(coords[2]) - rectangle.x;
+		rectangle.height = rectangle.y - (int) parseFloat(coords[1]);
+		rectangle.x += xOffset;
+		rectangle.y += yOffset;
+		if (bounds != null)
+			rectangle.y = flipY(rectangle.y);
+		else
+			rectangle.y = rectangle.height - rectangle.y;
+		return rectangle;
+	}
+
+	private Point getPoint(String value) {
+		String[] coords = removeQuotes(value).split(",");
+		return new Point(xOffset + (int) parseFloat(coords[0]), flipY(yOffset
+				+ (int) parseFloat(coords[1])));
+	}
+
+	private int getSize(String value) {
+		return (int) (parseFloat(removeQuotes(value)) * 72);
+	}
+
+	private String removeQuotes(String value) {
+		String result = value.trim();
+		if (result.startsWith("\""))
+			result = result.substring(1);
+		if (result.endsWith("\""))
+			result = result.substring(0, result.length() - 1);
+		result = result.replaceAll("\\\\", "");
+		return result;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java
new file mode 100644
index 0000000..be92cdd
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraph.java
@@ -0,0 +1,439 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.COMPLETED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculatePoints;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static org.apache.batik.util.CSSConstants.CSS_BLACK_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TRANSFORM_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_CLICK_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_END_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_FAMILY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_SIZE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_MIDDLE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEMOVE_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEUP_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TEXT_ANCHOR_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_TRANSLATE;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseClickEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseMovedEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOutEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOverEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseUpEventListener;
+
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.apache.batik.dom.svg.SVGOMTextElement;
+import org.w3c.dom.Text;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.svg.SVGElement;
+
+/**
+ * SVG representation of a graph.
+ * 
+ * @author David Withers
+ */
+public class SVGGraph extends Graph {
+	private SVGGraphController graphController;
+	private SVGGraphElementDelegate delegate;
+	private SVGMouseClickEventListener mouseClickAction;
+	private SVGMouseMovedEventListener mouseMovedAction;
+	private SVGMouseUpEventListener mouseUpAction;
+	@SuppressWarnings("unused")
+	private SVGMouseOverEventListener mouseOverAction;
+	@SuppressWarnings("unused")
+	private SVGMouseOutEventListener mouseOutAction;
+	private SVGOMGElement mainGroup, labelGroup;
+	private SVGOMPolygonElement polygon, completedPolygon;
+	private SVGOMTextElement label, iteration, error;
+	private Text labelText, iterationText, errorsText;
+	private SVGOMAnimationElement animateShape, animatePosition, animateLabel;
+
+	public SVGGraph(SVGGraphController graphController) {
+		super(graphController);
+		this.graphController = graphController;
+
+		mouseClickAction = new SVGMouseClickEventListener(this);
+		mouseMovedAction = new SVGMouseMovedEventListener(this);
+		mouseUpAction = new SVGMouseUpEventListener(this);
+		mouseOverAction = new SVGMouseOverEventListener(this);
+		mouseOutAction = new SVGMouseOutEventListener(this);
+
+		mainGroup = graphController.createGElem();
+		mainGroup.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "10");
+		mainGroup.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "Helvetica");
+		mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, CSS_BLACK_VALUE);
+		mainGroup.setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, CSS_NONE_VALUE);
+		mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+		mainGroup.setAttribute(SVG_FILL_ATTRIBUTE, CSS_NONE_VALUE);
+
+		EventTarget t = (EventTarget) mainGroup;
+		t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+		t.addEventListener(SVG_MOUSEMOVE_EVENT_TYPE, mouseMovedAction, false);
+		t.addEventListener(SVG_MOUSEUP_EVENT_TYPE, mouseUpAction, false);
+		// t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+		// t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+
+		polygon = graphController.createPolygon();
+		mainGroup.appendChild(polygon);
+
+		completedPolygon = graphController.createPolygon();
+		completedPolygon.setAttribute(SVG_POINTS_ATTRIBUTE,
+				calculatePoints(getShape(), 0, 0));
+		completedPolygon.setAttribute(SVG_FILL_ATTRIBUTE, COMPLETED_COLOUR);
+		// completedPolygon.setAttribute(SVGConstants.SVG_FILL_OPACITY_ATTRIBUTE, "0.8");
+		// completedPolygon.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+		mainGroup.appendChild(completedPolygon);
+
+		labelText = graphController.createText("");
+		label = graphController.createText(labelText);
+		label.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_MIDDLE_VALUE);
+		label.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+		label.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+		labelGroup = graphController.createGElem();
+		labelGroup.appendChild(label);
+		mainGroup.appendChild(labelGroup);
+
+		iterationText = graphController.createText("");
+		iteration = graphController.createText(iterationText);
+		iteration.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_END_VALUE);
+		iteration.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "6");
+		iteration.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+		iteration.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+		iteration.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+		polygon.appendChild(iteration);
+
+		errorsText = graphController.createText("");
+		error = graphController.createText(errorsText);
+		error.setAttribute(SVG_TEXT_ANCHOR_ATTRIBUTE, SVG_END_VALUE);
+		error.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "6");
+		error.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+		error.setAttribute(SVG_FILL_ATTRIBUTE, CSS_BLACK_VALUE);
+		error.setAttribute(SVG_STROKE_ATTRIBUTE, SVG_NONE_VALUE);
+		polygon.appendChild(error);
+
+		animateShape = createAnimationElement(graphController, SVG_ANIMATE_TAG,
+				SVG_POINTS_ATTRIBUTE, null);
+
+		animatePosition = createAnimationElement(graphController,
+				SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+				TRANSFORM_TRANSLATE);
+
+		animateLabel = createAnimationElement(graphController,
+				SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+				TRANSFORM_TRANSLATE);
+
+		delegate = new SVGGraphElementDelegate(graphController, this, mainGroup);
+	}
+
+	public SVGElement getSVGElement() {
+		return mainGroup;
+	}
+
+	@Override
+	public void addEdge(GraphEdge edge) {
+		if (edge instanceof SVGGraphEdge) {
+			final SVGGraphEdge svgGraphEdge = (SVGGraphEdge) edge;
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+				    float opacity = svgGraphEdge.getOpacity();
+					svgGraphEdge.setOpacity(0);
+					mainGroup.appendChild(svgGraphEdge.getSVGElement());
+					svgGraphEdge.setOpacity(opacity);
+				}
+			});
+		}
+		super.addEdge(edge);
+	}
+
+	@Override
+	public void addNode(GraphNode node) {
+		super.addNode(node);
+		if (node instanceof SVGGraphNode) {
+			final SVGGraphNode svgGraphNode = (SVGGraphNode) node;
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+				    float opacity = svgGraphNode.getOpacity();
+					svgGraphNode.setOpacity(0);
+					mainGroup.appendChild(svgGraphNode.getSVGElement());
+					svgGraphNode.setOpacity(opacity);
+				}
+			});
+		}
+	}
+
+	@Override
+	public void addSubgraph(Graph subgraph) {
+		super.addSubgraph(subgraph);
+		if (subgraph instanceof SVGGraph) {
+			final SVGGraph svgGraph = (SVGGraph) subgraph;
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+				    float opacity = svgGraph.getOpacity();
+					svgGraph.setOpacity(0);
+					mainGroup.appendChild(svgGraph.getSVGElement());
+					svgGraph.setOpacity(opacity);
+				}
+			});
+		}
+	}
+
+	@Override
+	public boolean removeEdge(GraphEdge edge) {
+		if (edge instanceof SVGGraphEdge) {
+			final SVGGraphEdge svgGraphEdge = (SVGGraphEdge) edge;
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.removeChild(svgGraphEdge.getSVGElement());
+				}
+			});
+		}
+		return super.removeEdge(edge);
+	}
+
+	@Override
+	public boolean removeNode(GraphNode node) {
+		if (node instanceof SVGGraphNode) {
+			final SVGGraphNode svgGraphNode = (SVGGraphNode) node;
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.removeChild(svgGraphNode.getSVGElement());
+				}
+			});
+		}
+		return super.removeNode(node);
+	}
+
+	@Override
+	public boolean removeSubgraph(Graph subgraph) {
+		if (subgraph instanceof SVGGraph) {
+			final SVGGraph svgGraph = (SVGGraph) subgraph;
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.removeChild(svgGraph.getSVGElement());
+				}
+			});
+		}
+		return super.removeSubgraph(subgraph);
+	}
+
+	@Override
+	public void setPosition(final Point position) {
+		final Point oldPosition = getPosition();
+		if (position != null && !position.equals(oldPosition)) {
+			super.setPosition(position);
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					if (graphController.isAnimatable())
+						animate(animatePosition, polygon,
+								graphController.getAnimationSpeed(),
+								oldPosition.x + ", " + oldPosition.y,
+								position.x + ", " + position.y);
+					else
+						polygon.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+								"translate(" + position.x + " " + position.y
+										+ ")");
+				}
+			});
+		}
+	}
+
+	@Override
+	public void setSize(final Dimension size) {
+		final Dimension oldSize = getSize();
+		if (size != null && !size.equals(oldSize)) {
+			super.setSize(size);
+			updateShape(oldSize.width, oldSize.height);
+		}
+	}
+
+	@Override
+	public void setShape(Shape shape) {
+		final Dimension oldSize = getSize();
+		final Shape currentShape = getShape();
+		if (shape != null && !shape.equals(currentShape)) {
+			super.setShape(shape);
+			updateShape(oldSize.width, oldSize.height);
+		}
+	}
+
+	@Override
+	public void setLabel(final String label) {
+		super.setLabel(label);
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				labelText.setData(label);
+			}
+		});
+	}
+
+	@Override
+	public void setLabelPosition(final Point labelPosition) {
+		final Point oldLabelPosition = getLabelPosition();
+		if (labelPosition != null && !labelPosition.equals(oldLabelPosition)) {
+			super.setLabelPosition(labelPosition);
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					if (graphController.isAnimatable()
+							&& oldLabelPosition != null)
+						animate(animateLabel, labelGroup,
+								graphController.getAnimationSpeed(),
+								oldLabelPosition.x + ", " + oldLabelPosition.y,
+								labelPosition.x + ", " + labelPosition.y);
+					else
+						labelGroup.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+								"translate(" + labelPosition.x + " "
+										+ labelPosition.y + ")");
+				}
+			});
+		}
+	}
+
+	@Override
+	public void setIteration(final int iteration) {
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				if (iteration > 0)
+					iterationText.setData(String.valueOf(iteration));
+				else
+					iterationText.setData("");
+			}
+		});
+	}
+
+	@Override
+	public void setCompleted(final float complete) {
+		super.setCompleted(complete);
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				Dimension size = getSize();
+				Point position = getPosition();
+				completedPolygon.setAttribute(
+						SVG_POINTS_ATTRIBUTE,
+						calculatePoints(getShape(),
+								(int) (size.width * complete), size.height));
+				completedPolygon.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+						"translate(" + position.x + " " + position.y + ")");
+			}
+		});
+	}
+
+	private void updateShape(final int oldWidth, final int oldHeight) {
+		if (getShape() != null && getWidth() > 0f && getHeight() > 0f) {
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					if (graphController.isAnimatable())
+						animate(animateShape,
+								polygon,
+								graphController.getAnimationSpeed(),
+								calculatePoints(getShape(), oldWidth, oldHeight),
+								calculatePoints(getShape(), getWidth(),
+										getHeight()));
+					else {
+						polygon.setAttribute(
+								SVG_POINTS_ATTRIBUTE,
+								calculatePoints(getShape(), getWidth(),
+										getHeight()));
+						iteration.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+								"translate(" + (getWidth() - 1.5) + " 5.5)");
+						error.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+								"translate(" + (getWidth() - 1.5) + " "
+										+ (getHeight() - 1) + ")");
+					}
+				}
+			});
+		}
+	}
+
+	@Override
+	public void setSelected(final boolean selected) {
+		delegate.setSelected(selected);
+		super.setSelected(selected);
+	}
+
+	@Override
+	public void setLineStyle(final LineStyle lineStyle) {
+		delegate.setLineStyle(lineStyle);
+		super.setLineStyle(lineStyle);
+	}
+
+	@Override
+	public void setColor(final Color color) {
+		delegate.setColor(color);
+		super.setColor(color);
+	}
+
+	@Override
+	public void setFillColor(final Color fillColor) {
+		delegate.setFillColor(fillColor);
+		super.setFillColor(fillColor);
+	}
+
+	@Override
+	public void setVisible(final boolean visible) {
+		delegate.setVisible(visible);
+		super.setVisible(visible);
+	}
+
+	@Override
+	public void setFiltered(final boolean filtered) {
+		delegate.setFiltered(filtered);
+		super.setFiltered(filtered);
+	}
+
+	@Override
+	public void setOpacity(final float opacity) {
+		delegate.setOpacity(opacity);
+		super.setOpacity(opacity);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java
new file mode 100644
index 0000000..a4c8e4c
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphController.java
@@ -0,0 +1,555 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph.svg;
+
+import static java.awt.Color.BLACK;
+import static java.awt.Color.GREEN;
+import static java.lang.Float.parseFloat;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculateAngle;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createSVGDocument;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getDot;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getHexValue;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.svgNS;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ELLIPSE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_FAMILY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FONT_SIZE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_G_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_LINE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_PATH_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_POLYGON_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_RECT_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_STYLE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TEXT_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_VIEW_BOX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X1_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_X2_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y1_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y2_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_Y_ATTRIBUTE;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import net.sf.taverna.t2.ui.menu.MenuManager;
+import net.sf.taverna.t2.workbench.configuration.colour.ColourManager;
+import net.sf.taverna.t2.workbench.configuration.workbench.WorkbenchConfiguration;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.models.graph.DotWriter;
+import net.sf.taverna.t2.workbench.models.graph.Graph;
+import net.sf.taverna.t2.workbench.models.graph.Graph.Alignment;
+import net.sf.taverna.t2.workbench.models.graph.GraphController;
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphNode;
+import net.sf.taverna.t2.workbench.models.graph.dot.GraphLayout;
+import net.sf.taverna.t2.workbench.models.graph.dot.ParseException;
+
+import org.apache.batik.bridge.UpdateManager;
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMEllipseElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPathElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.apache.batik.dom.svg.SVGOMRectElement;
+import org.apache.batik.dom.svg.SVGOMTextElement;
+import org.apache.batik.swing.JSVGCanvas;
+import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
+import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGElement;
+import org.w3c.dom.svg.SVGPoint;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import uk.org.taverna.scufl2.api.core.Workflow;
+import uk.org.taverna.scufl2.api.profiles.Profile;
+
+public class SVGGraphController extends GraphController {
+	private static final Logger logger = Logger.getLogger(SVGGraphController.class);
+	@SuppressWarnings("unused")
+	private static final Timer timer = new Timer("SVG Graph controller timer", true);
+	private static final String dotErrorMessage = "Cannot draw diagram(s)\n" +
+			"\n" +
+			"Install dot as described\n" +
+			"at http://www.taverna.org.uk\n" +
+			"and specify its location\n" +
+			"in the workbench preferences";
+
+	private Map<String, List<SVGGraphEdge>> datalinkMap = new HashMap<>();
+	private final JSVGCanvas svgCanvas;
+	private SVGDocument svgDocument;
+	private GraphLayout graphLayout = new GraphLayout();
+	private EdgeLine edgeLine;
+	private UpdateManager updateManager;
+	private ExecutorService executor = Executors.newFixedThreadPool(1);
+	private boolean drawingDiagram = false;
+	private int animationSpeed;
+	private Rectangle bounds, oldBounds;
+	private SVGOMAnimationElement animateBounds;
+	private boolean dotMissing = false;
+	private final WorkbenchConfiguration workbenchConfiguration;
+
+	public SVGGraphController(Workflow dataflow, Profile profile,
+			boolean interactive, JSVGCanvas svgCanvas, EditManager editManager,
+			MenuManager menuManager, ColourManager colourManager,
+			WorkbenchConfiguration workbenchConfiguration) {
+		super(dataflow, profile, interactive, svgCanvas, editManager,
+				menuManager, colourManager);
+		this.svgCanvas = svgCanvas;
+		this.workbenchConfiguration = workbenchConfiguration;
+		installUpdateManager();
+		layoutSVGDocument(svgCanvas.getBounds());
+		svgCanvas.setDocument(getSVGDocument());
+	}
+
+	public SVGGraphController(Workflow dataflow, Profile profile,
+			boolean interactive, JSVGCanvas svgCanvas, Alignment alignment,
+			PortStyle portStyle, EditManager editManager,
+			MenuManager menuManager, ColourManager colourManager,
+			WorkbenchConfiguration workbenchConfiguration) {
+		super(dataflow, profile, interactive, svgCanvas, alignment, portStyle,
+				editManager, menuManager, colourManager);
+		this.svgCanvas = svgCanvas;
+		this.workbenchConfiguration = workbenchConfiguration;
+		installUpdateManager();
+		layoutSVGDocument(svgCanvas.getBounds());
+		svgCanvas.setDocument(getSVGDocument());
+	}
+
+	private void installUpdateManager() {
+		svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
+			@Override
+			public void gvtRenderingCompleted(GVTTreeRendererEvent ev) {
+				setUpdateManager(svgCanvas.getUpdateManager());
+			}
+		});
+	}
+	
+	@Override
+	public GraphEdge createGraphEdge() {
+		return new SVGGraphEdge(this);
+	}
+
+	@Override
+	public Graph createGraph() {
+		return new SVGGraph(this);
+	}
+
+	@Override
+	public GraphNode createGraphNode() {
+		return new SVGGraphNode(this);
+	}
+
+	public JSVGCanvas getSVGCanvas() {
+		return svgCanvas;
+	}
+
+	public synchronized SVGDocument getSVGDocument() {
+		if (svgDocument == null)
+			svgDocument = createSVGDocument();
+		return svgDocument;
+	}
+
+	@Override
+	public void redraw() {
+		Graph graph = generateGraph();
+		Rectangle actualBounds = layoutGraph(graph, svgCanvas.getBounds());
+		setBounds(actualBounds);
+		transformGraph(getGraph(), graph);
+	}
+
+	private void layoutSVGDocument(Rectangle bounds) {
+		animateBounds = createAnimationElement(this, SVG_ANIMATE_TAG,
+				SVG_VIEW_BOX_ATTRIBUTE, null);
+		updateManager = null;
+		datalinkMap.clear();
+
+		Graph graph = getGraph();
+		if (graph instanceof SVGGraph) {
+			SVGGraph svgGraph = (SVGGraph) graph;
+			SVGSVGElement svgElement = getSVGDocument().getRootElement();
+			SVGElement graphElement = svgGraph.getSVGElement();
+			svgElement.appendChild(graphElement);
+
+			setBounds(layoutGraph(graph, bounds));
+
+			edgeLine = EdgeLine.createAndAdd(getSVGDocument(), this);
+		}
+		drawingDiagram = true;
+	}
+
+	public Rectangle layoutGraph(Graph graph, Rectangle bounds) {
+		Rectangle actualBounds = null;
+		bounds = new Rectangle(bounds);
+		StringWriter stringWriter = new StringWriter();
+		DotWriter dotWriter = new DotWriter(stringWriter);
+		try {
+			dotWriter.writeGraph(graph);
+			String layout = getDot(stringWriter.toString(), workbenchConfiguration);
+			if (layout.isEmpty())
+				logger.warn("Invalid dot returned");
+			else
+				actualBounds = graphLayout.layoutGraph(this, graph, layout, bounds);
+		} catch (IOException e) {
+			outputMessage(dotErrorMessage);
+			setDotMissing(true);
+			logger.warn("Couldn't generate dot");
+		} catch (ParseException e) {
+			logger.warn("Couldn't layout graph", e);
+		}
+		return actualBounds;
+	}
+
+	private void setDotMissing(boolean b) {
+		this.dotMissing = b;
+	}
+
+	public boolean isDotMissing() {
+		return dotMissing;
+	}
+
+	public void setBounds(final Rectangle bounds) {
+		oldBounds = this.bounds;
+		this.bounds = bounds;
+		updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				SVGSVGElement svgElement = getSVGDocument().getRootElement();
+				if (isAnimatable() && oldBounds != null) {
+					String from = "0 0 " + oldBounds.width + " "
+							+ oldBounds.height;
+					String to = "0 0 " + bounds.width + " " + bounds.height;
+					animate(animateBounds, svgElement, getAnimationSpeed(),
+							from, to);
+				} else if ((svgElement != null) && (bounds != null))
+					svgElement.setAttribute(SVG_VIEW_BOX_ATTRIBUTE,
+							"0 0 " + String.valueOf(bounds.width) + " "
+									+ String.valueOf(bounds.height));
+			}
+		});
+	}
+
+	private void outputMessage(final String message) {
+		SVGSVGElement svgElement = getSVGDocument().getRootElement();
+		String[] parts = message.split("\n");
+		int initialPosition = 200;
+		for (int i = 0; i < parts.length; i++) {
+			Text errorsText = createText(parts[i]);
+			SVGOMTextElement error = (SVGOMTextElement) createElement(SVG_TEXT_TAG);
+			error.setAttribute(SVG_Y_ATTRIBUTE,
+					Integer.toString(initialPosition + i * 60));
+			error.setAttribute(SVG_FONT_SIZE_ATTRIBUTE, "20");
+			error.setAttribute(SVG_FONT_FAMILY_ATTRIBUTE, "sans-serif");
+			error.setAttribute(SVG_FILL_ATTRIBUTE, "red");
+			error.appendChild(errorsText);
+			svgElement.appendChild(error);
+		}
+		bounds = new Rectangle(300, parts.length * 60 + 200);
+		svgCanvas.setDocument(getSVGDocument());
+	}
+
+	public void setUpdateManager(UpdateManager updateManager) {
+		this.updateManager = updateManager;
+		drawingDiagram = false;
+		resetSelection();
+	}
+
+	@Override
+	public boolean startEdgeCreation(GraphElement graphElement, Point point) {
+		boolean alreadyStarted = edgeCreationFromSource || edgeCreationFromSink;
+		boolean started = super.startEdgeCreation(graphElement, point);
+		if (!alreadyStarted && started) {
+			if (edgeMoveElement instanceof SVGGraphEdge) {
+				SVGGraphEdge svgGraphEdge = (SVGGraphEdge) edgeMoveElement;
+				SVGPoint sourcePoint = svgGraphEdge.getPathElement()
+						.getPointAtLength(0f);
+				edgeLine.setSourcePoint(new Point((int) sourcePoint.getX(),
+						(int) sourcePoint.getY()));
+			} else
+				edgeLine.setSourcePoint(point);
+			edgeLine.setTargetPoint(point);
+			edgeLine.setColour(Color.BLACK);
+			// edgeLine.setVisible(true);
+		}
+		return started;
+	}
+
+	@Override
+	public boolean moveEdgeCreationTarget(GraphElement graphElement, Point point) {
+		boolean linkValid = super.moveEdgeCreationTarget(graphElement, point);
+		if (edgeMoveElement instanceof SVGGraphEdge)
+			((SVGGraphEdge) edgeMoveElement).setVisible(false);
+		if (edgeCreationFromSink) {
+			edgeLine.setSourcePoint(point);
+			if (linkValid)
+				edgeLine.setColour(GREEN);
+			else
+				edgeLine.setColour(BLACK);
+			edgeLine.setVisible(true);
+		} else if (edgeCreationFromSource) {
+			edgeLine.setTargetPoint(point);
+			if (linkValid)
+				edgeLine.setColour(GREEN);
+			else
+				edgeLine.setColour(BLACK);
+			edgeLine.setVisible(true);
+		}
+		return linkValid;
+	}
+
+	@Override
+	public boolean stopEdgeCreation(GraphElement graphElement, Point point) {
+		GraphEdge movedEdge = edgeMoveElement;
+		boolean edgeCreated = super.stopEdgeCreation(graphElement, point);
+		if (!edgeCreated && movedEdge instanceof SVGGraphEdge)
+			((SVGGraphEdge) movedEdge).setVisible(true);
+		edgeLine.setVisible(false);
+		return edgeCreated;
+	}
+
+	@Override
+	public void setEdgeActive(String edgeId, boolean active) {
+		if (datalinkMap.containsKey(edgeId))
+			for (GraphEdge datalink : datalinkMap.get(edgeId))
+				datalink.setActive(active);
+	}
+
+	public Element createElement(String tag) {
+		return getSVGDocument().createElementNS(svgNS, tag);
+	}
+
+	SVGOMGElement createGElem() {
+		return (SVGOMGElement) createElement(SVG_G_TAG);
+	}
+
+	SVGOMPolygonElement createPolygon() {
+		return (SVGOMPolygonElement) createElement(SVG_POLYGON_TAG);
+	}
+
+	SVGOMEllipseElement createEllipse() {
+		return (SVGOMEllipseElement) createElement(SVG_ELLIPSE_TAG);
+	}
+
+	SVGOMPathElement createPath() {
+		return (SVGOMPathElement) createElement(SVG_PATH_TAG);
+	}
+
+	SVGOMRectElement createRect() {
+		return (SVGOMRectElement) createElement(SVG_RECT_TAG);
+	}
+
+	public Text createText(String text) {
+		return getSVGDocument().createTextNode(text);
+	}
+
+	SVGOMTextElement createText(Text text) {
+		SVGOMTextElement elem = (SVGOMTextElement) createElement(SVG_TEXT_TAG);
+		elem.appendChild(text);
+		return elem;
+	}
+
+	public void updateSVGDocument(final Runnable thread) {
+		if (updateManager == null && !drawingDiagram)
+			thread.run();
+		else if (!executor.isShutdown())
+			executor.execute(new Runnable() {
+				@Override
+				public void run() {
+					waitForUpdateManager();
+					try {
+						updateManager.getUpdateRunnableQueue().invokeLater(
+								thread);
+					} catch (IllegalStateException e) {
+						logger.error("Update of SVG failed", e);
+					}
+				}
+
+				private void waitForUpdateManager() {
+					try {
+						while (updateManager == null)
+							Thread.sleep(200);
+					} catch (InterruptedException e) {
+					}
+				}
+			});
+//		if (updateManager == null)
+//			thread.run();
+//		else
+//			updateManager.getUpdateRunnableQueue().invokeLater(thread);
+	}
+
+	public boolean isAnimatable() {
+		return animationSpeed > 0 && updateManager != null && !drawingDiagram;
+	}
+
+	/**
+	 * Returns the animation speed in milliseconds.
+	 *
+	 * @return the animation speed in milliseconds
+	 */
+	public int getAnimationSpeed() {
+		return animationSpeed;
+	}
+
+	/**
+	 * Sets the animation speed in milliseconds. A value of 0 turns off animation.
+	 *
+	 * @param animationSpeed the animation speed in milliseconds
+	 */
+	public void setAnimationSpeed(int animationSpeed) {
+		this.animationSpeed = animationSpeed;
+	}
+
+	@Override
+	public void shutdown() {
+		super.shutdown();
+		executor.execute(new Runnable() {
+			@Override
+			public void run() {
+				getSVGCanvas().stopProcessing();
+				executor.shutdown();
+			}
+		});
+	}
+
+}
+
+class EdgeLine {
+	private static final float arrowLength = 10f;
+	private static final float arrowWidth = 3f;
+
+	private Element line;
+	private Element pointer;
+	private SVGGraphController graphController;
+
+	private EdgeLine(SVGGraphController graphController) {
+		this.graphController = graphController;
+	}
+
+	public static EdgeLine createAndAdd(SVGDocument svgDocument, SVGGraphController graphController) {
+		EdgeLine edgeLine = new EdgeLine(graphController);
+		edgeLine.line = svgDocument.createElementNS(svgNS, SVG_LINE_TAG);
+		edgeLine.line.setAttribute(SVG_STYLE_ATTRIBUTE,
+				"fill:none;stroke:black");
+		edgeLine.line.setAttribute("pointer-events", "none");
+		edgeLine.line.setAttribute("visibility", "hidden");
+		edgeLine.line.setAttribute(SVG_X1_ATTRIBUTE, "0");
+		edgeLine.line.setAttribute(SVG_Y1_ATTRIBUTE, "0");
+		edgeLine.line.setAttribute(SVG_X2_ATTRIBUTE, "0");
+		edgeLine.line.setAttribute(SVG_Y2_ATTRIBUTE, "0");
+
+		edgeLine.pointer = svgDocument.createElementNS(svgNS, SVG_POLYGON_TAG);
+		edgeLine.pointer.setAttribute(SVG_STYLE_ATTRIBUTE,
+				"fill:black;stroke:black");
+		edgeLine.pointer.setAttribute(SVG_POINTS_ATTRIBUTE, "0,0 "
+				+ -arrowLength + "," + arrowWidth + " " + -arrowLength + ","
+				+ -arrowWidth + " 0,0");
+		edgeLine.pointer.setAttribute("pointer-events", "none");
+		edgeLine.pointer.setAttribute("visibility", "hidden");
+
+		Element svgRoot = svgDocument.getDocumentElement();
+		svgRoot.insertBefore(edgeLine.line, null);
+		svgRoot.insertBefore(edgeLine.pointer, null);
+
+		return edgeLine;
+	}
+
+	public void setSourcePoint(final Point point) {
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				line.setAttribute(SVG_X1_ATTRIBUTE,
+						String.valueOf(point.getX()));
+				line.setAttribute(SVG_Y1_ATTRIBUTE,
+						String.valueOf(point.getY()));
+
+				float x = parseFloat(line.getAttribute(SVG_X2_ATTRIBUTE));
+				float y = parseFloat(line.getAttribute(SVG_Y2_ATTRIBUTE));
+				double angle = calculateAngle(line);
+
+				pointer.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate(" + x
+						+ " " + y + ") rotate(" + angle + " 0 0) ");
+			}
+		});
+	}
+
+	public void setTargetPoint(final Point point) {
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				line.setAttribute(SVG_X2_ATTRIBUTE,
+						String.valueOf(point.getX()));
+				line.setAttribute(SVG_Y2_ATTRIBUTE,
+						String.valueOf(point.getY()));
+
+				double angle = calculateAngle(line);
+				pointer.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+						+ point.x + " " + point.y + ") rotate(" + angle
+						+ " 0 0) ");
+			}
+		});
+	}
+
+	public void setColour(final Color colour) {
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				String hexColour = getHexValue(colour);
+				line.setAttribute(SVG_STYLE_ATTRIBUTE, "fill:none;stroke:"
+						+ hexColour + ";");
+				pointer.setAttribute(SVG_STYLE_ATTRIBUTE, "fill:" + hexColour
+						+ ";stroke:" + hexColour + ";");
+			}
+		});
+	}
+
+	public void setVisible(final boolean visible) {
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				if (visible) {
+					line.setAttribute("visibility", "visible");
+					pointer.setAttribute("visibility", "visible");
+				} else {
+					line.setAttribute("visibility", "hidden");
+					pointer.setAttribute("visibility", "hidden");
+				}
+			}
+		});
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java
new file mode 100644
index 0000000..7884d62
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphEdge.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.SELECTED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.adjustPathLength;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.calculateAngle;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getHexValue;
+import static org.apache.batik.util.CSSConstants.CSS_BLACK_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_DISPLAY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_INLINE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.SMILConstants.SMIL_ADDITIVE_ATTRIBUTE;
+import static org.apache.batik.util.SMILConstants.SMIL_SUM_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TRANSFORM_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_CLICK_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_CX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_CY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_D_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_MOUSEDOWN_EVENT_TYPE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_POINTS_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_RX_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_RY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_TRANSFORM_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_ZERO_VALUE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_ROTATE;
+import static org.apache.batik.util.SVGConstants.TRANSFORM_TRANSLATE;
+
+import java.awt.Color;
+import java.awt.Point;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphEdge;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseClickEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseDownEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOutEventListener;
+import net.sf.taverna.t2.workbench.models.graph.svg.event.SVGMouseOverEventListener;
+
+import org.apache.batik.dom.svg.SVGGraphicsElement;
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMEllipseElement;
+import org.apache.batik.dom.svg.SVGOMGElement;
+import org.apache.batik.dom.svg.SVGOMPathElement;
+import org.apache.batik.dom.svg.SVGOMPolygonElement;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.svg.SVGElement;
+
+/**
+ * SVG representation of a graph edge.
+ * 
+ * @author David Withers
+ */
+public class SVGGraphEdge extends GraphEdge {
+	private static final String ARROW_LENGTH = "8.5";
+	private static final String ARROW_WIDTH = "3";
+	private static final String ELLIPSE_RADIUS = "3.5";
+
+	private SVGGraphController graphController;
+	private SVGGraphElementDelegate delegate;
+	private SVGMouseClickEventListener mouseClickAction;
+	private SVGMouseDownEventListener mouseDownAction;
+	@SuppressWarnings("unused")
+	private SVGMouseOverEventListener mouseOverAction;
+	@SuppressWarnings("unused")
+	private SVGMouseOutEventListener mouseOutAction;
+	private SVGOMGElement mainGroup;
+	private SVGOMPathElement path, deleteButton;
+	private SVGOMPolygonElement polygon;
+	private SVGOMEllipseElement ellipse;
+	private SVGGraphicsElement arrowHead;
+	private SVGOMAnimationElement animatePath, animatePosition, animateRotation;
+
+	public SVGGraphEdge(SVGGraphController graphController) {
+		super(graphController);
+		this.graphController = graphController;
+
+		mouseClickAction = new SVGMouseClickEventListener(this);
+		mouseDownAction = new SVGMouseDownEventListener(this);
+		mouseOverAction = new SVGMouseOverEventListener(this);
+		mouseOutAction = new SVGMouseOutEventListener(this);
+
+		mainGroup = graphController.createGElem();
+		mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, CSS_BLACK_VALUE);
+		mainGroup.setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, CSS_NONE_VALUE);
+		mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+
+		path = graphController.createPath();
+		path.setAttribute(SVG_FILL_ATTRIBUTE, SVG_NONE_VALUE);
+		EventTarget t = (EventTarget) path;
+		t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+		// t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+		// t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+		mainGroup.appendChild(path);
+
+		polygon = graphController.createPolygon();
+		polygon.setAttribute(SVG_POINTS_ATTRIBUTE, ARROW_LENGTH + ", 0"
+				+ " 0, -" + ARROW_WIDTH + " 0," + ARROW_WIDTH);
+		t = (EventTarget) polygon;
+		t.addEventListener(SVG_CLICK_EVENT_TYPE, mouseClickAction, false);
+		t.addEventListener(SVG_MOUSEDOWN_EVENT_TYPE, mouseDownAction, false);
+		// t.addEventListener(SVGConstants.SVG_MOUSEOVER_EVENT_TYPE, mouseOverAction, false);
+		// t.addEventListener(SVGConstants.SVG_MOUSEOUT_EVENT_TYPE, mouseOutAction, false);
+
+		ellipse = graphController.createEllipse();
+		ellipse.setAttribute(SVG_CX_ATTRIBUTE, ELLIPSE_RADIUS);
+		ellipse.setAttribute(SVG_CY_ATTRIBUTE, SVG_ZERO_VALUE);
+		ellipse.setAttribute(SVG_RX_ATTRIBUTE, ELLIPSE_RADIUS);
+		ellipse.setAttribute(SVG_RY_ATTRIBUTE, ELLIPSE_RADIUS);
+
+		arrowHead = polygon;
+		mainGroup.appendChild(arrowHead);
+
+		deleteButton = graphController.createPath();
+		deleteButton.setAttribute(SVG_STROKE_ATTRIBUTE, "red");
+		deleteButton.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "2");
+		deleteButton.setAttribute(SVG_D_ATTRIBUTE,
+				"M-3.5,-7L3.5,0M-3.5,0L3.5,-7");
+		deleteButton.setAttribute(CSS_DISPLAY_PROPERTY, CSS_NONE_VALUE);
+		mainGroup.appendChild(deleteButton);
+
+		animatePath = createAnimationElement(graphController, SVG_ANIMATE_TAG,
+				SVG_D_ATTRIBUTE, null);
+
+		animatePosition = createAnimationElement(graphController,
+				SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+				TRANSFORM_TRANSLATE);
+
+		animateRotation = createAnimationElement(graphController,
+				SVG_ANIMATE_TRANSFORM_TAG, SVG_TRANSFORM_ATTRIBUTE,
+				TRANSFORM_ROTATE);
+		animateRotation.setAttribute(SMIL_ADDITIVE_ATTRIBUTE, SMIL_SUM_VALUE);
+
+		delegate = new SVGGraphElementDelegate(graphController, this, mainGroup);
+	}
+
+	public SVGElement getSVGElement() {
+		return mainGroup;
+	}
+
+	/**
+	 * Returns the path.
+	 * 
+	 * @return the path
+	 */
+	public SVGOMPathElement getPathElement() {
+		return path;
+	}
+
+	@Override
+	public void setSelected(boolean selected) {
+		super.setSelected(selected);
+		final String color = selected ? SELECTED_COLOUR
+				: getHexValue(getColor());
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, color);
+				mainGroup.setAttribute(SVG_FILL_ATTRIBUTE, color);
+			}
+		});
+	}
+
+	@Override
+	public void setActive(final boolean active) {
+		super.setActive(active);
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				if (active) {
+					path.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "2");
+					deleteButton.setAttribute(CSS_DISPLAY_PROPERTY,
+							CSS_INLINE_VALUE);
+				} else {
+					path.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE, "1");
+					deleteButton.setAttribute(CSS_DISPLAY_PROPERTY,
+							CSS_NONE_VALUE);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void setArrowHeadStyle(final ArrowStyle arrowHeadStyle) {
+		super.setArrowHeadStyle(arrowHeadStyle);
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				if (ArrowStyle.NONE.equals(arrowHeadStyle))
+					mainGroup.removeChild(arrowHead);
+				else if (ArrowStyle.NORMAL.equals(arrowHeadStyle)) {
+					mainGroup.removeChild(arrowHead);
+					arrowHead = polygon;
+					mainGroup.appendChild(arrowHead);
+				} else if (ArrowStyle.DOT.equals(arrowHeadStyle)) {
+					mainGroup.removeChild(arrowHead);
+					arrowHead = ellipse;
+					mainGroup.appendChild(arrowHead);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void setPath(final List<Point> pointList) {
+		if (pointList == null)
+			return;
+
+		final List<Point> oldPointList = getPath();
+		super.setPath(pointList);
+		graphController.updateSVGDocument(new Runnable() {
+			@Override
+			public void run() {
+				Point lastPoint = pointList.get(pointList.size() - 1);
+				double angle = calculateAngle(pointList);
+				if (graphController.isAnimatable() && oldPointList != null) {
+					adjustPathLength(oldPointList, pointList.size());
+					Point oldLastPoint = oldPointList.get(oldPointList.size() - 1);
+					double oldAngle = calculateAngle(oldPointList);
+					animate(animatePath, path,
+							graphController.getAnimationSpeed(),
+							SVGUtil.getPath(oldPointList),
+							SVGUtil.getPath(pointList));
+
+					animate(animatePosition, polygon,
+							graphController.getAnimationSpeed(), oldLastPoint.x
+									+ ", " + oldLastPoint.y, lastPoint.x + ", "
+									+ lastPoint.y);
+
+					animate(animateRotation, polygon,
+							graphController.getAnimationSpeed(), oldAngle
+									+ " 0 0", angle + " 0 0");
+
+					ellipse.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+							+ lastPoint.x + " " + lastPoint.y + ") rotate("
+							+ angle + " 0 0) ");
+					deleteButton.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+							"translate(" + lastPoint.x + " " + lastPoint.y
+									+ ")");
+				} else {
+					path.setAttribute(SVG_D_ATTRIBUTE,
+							SVGUtil.getPath(pointList));
+					polygon.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+							+ lastPoint.x + " " + lastPoint.y + ") rotate("
+							+ angle + " 0 0) ");
+					ellipse.setAttribute(SVG_TRANSFORM_ATTRIBUTE, "translate("
+							+ lastPoint.x + " " + lastPoint.y + ") rotate("
+							+ angle + " 0 0) ");
+					deleteButton.setAttribute(SVG_TRANSFORM_ATTRIBUTE,
+							"translate(" + lastPoint.x + " " + lastPoint.y
+									+ ")");
+				}
+			}
+		});
+	}
+
+	@Override
+	public void setColor(final Color color) {
+		delegate.setColor(color);
+		super.setColor(color);
+	}
+
+	@Override
+	public void setFillColor(final Color fillColor) {
+		delegate.setFillColor(fillColor);
+		super.setFillColor(fillColor);
+	}
+
+	@Override
+	public void setVisible(final boolean visible) {
+		delegate.setVisible(visible);
+		super.setVisible(visible);
+	}
+
+	@Override
+	public void setFiltered(final boolean filtered) {
+		delegate.setFiltered(filtered);
+		super.setFiltered(filtered);
+	}
+
+	@Override
+	public void setOpacity(final float opacity) {
+		delegate.setOpacity(opacity);
+		super.setOpacity(opacity);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/8c4b365e/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java
new file mode 100644
index 0000000..cf7f852
--- /dev/null
+++ b/taverna-workbench-graph-model/src/main/java/net/sf/taverna/t2/workbench/models/graph/svg/SVGGraphElementDelegate.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (C) 2009 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.models.graph.svg;
+
+import static net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle.NONE;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGGraphSettings.SELECTED_COLOUR;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.animate;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.createAnimationElement;
+import static net.sf.taverna.t2.workbench.models.graph.svg.SVGUtil.getHexValue;
+import static org.apache.batik.util.CSSConstants.CSS_DISPLAY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_INLINE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_NONE_VALUE;
+import static org.apache.batik.util.CSSConstants.CSS_OPACITY_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_POINTER_EVENTS_PROPERTY;
+import static org.apache.batik.util.CSSConstants.CSS_VISIBLEPAINTED_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_ANIMATE_TAG;
+import static org.apache.batik.util.SVGConstants.SVG_FILL_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_NONE_VALUE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_DASHARRAY_ATTRIBUTE;
+import static org.apache.batik.util.SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE;
+
+import java.awt.Color;
+
+import net.sf.taverna.t2.workbench.models.graph.GraphElement;
+import net.sf.taverna.t2.workbench.models.graph.GraphElement.LineStyle;
+
+import org.apache.batik.dom.svg.SVGOMAnimationElement;
+import org.apache.batik.dom.svg.SVGOMElement;
+
+/**
+ * Delegate for GraphElements. Logically a superclass of SVGGraph, SVGGraphNode
+ * and SVGGraphEdge (if java had multiple inheritance).
+ * 
+ * @author David Withers
+ */
+public class SVGGraphElementDelegate {
+	private SVGGraphController graphController;
+	private GraphElement graphElement;
+	private SVGOMElement mainGroup;
+	private SVGOMAnimationElement animateOpacity;
+
+	public SVGGraphElementDelegate(SVGGraphController graphController,
+			GraphElement graphElement, SVGOMElement mainGroup) {
+		this.graphController = graphController;
+		this.graphElement = graphElement;
+		this.mainGroup = mainGroup;
+
+		animateOpacity = createAnimationElement(graphController,
+				SVG_ANIMATE_TAG, CSS_OPACITY_PROPERTY, null);
+	}
+
+	public void setSelected(final boolean selected) {
+		boolean currentSelected = graphElement.isSelected();
+		if (currentSelected != selected
+				&& !LineStyle.NONE.equals(graphElement.getLineStyle()))
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE,
+							selected ? SELECTED_COLOUR
+									: getHexValue(graphElement.getColor()));
+					mainGroup.setAttribute(SVG_STROKE_WIDTH_ATTRIBUTE,
+							selected ? "2" : "1");
+				}
+			});
+	}
+
+	public void setLineStyle(final LineStyle lineStyle) {
+		LineStyle currentLineStyle = graphElement.getLineStyle();
+		if (!currentLineStyle.equals(lineStyle))
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					String stroke = SVG_NONE_VALUE, dash = SVG_NONE_VALUE;
+					switch (lineStyle) {
+					case DOTTED:
+						stroke = getHexValue(graphElement.getColor());
+						dash = "1,5";
+						break;
+					case SOLID:
+						stroke = getHexValue(graphElement.getColor());
+					default:
+						break;
+					}
+					mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE, stroke);
+					mainGroup
+							.setAttribute(SVG_STROKE_DASHARRAY_ATTRIBUTE, dash);
+				}
+			});
+	}
+
+	public void setColor(final Color color) {
+		Color currentColor = graphElement.getColor();
+		if (currentColor != color && NONE != graphElement.getLineStyle())
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.setAttribute(SVG_STROKE_ATTRIBUTE,
+							getHexValue(color));
+				}
+			});
+	}
+
+	public void setFillColor(final Color fillColor) {
+		Color currentFillColor = graphElement.getFillColor();
+		if (currentFillColor != fillColor)
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.setAttribute(SVG_FILL_ATTRIBUTE,
+							getHexValue(fillColor));
+				}
+			});
+	}
+
+	public void setVisible(final boolean visible) {
+		boolean currentVisible = graphElement.isVisible();
+		if (currentVisible != visible)
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.setAttribute(CSS_DISPLAY_PROPERTY,
+							visible ? CSS_INLINE_VALUE : CSS_NONE_VALUE);
+				}
+			});
+	}
+
+	public void setOpacity(final float opacity) {
+		final float currentOpacity = graphElement.getOpacity();
+		if (currentOpacity != opacity)
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					if (graphController.isAnimatable())
+						animate(animateOpacity, mainGroup,
+								graphController.getAnimationSpeed(),
+								String.valueOf(currentOpacity),
+								String.valueOf(opacity));
+					else
+						mainGroup.setAttribute(CSS_OPACITY_PROPERTY,
+								String.valueOf(opacity));
+				}
+			});
+	}
+
+	public void setFiltered(final boolean filtered) {
+		boolean currentFiltered = graphElement.isFiltered();
+		if (currentFiltered != filtered)
+			graphController.updateSVGDocument(new Runnable() {
+				@Override
+				public void run() {
+					mainGroup.setAttribute(CSS_POINTER_EVENTS_PROPERTY,
+							filtered ? CSS_NONE_VALUE
+									: CSS_VISIBLEPAINTED_VALUE);
+					setOpacity(filtered ? 0.2f : 1f);
+				}
+			});
+	}
+}