-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.elementclass.connection;\r
-\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Shape;\r
-import java.awt.Stroke;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.GeneralPath;\r
-import java.awt.geom.Line2D;\r
-import java.awt.geom.Path2D;\r
-import java.awt.geom.PathIterator;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Iterator;\r
-\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.Topology;\r
-import org.simantics.g2d.diagram.handler.Topology.Connection;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.SceneGraphNodeKey;\r
-import org.simantics.g2d.element.handler.BendsHandler;\r
-import org.simantics.g2d.element.handler.EdgeVisuals;\r
-import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType;\r
-import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
-import org.simantics.g2d.element.handler.Rotate;\r
-import org.simantics.g2d.element.handler.SceneGraph;\r
-import org.simantics.g2d.element.handler.TerminalLayout;\r
-import org.simantics.g2d.elementclass.BranchPoint;\r
-import org.simantics.g2d.utils.PathUtils;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.nodes.EdgeNode;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-\r
-/**\r
- * Generic edge painter\r
- *\r
- * @author J-P Laine\r
- */\r
-public class EdgeSceneGraph implements SceneGraph {\r
-\r
- private static final long serialVersionUID = 2914383071126238996L;\r
-\r
- public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph();\r
-\r
- public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
-\r
- public static final Key KEY_SG_NODE = new SceneGraphNodeKey(EdgeNode.class, "EDGE_SG_NODE");\r
-\r
- @Override\r
- public void init(IElement e, G2DParentNode parent) {\r
- ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.class);\r
- update(e);\r
- }\r
-\r
- @Override\r
- public void cleanup(IElement e) {\r
- ElementUtils.removePossibleNode(e, KEY_SG_NODE);\r
- }\r
-\r
- public void update(final IElement e) {\r
- EdgeNode node = e.getHint(KEY_SG_NODE);\r
- if(node == null) return;\r
-\r
- EdgeVisuals vh = e.getElementClass().getSingleItem(EdgeVisuals.class);\r
- ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin);\r
- ArrowType at2 = vh.getArrowType(e, EdgeEnd.End);\r
- Stroke stroke = vh.getStroke(e);\r
- //StrokeType strokeType = vh.getStrokeType(e);\r
- double as1 = vh.getArrowSize(e, EdgeEnd.Begin);\r
- double as2 = vh.getArrowSize(e, EdgeEnd.End);\r
-\r
- Color c = ElementUtils.getFillColor(e, Color.BLACK);\r
-\r
- // Get terminal shape for clipping the painted edge to its bounds.\r
- IDiagram diagram = ElementUtils.peekDiagram(e);\r
- Shape beginTerminalShape = null;\r
- Shape endTerminalShape = null;\r
- if (diagram != null) {\r
- Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
- if (topology != null) {\r
- Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin);\r
- Connection endConnection = topology.getConnection(e, EdgeEnd.End);\r
- beginTerminalShape = getTerminalShape(beginConnection);\r
- endTerminalShape = getTerminalShape(endConnection);\r
- int beginBranchDegree = getBranchPointDegree(beginConnection, topology);\r
- int endBranchDegree = getBranchPointDegree(endConnection, topology);\r
- if (beginBranchDegree > 0 && beginBranchDegree < 3) {\r
- at1 = ArrowType.None;\r
- }\r
- if (endBranchDegree > 0 && endBranchDegree < 3) {\r
- at2 = ArrowType.None;\r
- }\r
- }\r
- }\r
-\r
- // Read bends\r
- BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
- Path2D line = bh.getPath(e);\r
-\r
- boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None;\r
- //line = clipLineEnds(line, beginTerminalShape, endTerminalShape);\r
-\r
- Point2D first = new Point2D.Double();\r
- Point2D dir1 = new Point2D.Double();\r
- Point2D last = new Point2D.Double();\r
- Point2D dir2 = new Point2D.Double();\r
- PathIterator pi = line.getPathIterator(null);\r
- drawArrows &= PathUtils.getPathArrows(pi, first, dir1, last, dir2);\r
-\r
- if (drawArrows) {\r
- line = trimLineToArrows(line, at1, as1, at2, as2);\r
- }\r
-\r
- EdgeNode.ArrowType pat1 = convert(at1);\r
- EdgeNode.ArrowType pat2 = convert(at2);\r
-\r
- node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, last, as1, as2, pat1, pat2, null, null);\r
- }\r
-\r
- private static EdgeNode.ArrowType convert(ArrowType at) {\r
- switch (at) {\r
- case None: return EdgeNode.ArrowType.None;\r
- case Stroke: return EdgeNode.ArrowType.Stroke;\r
- case Fill: return EdgeNode.ArrowType.Fill;\r
- default:\r
- throw new IllegalArgumentException("unsupported arrow type: " + at);\r
- }\r
- }\r
-\r
- private static final Rectangle2D EMPTY = new Rectangle2D.Double();\r
-\r
- private static Shape getTerminalShape(Connection connection) {\r
- if (connection != null && connection.node != null && connection.terminal != null) {\r
- TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class);\r
- if (layout != null) {\r
- //return layout.getTerminalShape(connection.node, connection.terminal);\r
- Shape shp = layout.getTerminalShape(connection.node, connection.terminal);\r
- Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class);\r
- if (rotate == null)\r
- return shp;\r
-\r
- double theta = rotate.getAngle(connection.node);\r
- return AffineTransform.getRotateInstance(theta).createTransformedShape(shp);\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- private final Collection<Connection> connectionsTemp = new ArrayList<Connection>();\r
- private int getBranchPointDegree(Connection connection, Topology topology) {\r
- if (connection != null && connection.node != null) {\r
- if (connection.node.getElementClass().containsClass(BranchPoint.class)) {\r
- connectionsTemp.clear();\r
- topology.getConnections(connection.node, connection.terminal, connectionsTemp);\r
- int degree = connectionsTemp.size();\r
- connectionsTemp.clear();\r
- return degree;\r
- }\r
- }\r
- return -1;\r
- }\r
-\r
- private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) {\r
- if (beginTerminalShape == null && endTerminalShape == null)\r
- return line;\r
-\r
- Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY;\r
- Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY;\r
- // If the terminal shape doesn't contain its own coordinate system\r
- // origin, just ignore the terminal shape.\r
- if (bb != EMPTY && !bb.contains(0, 0))\r
- bb = EMPTY;\r
- if (eb != EMPTY && !eb.contains(0, 0))\r
- eb = EMPTY;\r
- if (bb.isEmpty() && eb.isEmpty())\r
- return line;\r
-\r
- Path2D result = new Path2D.Double();\r
-\r
- PathIterator pi = line.getPathIterator(null);\r
- Iterator<double[]> it = PathUtils.toLineIterator(pi);\r
- boolean first = true;\r
- while (it.hasNext()) {\r
- double[] seg = it.next();\r
- int degree = PathUtils.getLineDegree(seg);\r
- //System.out.println("SEG: " + Arrays.toString(seg));\r
-\r
- if (first) {\r
- first = false;\r
- Point2D start = PathUtils.getLinePos(seg, 0);\r
- Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start);\r
- if (sp != null) {\r
- result.moveTo(sp.getX(), sp.getY());\r
- } else {\r
- result.moveTo(start.getX(), start.getY());\r
- }\r
- }\r
- if (!it.hasNext()) {\r
- // this is the last segment\r
- Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1));\r
- //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1));\r
- if (ep != null) {\r
- seg[degree * 2] = ep.getX();\r
- seg[degree * 2 + 1] = ep.getY();\r
- }\r
- }\r
-\r
- if (degree == 1) {\r
- result.lineTo(seg[2], seg[3]);\r
- } else if (degree == 2) {\r
- result.quadTo(seg[2], seg[3], seg[4], seg[5]);\r
- } else if (degree == 3) {\r
- result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);\r
- } else {\r
- throw new UnsupportedOperationException("invalid path segment degree: " + degree);\r
- }\r
- }\r
-\r
- result.setWindingRule(line.getWindingRule());\r
- return result;\r
- }\r
-\r
- private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) {\r
- Path2D result = new Path2D.Double();\r
- PathIterator pi = line.getPathIterator(null);\r
- Iterator<double[]> it = PathUtils.toLineIterator(pi);\r
- boolean first = true;\r
-\r
- while (it.hasNext()) {\r
- double[] seg = it.next();\r
- int degree = PathUtils.getLineDegree(seg);\r
-\r
- if (first) {\r
- first = false;\r
-\r
- if (beginArrowType == ArrowType.Fill) {\r
- Point2D t = PathUtils.getLineTangent(seg, 0);\r
- double len = Math.sqrt(lensq(t, null));\r
- if (len > beginArrowSize) {\r
- double scale = beginArrowSize / len;\r
- seg[0] += t.getX() * scale;\r
- seg[1] += t.getY() * scale;\r
- } else {\r
- // Remove the first segment completely if the segment is too\r
- // small to be noticed from under the arrow.\r
- result.moveTo(seg[degree * 2], seg[degree * 2 + 1]);\r
- continue;\r
- }\r
- }\r
-\r
- result.moveTo(seg[0], seg[1]);\r
- }\r
- if (!it.hasNext()) {\r
- // this is the last segment\r
- if (endArrowType == ArrowType.Fill) {\r
- Point2D t = PathUtils.getLineTangent(seg, 1);\r
- double len = Math.sqrt(lensq(t, null));\r
- if (len > endArrowSize) {\r
- double scale = endArrowSize / len;\r
- seg[degree * 2] -= t.getX() * scale;\r
- seg[degree * 2 + 1] -= t.getY() * scale;\r
- }\r
- }\r
- }\r
-\r
- if (degree == 1) {\r
- result.lineTo(seg[2], seg[3]);\r
- } else if (degree == 2) {\r
- result.quadTo(seg[2], seg[3], seg[4], seg[5]);\r
- } else if (degree == 3) {\r
- result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);\r
- } else {\r
- throw new UnsupportedOperationException("invalid path segment degree: " + degree);\r
- }\r
- }\r
-\r
- result.setWindingRule(line.getWindingRule());\r
- return result;\r
- }\r
-\r
- private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) {\r
- if (bounds.isEmpty())\r
- return p2;\r
-\r
- Line2D line = new Line2D.Double(p1, p2);\r
- Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY());\r
- Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY());\r
- Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX());\r
- Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX());\r
-\r
- int i = 0;\r
- Point2D[] intersections = { null, null, null, null };\r
- if (vi1 != null)\r
- intersections[i++] = vi1;\r
- if (vi2 != null)\r
- intersections[i++] = vi2;\r
- if (hi1 != null)\r
- intersections[i++] = hi1;\r
- if (hi2 != null)\r
- intersections[i++] = hi2;\r
-\r
- //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections));\r
-\r
- if (i == 0)\r
- return p2;\r
- if (i == 1)\r
- return intersections[0];\r
-\r
- // Find the intersection i for which applies\r
- // lensq(p1, p2) >= lensq(p1, i) &\r
- // for all intersections j != i: lensq(p1, i) > lensq(p1, j)\r
- double len = lensq(p1, p2);\r
- //System.out.println("original line lensq: " + len);\r
- Point2D nearestIntersection = null;\r
- double nearestLen = -1;\r
- for (int j = 0; j < i; ++j) {\r
- double l = lensq(p1, intersections[j]);\r
- //System.out.println("intersected line lensq: " + l);\r
- if (l <= len && l > nearestLen) {\r
- nearestIntersection = intersections[j];\r
- nearestLen = l;\r
- //System.out.println("nearest");\r
- }\r
- }\r
- return nearestIntersection;\r
- }\r
-\r
- private static double lensq(Point2D p1, Point2D p2) {\r
- double dx = p1.getX();\r
- double dy = p1.getY();\r
- if (p2 != null) {\r
- dx = p2.getX() - dx;\r
- dy = p2.getY() - dy;\r
- }\r
- return dx*dx + dy*dy;\r
- }\r
-\r
- private static Point2D intersectWithHorizontalLine(Line2D l, double y) {\r
- double dx = l.getX2() - l.getX1();\r
- double dy = l.getY2() - l.getY1();\r
-\r
- if (Math.abs(dy) < 1e-5) {\r
- // input line as horizontal, no intersection.\r
- return null;\r
- }\r
- double a = dx / dy;\r
- return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y);\r
- }\r
-\r
- private static Point2D intersectWithVerticalLine(Line2D l, double x) {\r
- double dx = l.getX2() - l.getX1();\r
- double dy = l.getY2() - l.getY1();\r
-\r
- if (Math.abs(dx) < 1e-5) {\r
- // input line as vertical, no intersection.\r
- return null;\r
- }\r
- double a = dy / dx;\r
- return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1());\r
- }\r
-\r
-\r
- public final static Path2D NORMAL_ARROW;\r
- public final static Path2D FILLED_ARROW;\r
-\r
- static {\r
- FILLED_ARROW = new Path2D.Double();\r
- FILLED_ARROW.moveTo(-0.5, 1);\r
- FILLED_ARROW.lineTo( 0, 0);\r
- FILLED_ARROW.lineTo( 0.5, 1);\r
- FILLED_ARROW.closePath();\r
-\r
- NORMAL_ARROW = new Path2D.Double();\r
- NORMAL_ARROW.moveTo(-0.5, 1);\r
- NORMAL_ARROW.lineTo( 0, 0);\r
- NORMAL_ARROW.lineTo( 0.5, 1);\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g2d.elementclass.connection;
+
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.Topology;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.SceneGraphNodeKey;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.EdgeVisuals;
+import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.Rotate;
+import org.simantics.g2d.element.handler.SceneGraph;
+import org.simantics.g2d.element.handler.TerminalLayout;
+import org.simantics.g2d.elementclass.BranchPoint;
+import org.simantics.g2d.utils.PathUtils;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.nodes.EdgeNode;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+
+/**
+ * Generic edge painter
+ *
+ * @author J-P Laine
+ */
+public class EdgeSceneGraph implements SceneGraph {
+
+ private static final long serialVersionUID = 2914383071126238996L;
+
+ public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph();
+
+ public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
+
+ public static final Key KEY_SG_NODE = new SceneGraphNodeKey(EdgeNode.class, "EDGE_SG_NODE");
+
+ @Override
+ public void init(IElement e, G2DParentNode parent) {
+ ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.class);
+ update(e);
+ }
+
+ @Override
+ public void cleanup(IElement e) {
+ ElementUtils.removePossibleNode(e, KEY_SG_NODE);
+ }
+
+ public void update(final IElement e) {
+ EdgeNode node = e.getHint(KEY_SG_NODE);
+ if(node == null) return;
+
+ EdgeVisuals vh = e.getElementClass().getSingleItem(EdgeVisuals.class);
+ ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin);
+ ArrowType at2 = vh.getArrowType(e, EdgeEnd.End);
+ Stroke stroke = vh.getStroke(e);
+ //StrokeType strokeType = vh.getStrokeType(e);
+ double as1 = vh.getArrowSize(e, EdgeEnd.Begin);
+ double as2 = vh.getArrowSize(e, EdgeEnd.End);
+
+ Color c = ElementUtils.getFillColor(e, Color.BLACK);
+
+ // Get terminal shape for clipping the painted edge to its bounds.
+ IDiagram diagram = ElementUtils.peekDiagram(e);
+ Shape beginTerminalShape = null;
+ Shape endTerminalShape = null;
+ if (diagram != null) {
+ Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
+ if (topology != null) {
+ Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin);
+ Connection endConnection = topology.getConnection(e, EdgeEnd.End);
+ beginTerminalShape = getTerminalShape(beginConnection);
+ endTerminalShape = getTerminalShape(endConnection);
+ int beginBranchDegree = getBranchPointDegree(beginConnection, topology);
+ int endBranchDegree = getBranchPointDegree(endConnection, topology);
+ if (beginBranchDegree > 0 && beginBranchDegree < 3) {
+ at1 = ArrowType.None;
+ }
+ if (endBranchDegree > 0 && endBranchDegree < 3) {
+ at2 = ArrowType.None;
+ }
+ }
+ }
+
+ // Read bends
+ BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
+ Path2D line = bh.getPath(e);
+
+ boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None;
+ //line = clipLineEnds(line, beginTerminalShape, endTerminalShape);
+
+ Point2D first = new Point2D.Double();
+ Point2D dir1 = new Point2D.Double();
+ Point2D last = new Point2D.Double();
+ Point2D dir2 = new Point2D.Double();
+ PathIterator pi = line.getPathIterator(null);
+ drawArrows &= PathUtils.getPathArrows(pi, first, dir1, last, dir2);
+
+ if (drawArrows) {
+ line = trimLineToArrows(line, at1, as1, at2, as2);
+ }
+
+ EdgeNode.ArrowType pat1 = convert(at1);
+ EdgeNode.ArrowType pat2 = convert(at2);
+
+ node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, last, as1, as2, pat1, pat2, null, null);
+ }
+
+ private static EdgeNode.ArrowType convert(ArrowType at) {
+ switch (at) {
+ case None: return EdgeNode.ArrowType.None;
+ case Stroke: return EdgeNode.ArrowType.Stroke;
+ case Fill: return EdgeNode.ArrowType.Fill;
+ default:
+ throw new IllegalArgumentException("unsupported arrow type: " + at);
+ }
+ }
+
+ private static final Rectangle2D EMPTY = new Rectangle2D.Double();
+
+ private static Shape getTerminalShape(Connection connection) {
+ if (connection != null && connection.node != null && connection.terminal != null) {
+ TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class);
+ if (layout != null) {
+ //return layout.getTerminalShape(connection.node, connection.terminal);
+ Shape shp = layout.getTerminalShape(connection.node, connection.terminal);
+ Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class);
+ if (rotate == null)
+ return shp;
+
+ double theta = rotate.getAngle(connection.node);
+ return AffineTransform.getRotateInstance(theta).createTransformedShape(shp);
+ }
+ }
+ return null;
+ }
+
+ private final Collection<Connection> connectionsTemp = new ArrayList<Connection>();
+ private int getBranchPointDegree(Connection connection, Topology topology) {
+ if (connection != null && connection.node != null) {
+ if (connection.node.getElementClass().containsClass(BranchPoint.class)) {
+ connectionsTemp.clear();
+ topology.getConnections(connection.node, connection.terminal, connectionsTemp);
+ int degree = connectionsTemp.size();
+ connectionsTemp.clear();
+ return degree;
+ }
+ }
+ return -1;
+ }
+
+ private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) {
+ if (beginTerminalShape == null && endTerminalShape == null)
+ return line;
+
+ Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY;
+ Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY;
+ // If the terminal shape doesn't contain its own coordinate system
+ // origin, just ignore the terminal shape.
+ if (bb != EMPTY && !bb.contains(0, 0))
+ bb = EMPTY;
+ if (eb != EMPTY && !eb.contains(0, 0))
+ eb = EMPTY;
+ if (bb.isEmpty() && eb.isEmpty())
+ return line;
+
+ Path2D result = new Path2D.Double();
+
+ PathIterator pi = line.getPathIterator(null);
+ Iterator<double[]> it = PathUtils.toLineIterator(pi);
+ boolean first = true;
+ while (it.hasNext()) {
+ double[] seg = it.next();
+ int degree = PathUtils.getLineDegree(seg);
+ //System.out.println("SEG: " + Arrays.toString(seg));
+
+ if (first) {
+ first = false;
+ Point2D start = PathUtils.getLinePos(seg, 0);
+ Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start);
+ if (sp != null) {
+ result.moveTo(sp.getX(), sp.getY());
+ } else {
+ result.moveTo(start.getX(), start.getY());
+ }
+ }
+ if (!it.hasNext()) {
+ // this is the last segment
+ Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1));
+ //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1));
+ if (ep != null) {
+ seg[degree * 2] = ep.getX();
+ seg[degree * 2 + 1] = ep.getY();
+ }
+ }
+
+ if (degree == 1) {
+ result.lineTo(seg[2], seg[3]);
+ } else if (degree == 2) {
+ result.quadTo(seg[2], seg[3], seg[4], seg[5]);
+ } else if (degree == 3) {
+ result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);
+ } else {
+ throw new UnsupportedOperationException("invalid path segment degree: " + degree);
+ }
+ }
+
+ result.setWindingRule(line.getWindingRule());
+ return result;
+ }
+
+ private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) {
+ Path2D result = new Path2D.Double();
+ PathIterator pi = line.getPathIterator(null);
+ Iterator<double[]> it = PathUtils.toLineIterator(pi);
+ boolean first = true;
+
+ while (it.hasNext()) {
+ double[] seg = it.next();
+ int degree = PathUtils.getLineDegree(seg);
+
+ if (first) {
+ first = false;
+
+ if (beginArrowType == ArrowType.Fill) {
+ Point2D t = PathUtils.getLineTangent(seg, 0);
+ double len = Math.sqrt(lensq(t, null));
+ if (len > beginArrowSize) {
+ double scale = beginArrowSize / len;
+ seg[0] += t.getX() * scale;
+ seg[1] += t.getY() * scale;
+ } else {
+ // Remove the first segment completely if the segment is too
+ // small to be noticed from under the arrow.
+ result.moveTo(seg[degree * 2], seg[degree * 2 + 1]);
+ continue;
+ }
+ }
+
+ result.moveTo(seg[0], seg[1]);
+ }
+ if (!it.hasNext()) {
+ // this is the last segment
+ if (endArrowType == ArrowType.Fill) {
+ Point2D t = PathUtils.getLineTangent(seg, 1);
+ double len = Math.sqrt(lensq(t, null));
+ if (len > endArrowSize) {
+ double scale = endArrowSize / len;
+ seg[degree * 2] -= t.getX() * scale;
+ seg[degree * 2 + 1] -= t.getY() * scale;
+ }
+ }
+ }
+
+ if (degree == 1) {
+ result.lineTo(seg[2], seg[3]);
+ } else if (degree == 2) {
+ result.quadTo(seg[2], seg[3], seg[4], seg[5]);
+ } else if (degree == 3) {
+ result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);
+ } else {
+ throw new UnsupportedOperationException("invalid path segment degree: " + degree);
+ }
+ }
+
+ result.setWindingRule(line.getWindingRule());
+ return result;
+ }
+
+ private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) {
+ if (bounds.isEmpty())
+ return p2;
+
+ Line2D line = new Line2D.Double(p1, p2);
+ Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY());
+ Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY());
+ Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX());
+ Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX());
+
+ int i = 0;
+ Point2D[] intersections = { null, null, null, null };
+ if (vi1 != null)
+ intersections[i++] = vi1;
+ if (vi2 != null)
+ intersections[i++] = vi2;
+ if (hi1 != null)
+ intersections[i++] = hi1;
+ if (hi2 != null)
+ intersections[i++] = hi2;
+
+ //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections));
+
+ if (i == 0)
+ return p2;
+ if (i == 1)
+ return intersections[0];
+
+ // Find the intersection i for which applies
+ // lensq(p1, p2) >= lensq(p1, i) &
+ // for all intersections j != i: lensq(p1, i) > lensq(p1, j)
+ double len = lensq(p1, p2);
+ //System.out.println("original line lensq: " + len);
+ Point2D nearestIntersection = null;
+ double nearestLen = -1;
+ for (int j = 0; j < i; ++j) {
+ double l = lensq(p1, intersections[j]);
+ //System.out.println("intersected line lensq: " + l);
+ if (l <= len && l > nearestLen) {
+ nearestIntersection = intersections[j];
+ nearestLen = l;
+ //System.out.println("nearest");
+ }
+ }
+ return nearestIntersection;
+ }
+
+ private static double lensq(Point2D p1, Point2D p2) {
+ double dx = p1.getX();
+ double dy = p1.getY();
+ if (p2 != null) {
+ dx = p2.getX() - dx;
+ dy = p2.getY() - dy;
+ }
+ return dx*dx + dy*dy;
+ }
+
+ private static Point2D intersectWithHorizontalLine(Line2D l, double y) {
+ double dx = l.getX2() - l.getX1();
+ double dy = l.getY2() - l.getY1();
+
+ if (Math.abs(dy) < 1e-5) {
+ // input line as horizontal, no intersection.
+ return null;
+ }
+ double a = dx / dy;
+ return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y);
+ }
+
+ private static Point2D intersectWithVerticalLine(Line2D l, double x) {
+ double dx = l.getX2() - l.getX1();
+ double dy = l.getY2() - l.getY1();
+
+ if (Math.abs(dx) < 1e-5) {
+ // input line as vertical, no intersection.
+ return null;
+ }
+ double a = dy / dx;
+ return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1());
+ }
+
+
+ public final static Path2D NORMAL_ARROW;
+ public final static Path2D FILLED_ARROW;
+
+ static {
+ FILLED_ARROW = new Path2D.Double();
+ FILLED_ARROW.moveTo(-0.5, 1);
+ FILLED_ARROW.lineTo( 0, 0);
+ FILLED_ARROW.lineTo( 0.5, 1);
+ FILLED_ARROW.closePath();
+
+ NORMAL_ARROW = new Path2D.Double();
+ NORMAL_ARROW.moveTo(-0.5, 1);
+ NORMAL_ARROW.lineTo( 0, 0);
+ NORMAL_ARROW.lineTo( 0.5, 1);
+ }
+
+}