1
28 package demo.view.advanced;
29
30 import java.awt.Graphics2D;
31 import java.awt.EventQueue;
32 import java.awt.geom.GeneralPath;
33 import java.awt.geom.PathIterator;
34 import java.awt.geom.Point2D;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.WeakHashMap;
40
41 import javax.swing.ActionMap;
42 import javax.swing.InputMap;
43 import javax.swing.JComponent;
44
45 import org.w3c.dom.Element;
46
47 import demo.view.DemoBase;
48 import demo.view.DemoDefaults;
49
50 import y.base.DataMap;
51 import y.base.Edge;
52 import y.base.EdgeCursor;
53 import y.base.EdgeList;
54 import y.base.Graph;
55 import y.base.GraphEvent;
56 import y.base.GraphListener;
57 import y.base.Node;
58 import y.base.NodeCursor;
59 import y.base.NodeList;
60 import y.base.YList;
61 import y.geom.Geom;
62 import y.geom.YDimension;
63 import y.geom.YPoint;
64 import y.geom.YVector;
65 import y.io.GraphMLIOHandler;
66 import y.io.graphml.KeyScope;
67 import y.io.graphml.KeyType;
68 import y.io.graphml.input.GraphMLParseContext;
69 import y.io.graphml.input.GraphMLParseException;
70 import y.io.graphml.input.NameBasedDeserializer;
71 import y.io.graphml.input.ParseEventListenerAdapter;
72 import y.io.graphml.output.AbstractOutputHandler;
73 import y.io.graphml.output.GraphElementIdProvider;
74 import y.io.graphml.output.GraphMLWriteContext;
75 import y.io.graphml.output.GraphMLWriteException;
76 import y.io.graphml.output.XmlWriter;
77 import y.util.DataAcceptorAdapter;
78 import y.util.GraphCopier;
79 import y.util.Maps;
80 import y.util.Tuple;
81 import y.view.Bend;
82 import y.view.BendCursor;
83 import y.view.BendList;
84 import y.view.CreateEdgeMode;
85 import y.view.DefaultGraph2DRenderer;
86 import y.view.EdgeRealizer;
87 import y.view.EditMode;
88 import y.view.GenericNodeRealizer;
89 import y.view.Graph2D;
90 import y.view.Graph2DClipboard;
91 import y.view.Graph2DTraversal;
92 import y.view.Graph2DViewActions;
93 import y.view.HitInfo;
94 import y.view.MovePortMode;
95 import y.view.MoveSelectionMode;
96 import y.view.NodeRealizer;
97 import y.view.Port;
98 import y.view.ShapeNodePainter;
99
100
111 public class EdgeConnectorDemo extends DemoBase {
112
113
116 static {
117 Map configurationMap = GenericNodeRealizer.getFactory().createDefaultConfigurationMap();
118 ShapeNodePainter painter = new ShapeNodePainter();
119 painter.setShapeType(ShapeNodePainter.ELLIPSE);
120 configurationMap.put(y.view.GenericNodeRealizer.Painter.class, painter);
121
122 GenericNodeRealizer.GenericSizeConstraintProvider scp = new GenericNodeRealizer.GenericSizeConstraintProvider() {
124 public YDimension getMaximumSize(NodeRealizer context) {
125 return new YDimension(5,5);
126 }
127
128 public YDimension getMinimumSize(NodeRealizer context) {
129 return new YDimension(5,5);
130 }
131 };
132 configurationMap.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, scp);
133 GenericNodeRealizer.getFactory().addConfiguration("EdgeConnector", configurationMap);
134 }
135
136 protected void initialize() {
137 super.initialize();
138 view.setAntialiasedPainting(true);
139 EdgeConnectorGraph2DRenderer r = new EdgeConnectorGraph2DRenderer();
140 r.setDrawEdgesFirst(true);
141 view.setGraph2DRenderer(r);
142 view.getGraph2D().addGraphListener(new EdgeConnectorListener());
143 loadGraph("resource/EdgeConnectorDemo.graphml");
144 }
145
146 protected void registerViewModes() {
147 EditMode editMode = new EdgeConnectorEditMode();
148 editMode.setCreateEdgeMode(new CreateEdgeConnectorMode());
149 editMode.setMoveSelectionMode(new EdgeConnectorMoveSelectionMode());
150 editMode.setMovePortMode(new EdgeConnectorMovePortMode());
151 view.addViewMode(editMode);
152 }
153
154
158 static class EdgeConnectorGraph2DRenderer extends DefaultGraph2DRenderer {
159 public void paint(final Graphics2D gfx, final Graph2D graph) {
160 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
161 Node n = nc.node();
162 if(EdgeConnectorManager.isEdgeConnector(n)) {
163 updateEdgeConnectorLocation(n);
164 }
165 }
166 super.paint(gfx, graph);
167 }
168
169 public void updateEdgeConnectorLocation(Node node) {
170 if(node != null) {
171 Edge edge = EdgeConnectorManager.getEdgeConnection(node);
172 if(edge != null) {
173 Graph2D graph = (Graph2D) node.getGraph();
174 double ratio = EdgeConnectorManager.getEdgeConnectionRatio(node);
175 Point2D point = PointPathProjector.getPointForGlobalRatio(graph.getRealizer(edge), ratio);
176 NodeRealizer nr = graph.getRealizer(node);
177 nr.setCenter(point.getX(), point.getY());
178 }
179 }
180 }
181 }
182
183
186 protected GraphMLIOHandler createGraphMLIOHandler() {
187 GraphMLIOHandler ioHandler = super.createGraphMLIOHandler();
188
189 ioHandler.getGraphMLHandler().addOutputHandlerProvider(new AbstractOutputHandler("edgeConnectingData", KeyScope.NODE, KeyType.COMPLEX) {
190 protected void writeValueCore(GraphMLWriteContext context, Object data)
191 throws GraphMLWriteException {
192 if(data != null) {
193 Tuple tuple = (Tuple) data;
194 Edge edge = (Edge) tuple.o1;
195 double ratio = ((Double)tuple.o2).doubleValue();
196 XmlWriter writer = context.getWriter();
197 GraphElementIdProvider idProvider = (GraphElementIdProvider) context.lookup(GraphElementIdProvider.class);
198 String edgeId = idProvider.getEdgeId(edge, context);
199 writer.writeStartElement("connectorData", "demo");
200 writer.writeAttribute("edgeId", edgeId);
201 writer.writeAttribute("ratio", ratio);
202 writer.writeEndElement();
203 }
204 }
205
206 protected Object getValue(GraphMLWriteContext context, Object key)
207 throws GraphMLWriteException {
208 return EdgeConnectorManager.map.get(key);
209 }
210 });
211
212 ioHandler.getGraphMLHandler().addOutputHandlerProvider(new AbstractOutputHandler("edgeId", KeyScope.EDGE, KeyType.STRING) {
213 protected void writeValueCore(GraphMLWriteContext context, Object data)
214 throws GraphMLWriteException {
215 if(data != null) {
216 XmlWriter writer = context.getWriter();
217 writer.writeText(data.toString());
218 }
219 }
220
221 protected Object getValue(GraphMLWriteContext context, Object key)
222 throws GraphMLWriteException {
223 GraphElementIdProvider idProvider = (GraphElementIdProvider) context.lookup(GraphElementIdProvider.class);
224 return idProvider.getEdgeId((Edge) key, context);
225 }
226 });
227
228 final DataMap edgeIdMap = Maps.createHashedDataMap();
229 ioHandler.getGraphMLHandler().addInputDataAcceptor("edgeId",
230 new DataAcceptorAdapter() {
231 public void set(Object dataHolder, Object value) {
232 edgeIdMap.set(value, dataHolder);
233 }
234 },
235 KeyScope.EDGE, KeyType.STRING);
236
237
238 final DataMap tempConnectorMap = Maps.createHashedDataMap();
239
240 ioHandler.getGraphMLHandler().addInputDataAcceptor("edgeConnectingData", tempConnectorMap, KeyScope.NODE, new NameBasedDeserializer() {
241 public Object deserializeNode(org.w3c.dom.Node xmlNode,
242 GraphMLParseContext context) throws GraphMLParseException {
243 Element xmlElem = (Element) xmlNode;
244 String edgeId = xmlElem.getAttribute("edgeId");
245 String doubleStr = xmlElem.getAttribute("ratio");
246 return new Tuple(edgeId, doubleStr);
247 }
248
249 public String getNodeName(GraphMLParseContext context) {
250 return "connectorData";
251 }
252
253 public String getNamespaceURI(GraphMLParseContext context) {
254 return "demo";
255 }
256 });
257
258 ioHandler.getGraphMLHandler().addParseEventListener(new ParseEventListenerAdapter() {
259 public void onGraphMLParsed(y.io.graphml.input.ParseEvent event) {
260 Graph2D graph = (Graph2D) event.getContext().getGraph();
261 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
262 Node n = nc.node();
263 Tuple tuple = (Tuple) tempConnectorMap.get(n);
264 if(tuple != null) {
265 Edge edge = (Edge) edgeIdMap.get(tuple.o1);
266 Double ratio = Double.valueOf(tuple.o2.toString());
267 EdgeConnectorManager.map.put(n, new Tuple(edge, ratio));
268 }
269 }
270 }
271 });
272 return ioHandler;
273 }
274
275
279 protected Graph2DClipboard getClipboard() {
280 final Graph2DClipboard clipboard = super.getClipboard();
281 clipboard.setCopyFactory(new EdgeConnectorGraphCopyFactory(clipboard.getCopyFactory()));
282 return clipboard;
283 }
284
285 protected void registerViewActions() {
286 super.registerViewActions();
288 ActionMap amap = view.getCanvasComponent().getActionMap();
289 InputMap imap = view.getCanvasComponent().getInputMap();
290 if (!isDeletionEnabled()) {
291 amap.remove(Graph2DViewActions.DELETE_SELECTION);
292 }
293 view.getCanvasComponent().setActionMap(amap);
294 view.getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, imap);
295 }
296
297
300 static class EdgeConnectorManager {
301 static final Map map = new WeakHashMap();
302
303 private EdgeConnectorManager() {
304 }
305
306 static boolean isEdgeConnector(Node n) {
307 return map.containsKey(n);
308 }
309
310 static void addEdgeConnection(Node connector, Edge edge, double pathRatio) {
311 map.put(connector, Tuple.create(edge, new Double(pathRatio)));
312 }
313
314 static Edge getEdgeConnection(Node connector) {
315 Tuple tuple = (Tuple) map.get(connector);
316 if (tuple != null) {
317 return (Edge) tuple.o1;
318 }
319 return null;
320 }
321
322 static double getEdgeConnectionRatio(Node connector) {
323 Tuple tuple = (Tuple) map.get(connector);
324 if (tuple != null) {
325 return ((Double) tuple.o2).doubleValue();
326 }
327 return 0.0; }
329
330 static NodeList getConnectorNodes(Edge edge) {
331 NodeList result = new NodeList();
332 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
333 Map.Entry entry = (Map.Entry) iter.next();
334 Tuple value = (Tuple) entry.getValue();
335 if (value.o1 == edge) {
336 result.add(entry.getKey());
337 }
338 }
339 return result;
340 }
341
342 static NodeRealizer createEdgeConnectorRealizer() {
343 GenericNodeRealizer gnr = new GenericNodeRealizer("EdgeConnector");
344 gnr.setSize(5,5);
345 gnr.setFillColor(DemoDefaults.DEFAULT_CONTRAST_COLOR);
346 return gnr;
347 }
348
349 }
350
351
358 static class EdgeConnectorListener implements GraphListener {
359
360 private int block;
361
362 private Map block2edges;
363
364 private boolean armed;
365
366 EdgeConnectorListener() {
367 armed = true;
368 }
369
370 public void onGraphEvent(final GraphEvent e) {
371 if (!armed) {
372 return;
373 }
374
375 switch (e.getType()) {
376 case GraphEvent.PRE_EVENT:
377 ++block;
378 break;
379 case GraphEvent.POST_EVENT:
380 handleBlock();
381 --block;
382 break;
383 case GraphEvent.POST_EDGE_REMOVAL:
384 storeForHandleBlock((Edge) e.getData());
385 break;
386 }
387 }
388
389
393 private void storeForHandleBlock( final Edge e ) {
394 if (block2edges == null) {
395 block2edges = new HashMap();
396 }
397 final Integer key = new Integer(block);
398 EdgeList edges = (EdgeList) block2edges.get(key);
399 if (edges == null) {
400 edges = new EdgeList();
401 block2edges.put(key, edges);
402 }
403 edges.add(e);
404 }
405
406
410 private void handleBlock() {
411 if (block2edges == null) {
412 return;
413 }
414
415 final EdgeList el = (EdgeList) block2edges.remove(new Integer(block));
416 if (el == null) {
417 return;
418 }
419
420 armed = false;
421 handleRecursive(el);
422 armed = true;
423
424 if (block2edges.isEmpty()) {
425 block2edges = null;
426 }
427 }
428
429 private void handleRecursive( final EdgeList el ) {
430 final EdgeList cascade = new EdgeList();
431 for (EdgeCursor ec = el.edges(); ec.ok(); ec.next()) {
432 final Edge edge = ec.edge();
433 Node node;
434 node = edge.source();
435 if (EdgeConnectorManager.getEdgeConnection(node) != null) {
436 final Graph graph = node.getGraph();
437 if (graph != null && node.degree() == 0) {
438 graph.removeNode(node);
439 }
440 }
441 node = edge.target();
442 if (EdgeConnectorManager.getEdgeConnection(node) != null) {
443 final Graph graph = node.getGraph();
444 if (graph != null && node.degree() == 0) {
445 graph.removeNode(node);
446 }
447 }
448 final NodeList connectors = EdgeConnectorManager.getConnectorNodes(edge);
449 if (connectors != null) {
450 for (NodeCursor nc = connectors.nodes(); nc.ok(); nc.next()) {
451 node = nc.node();
452 final Graph graph = node.getGraph();
453 if (graph != null) {
454 for (EdgeCursor nec = node.edges(); nec.ok(); nec.next()) {
455 cascade.add(nec.edge());
456 }
457 graph.removeNode(node);
458 }
459 }
460 }
461 }
462
463 if (!cascade.isEmpty()) {
464 handleRecursive(cascade);
465 }
466 }
467 }
468
501
504 static class EdgeConnectorMoveSelectionMode extends MoveSelectionMode {
505 protected NodeList getNodesToBeMoved() {
506 NodeList result = super.getNodesToBeMoved();
507 for(NodeCursor nc = result.nodes(); nc.ok(); nc.next()) {
508 Node n = nc.node();
509 for(EdgeCursor ec = n.edges(); ec.ok(); ec.next()) {
510 Edge edge = ec.edge();
511 NodeList connectors = EdgeConnectorManager.getConnectorNodes(edge);
512 result.splice(connectors);
513 }
514 }
515 BendList bends = getBendsToBeMoved();
516 for (BendCursor bc = bends.bends(); bc.ok(); bc.next()) {
517 Bend b = bc.bend();
518 NodeList connectors = EdgeConnectorManager.getConnectorNodes(b.getEdge());
519 result.splice(connectors);
520 }
521 return result;
522 }
523 }
524
525
528 static class CreateEdgeConnectorMode extends CreateEdgeMode {
529 private Node startNode;
530
531 public void mousePressedLeft(double x, double y) {
532 final Node hitNode = getHitInfo(x, y).getHitNode();
534 if (hitNode != null) {
535 getGraph2D().firePreEvent();
536 }
537 super.mousePressedLeft(x, y);
538 }
539
540 public void mouseShiftPressedLeft(double x, double y) {
541 final Node hitNode = getHitInfo(x, y).getHitNode();
543 if (hitNode != null) {
544 getGraph2D().firePreEvent();
545 }
546 if(isEditing()) {
547 super.mouseShiftPressedLeft(x,y);
548 }
549 else {
550 Graph2D graph = getGraph2D();
551 Edge edge = getHitInfo(x,y).getHitEdge();
552 if (edge != null) {
553 getGraph2D().firePreEvent();
555 NodeRealizer ecNR = EdgeConnectorManager.createEdgeConnectorRealizer();
556 Point2D p = new Point2D.Double(x, y);
557 double[] result = PointPathProjector.calculateClosestPathPoint(graph.getRealizer(edge).getPath(), p);
558 ecNR.setCenter(result[0], result[1]);
559 startNode = getGraph2D().createNode(ecNR);
561 view.updateView();
562 super.mouseShiftPressedLeft(result[0], result[1]);
563 EdgeConnectorManager.addEdgeConnection(startNode, edge, result[5]);
564 }
565 else {
566 startNode = null;
567 super.mouseShiftPressedLeft(x, y);
568 }
569 }
570 }
571
572 public void mouseReleasedLeft(double x, double y) {
573 super.mouseReleasedLeft(x, y);
575 final Node hitNode = getHitInfo(x, y).getHitNode();
576 if (hitNode != null) {
577 getGraph2D().firePostEvent();
578 }
579 }
580
581 public void mouseShiftReleasedLeft(double x, double y) {
582 Graph2D graph = getGraph2D();
583 Edge edge = getHitInfo(x, y).getHitEdge();
584 if (edge != null) {
585 NodeRealizer ecNR = EdgeConnectorManager.createEdgeConnectorRealizer();
586 Point2D p = new Point2D.Double(x, y);
587 double[] result = PointPathProjector.calculateClosestPathPoint(graph.getRealizer(edge).getPath(), p);
588 ecNR.setCenter(result[0], result[1]);
589 Node endNode = getGraph2D().createNode(ecNR);
590 view.updateView();
591 super.mouseShiftReleasedLeft(result[0], result[1]);
592 EdgeConnectorManager.addEdgeConnection(endNode, edge, result[5]);
593 getGraph2D().firePostEvent();
595 } else {
596 super.mouseShiftReleasedLeft(x, y);
597 }
598 final Node hitNode = getHitInfo(x, y).getHitNode();
600 if (hitNode != null && !EdgeConnectorManager.isEdgeConnector(hitNode)) {
601 getGraph2D().firePostEvent();
602 }
603 }
604
605 public HitInfo getHitInfo(double x, double y) {
606 final HitInfo info = view.getHitInfoFactory()
607 .createHitInfo(x, y, Graph2DTraversal.ALL, false);
608 setLastHitInfo(info);
609 return info;
610 }
611
612 protected void cancelEdgeCreation() {
613 if(startNode != null) {
614 final Node tmp = startNode;
615 startNode = null;
616 getGraph2D().removeNode(tmp);
617 getGraph2D().firePostEvent();
619 }
620 super.cancelEdgeCreation();
621 }
622
623 public void setEditing(boolean active) {
624 if (!active) {
625 startNode = null;
626 }
627 super.setEditing(active);
628 }
629 }
630
631 static class EdgeConnectorEditMode extends EditMode {
632 public void mouseDraggedLeft(double x, double y) {
633 if(isModifierPressed(lastPressEvent)) {
634 double px = translateX(lastPressEvent.getX());
635 double py = translateY(lastPressEvent.getY());
636 Edge edge = getHitInfo(px,py).getHitEdge();
637 if(edge != null) {
638 setChild(getCreateEdgeMode(), lastPressEvent, lastDragEvent);
639 return;
640 }
641 }
642 super.mouseDraggedLeft(x, y);
643 }
644 }
645
646
650 static class EdgeConnectorMovePortMode extends MovePortMode {
651
652 protected YList getPortCandidates(Node v, Edge e, double gridSpacing) {
653 Edge connectedEdge = EdgeConnectorManager.getEdgeConnection(v);
654 if(connectedEdge != null) {
655 Graph2D graph = getGraph2D();
656 YList result = new YList();
658 YPoint yport = e.source() == v ? graph.getSourcePointAbs(e) : graph.getTargetPointAbs(e);
659 Point2D p = new Point2D.Double(yport.x, yport.y);
660 double[] pppResult = PointPathProjector.calculateClosestPathPoint(getGraph2D().getRealizer(connectedEdge).getPath(), p);
661 result.add(new YPoint(pppResult[0], pppResult[1]));
662 return result;
663 }
664 return super.getPortCandidates(v,e,gridSpacing);
665 }
666
667 public void mouseReleasedLeft(double x, double y) {
668 Port p = this.port;
669 if(p != null) {
670 Edge e = p.getOwner().getEdge();
671 Node v = null;
672 if(p == p.getOwner().getTargetPort()) {
673 v = e.target();
674 }
675 else {
676 v = e.source();
677 }
678 Edge connectedEdge = EdgeConnectorManager.getEdgeConnection(v);
679 if(connectedEdge == null) {
680 super.mouseReleasedLeft(x,y);
681 return;
682 }
683 else {
684 double[] result = PointPathProjector.calculateClosestPathPoint(getGraph2D().getRealizer(connectedEdge).getPath(), x, y);
685 double ratio = result[5];
686 EdgeConnectorManager.addEdgeConnection(v, connectedEdge, ratio);
687 super.mouseReleasedLeft(x,y);
688 getGraph2D().setCenter(v, result[0], result[1]);
689 p.setOffsets(0,0);
690 }
691 getGraph2D().updateViews();
692 }
693 }
694 }
695
696
699 public static class PointPathProjector {
700
701 private PointPathProjector() {
702 }
703
704 public static double[] calculateClosestPathPoint(GeneralPath path, double px, double py) {
705 return calculateClosestPathPoint(path, new Point2D.Double(px,py));
706 }
707
708
725 public static double[] calculateClosestPathPoint(GeneralPath path, Point2D p) {
726 double[] result = new double[6];
727 double px = p.getX();
728 double py = p.getY();
729 YPoint point = new YPoint(px, py);
730 double pathLength = 0;
731
732 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
733 double[] curSeg = new double[4];
734 double minDist;
735 if (pi.ok()) {
736 curSeg = pi.segment();
737 minDist = YPoint.distance(px, py, curSeg[0], curSeg[1]);
738 result[0] = curSeg[0];
739 result[1] = curSeg[1];
740 result[2] = minDist;
741 result[3] = 0.0;
742 result[4] = 0.0;
743 result[5] = 0.0;
744 } else {
745 throw new IllegalStateException("path without any coordinates");
747 }
748
749 int segmentIndex = 0;
750 double lastPathLength = 0.0;
751 do {
752 YPoint segmentStart = new YPoint(curSeg[0], curSeg[1]);
753 YPoint segmentEnd = new YPoint(curSeg[2], curSeg[3]);
754 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
755 double segmentLength = segmentDirection.length();
756 pathLength += segmentLength;
757 segmentDirection.norm();
758
759 YPoint crossing = Geom.calcIntersection(segmentStart, segmentDirection, point, YVector.orthoNormal(segmentDirection));
760 YVector crossingVector = new YVector(crossing, segmentStart);
761
762 YVector segmentVector = new YVector(segmentEnd, segmentStart);
763 double indexEnd = YVector.scalarProduct(segmentVector, segmentDirection);
764 double indexCrossing = YVector.scalarProduct(crossingVector, segmentDirection);
765
766 double dist;
767 double segmentRatio;
768 YPoint nearestOnSegment;
769 if (indexCrossing <= 0.0) {
770 dist = YPoint.distance(point, segmentStart);
771 nearestOnSegment = segmentStart;
772 segmentRatio = 0.0;
773 } else if (indexCrossing >= indexEnd) {
774 dist = YPoint.distance(point, segmentEnd);
775 nearestOnSegment = segmentEnd;
776 segmentRatio = 1.0;
777 } else {
778 dist = YPoint.distance(point, crossing);
779 nearestOnSegment = crossing;
780 segmentRatio = indexCrossing / indexEnd;
781 }
782
783 if (dist < minDist) {
784 minDist = dist;
785 result[0] = nearestOnSegment.getX();
786 result[1] = nearestOnSegment.getY();
787 result[2] = minDist;
788 result[3] = segmentIndex;
789 result[4] = segmentRatio;
790 result[5] = segmentLength * segmentRatio + lastPathLength;
791 }
792
793 segmentIndex++;
794 lastPathLength = pathLength;
795 pi.next();
796 } while (pi.ok());
797
798 if(pathLength > 0) {
799 result[5] = result[5] / pathLength;
800 } else {
801 result[5] = 0.0;
802 }
803 return result;
804 }
805
806 static Point2D getPointForGlobalRatio(EdgeRealizer er, double globalRatio) {
807 GeneralPath path = er.getPath();
808 if(globalRatio > 1.0 || globalRatio < 0.0) {
809 throw new IllegalArgumentException("globalRatio outside of [0,1]");
810 }
811 double totalPathLength = getPathLength(path);
812 double targetPathLength = totalPathLength * globalRatio;
813 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
814 YPoint segmentStart = null, segmentEnd = null;
815 if (pi.isDone()) {
816 return getPointFromEndpoints(er);
819 } else {
820 segmentStart = pi.segmentStart();
821 segmentEnd = pi.segmentEnd();
822 }
823
824 double currentPathLength = 0.0;
825 double lastPathLength = 0.0;
826 while (pi.ok()) {
827 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
828 double segmentLength = segmentDirection.length();
829 currentPathLength += segmentLength;
830 if(currentPathLength / totalPathLength >= globalRatio) {
831 double remainingLength = targetPathLength - lastPathLength;
832 double localRatio = remainingLength / segmentLength;
833 segmentDirection.scale(localRatio);
834 YPoint targetPoint = YVector.add(segmentStart, segmentDirection);
835 return new Point2D.Double(targetPoint.getX(),targetPoint.getY());
836 }
837
838 lastPathLength = currentPathLength;
839 pi.next();
840 segmentStart = pi.segmentStart();
841 segmentEnd = pi.segmentEnd();
842 }
843
844 return new Point2D.Double(segmentStart.getX(), segmentStart.getY());
846 }
847
848 static Point2D getPointForLocalRatio(EdgeRealizer er, int segmentIndex, double segmentRatio) {
849 GeneralPath path = er.getPath();
850 if (segmentRatio > 1.0 || segmentRatio < 0.0) {
851 throw new IllegalArgumentException("segmentRatio outside of [0,1]");
852 }
853 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
854 if (pi.isDone()) {
855 return getPointFromEndpoints(er);
858 }
859 int currentIndex = 0;
860 while (pi.ok() && currentIndex < segmentIndex) {
861 pi.next();
862 currentIndex++;
863 }
864 if(currentIndex < segmentIndex)
865 {
866 throw new IllegalArgumentException("found no segment for given segmentIndex");
867 }
868
869 YPoint segmentStart = pi.segmentStart();
870 YPoint segmentEnd = pi.segmentEnd();
871 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
872 segmentDirection.scale(segmentRatio);
873 YPoint targetPoint = YVector.add(segmentStart, segmentDirection);
874 return new Point2D.Double(targetPoint.getX(), targetPoint.getY());
875 }
876
877 private static Point2D getPointFromEndpoints(EdgeRealizer er) {
878 final NodeRealizer sourceRealizer = er.getSourceRealizer();
879 double sourceX = sourceRealizer.getCenterX();
880 double sourceY = sourceRealizer.getCenterY();
881 final NodeRealizer targetRealizer = er.getTargetRealizer();
882 double targetX = targetRealizer.getCenterX();
883 double targetY = targetRealizer.getCenterY();
884 return new Point2D.Double((sourceX + targetX) * 0.5, (sourceY + targetY) * 0.5);
885 }
886
887 private static double getPathLength(GeneralPath path) {
888 double length = 0.0;
889 for(CustomPathIterator pi = new CustomPathIterator(path, 1.0); pi.ok(); pi.next()) {
890 length += pi.segmentDirection().length();
891 }
892 return length;
893 }
894 }
895
896
899 static class CustomPathIterator {
900 private double[] cachedSegment;
901 private boolean moreToGet;
902 private PathIterator pathIterator;
903
904 public CustomPathIterator(GeneralPath path, double flatness) {
905 pathIterator = (new GeneralPath(path)).getPathIterator(null, flatness);
907 cachedSegment = new double[4];
908 getFirstSegment();
909 }
910
911 public boolean ok()
912 {
913 return moreToGet;
914 }
915
916 public boolean isDone() {
917 return !moreToGet;
918 }
919
920 public final double[] segment() {
921 if (moreToGet) {
922 return cachedSegment;
923 } else {
924 return null;
925 }
926 }
927
928 public YPoint segmentStart() {
929 if(moreToGet) {
930 return new YPoint(cachedSegment[0], cachedSegment[1]);
931 } else {
932 return null;
933 }
934 }
935
936 public YPoint segmentEnd() {
937 if(moreToGet) {
938 return new YPoint(cachedSegment[2], cachedSegment[3]);
939 } else {
940 return null;
941 }
942 }
943
944 public YVector segmentDirection() {
945 if(moreToGet) {
946 return new YVector(segmentEnd(), segmentStart());
947 } else {
948 return null;
949 }
950 }
951
952 public void next() {
953 if (!pathIterator.isDone()) {
954 float[] curSeg = new float[2];
955 cachedSegment[0] = cachedSegment[2];
956 cachedSegment[1] = cachedSegment[3];
957 pathIterator.currentSegment(curSeg);
958 cachedSegment[2] = curSeg[0];
959 cachedSegment[3] = curSeg[1];
960 pathIterator.next();
961 } else {
962 moreToGet = false;
963 }
964 }
965
966 private void getFirstSegment() {
967 float[] curSeg = new float[2];
968 if (!pathIterator.isDone()) {
969 pathIterator.currentSegment(curSeg);
970 cachedSegment[0] = curSeg[0];
971 cachedSegment[1] = curSeg[1];
972 pathIterator.next();
973 moreToGet = true;
974 } else {
975 moreToGet = false;
976 }
977 if (!pathIterator.isDone()) {
978 pathIterator.currentSegment(curSeg);
979 cachedSegment[2] = curSeg[0];
980 cachedSegment[3] = curSeg[1];
981 pathIterator.next();
982 moreToGet = true;
983 } else {
984 moreToGet = false;
985 }
986 }
987 }
988
989
992 private class EdgeConnectorGraphCopyFactory implements GraphCopier.CopyFactory {
993 private final GraphCopier.CopyFactory copyFactory;
994 private final HashMap node2connector;
995
996 private EdgeConnectorGraphCopyFactory(GraphCopier.CopyFactory copyFactory) {
997 this.copyFactory = copyFactory;
998 node2connector = new HashMap();
999 }
1000
1001 public Node copyNode(Graph targetGraph, Node originalNode) {
1002 return copyFactory.copyNode(targetGraph, originalNode);
1003 }
1004
1005 public Edge copyEdge(Graph targetGraph, Node newSource, Node newTarget, Edge originalEdge) {
1006 return copyFactory.copyEdge(targetGraph, newSource, newTarget, originalEdge);
1007 }
1008
1009 public Graph createGraph() {
1010 return copyFactory.createGraph();
1011 }
1012
1013 public void preCopyGraphData(Graph sourceGraph, Graph targetGraph) {
1014 copyFactory.preCopyGraphData(sourceGraph, targetGraph);
1015 }
1016
1017
1021 public void postCopyGraphData(Graph sourceGraph, Graph targetGraph, Map nodeMap, Map edgeMap) {
1022 copyFactory.postCopyGraphData(sourceGraph, targetGraph, nodeMap, edgeMap);
1023
1024 if (sourceGraph == view.getGraph2D()) {
1026 node2connector.clear();
1029 for (NodeCursor nc = sourceGraph.nodes(); nc.ok(); nc.next()) {
1030 final Node sourceNode = nc.node();
1031 if (EdgeConnectorManager.isEdgeConnector(sourceNode)) {
1032 final Node targetNode = (Node) nodeMap.get(sourceNode);
1033 if (targetNode != null) {
1034 final Edge sourceEdge = EdgeConnectorManager.getEdgeConnection(sourceNode);
1035 final Edge targetEdge = (Edge) edgeMap.get(sourceEdge);
1036 final double ratio = EdgeConnectorManager.getEdgeConnectionRatio(sourceNode);
1037 node2connector.put(targetNode, Tuple.create(targetEdge, new Double(ratio)));
1038 }
1039 }
1040 }
1041
1042 for (NodeCursor nc = targetGraph.nodes(); nc.ok(); nc.next()) {
1044 final Node targetNode = nc.node();
1045 if (node2connector.containsKey(targetNode)
1046 && (targetNode.degree() == 0 || ((Tuple) node2connector.get(targetNode)).o1 == null)) {
1047 targetGraph.removeNode(targetNode);
1048 }
1049 }
1050 } else {
1051 for (NodeCursor nc = sourceGraph.nodes(); nc.ok(); nc.next()) {
1054 final Node sourceNode = nc.node();
1055 if (node2connector.containsKey(sourceNode)) {
1056 final Node targetNode = (Node) nodeMap.get(sourceNode);
1057 final Tuple connector = (Tuple) node2connector.get(sourceNode);
1058 final Edge sourceEdge = (Edge) connector.o1;
1059 final Edge targetEdge = (Edge) edgeMap.get(sourceEdge);
1060 EdgeConnectorManager.addEdgeConnection(targetNode, targetEdge, ((Double) connector.o2).doubleValue());
1061 }
1062 }
1063 }
1064 }
1065 }
1066
1067 public static void main(String[] args) {
1068 EventQueue.invokeLater(new Runnable() {
1069 public void run() {
1070 Locale.setDefault(Locale.ENGLISH);
1071 initLnF();
1072 (new EdgeConnectorDemo()).start();
1073 }
1074 });
1075 }
1076}
1077