1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.layout.tree;
15  
16  import demo.view.DemoBase;
17  import y.algo.Bfs;
18  import y.base.Edge;
19  import y.base.EdgeMap;
20  import y.base.Node;
21  import y.base.NodeCursor;
22  import y.base.NodeList;
23  import y.base.NodeMap;
24  import y.geom.YPoint;
25  import y.layout.PortConstraint;
26  import y.layout.PortConstraintKeys;
27  import y.layout.tree.DefaultNodePlacer;
28  import y.layout.tree.GenericTreeLayouter;
29  import y.layout.tree.NodePlacer;
30  import y.util.DataProviderAdapter;
31  import y.view.Arrow;
32  import y.view.CreateChildEdgeMode;
33  import y.view.EdgeRealizer;
34  import y.view.EditMode;
35  import y.view.Graph2D;
36  import y.view.Graph2DSelectionEvent;
37  import y.view.Graph2DSelectionListener;
38  import y.view.HotSpotMode;
39  import y.view.LineType;
40  import y.view.NodeRealizer;
41  import y.view.PolyLineEdgeRealizer;
42  import y.view.PopupMode;
43  import y.view.PortAssignmentMoveSelectionMode;
44  
45  import javax.swing.AbstractAction;
46  import javax.swing.JButton;
47  import javax.swing.JLabel;
48  import javax.swing.JMenu;
49  import javax.swing.JPanel;
50  import javax.swing.JPopupMenu;
51  import javax.swing.JSpinner;
52  import javax.swing.JSplitPane;
53  import javax.swing.SpinnerNumberModel;
54  import javax.swing.event.ChangeEvent;
55  import javax.swing.event.ChangeListener;
56  import java.awt.BorderLayout;
57  import java.awt.Color;
58  import java.awt.Cursor;
59  import java.awt.FlowLayout;
60  import java.awt.EventQueue;
61  import java.awt.event.ActionEvent;
62  import java.util.ArrayList;
63  import java.util.List;
64  import java.util.Locale;
65  
66  /**
67   * This demo shows how GenericTreeLayouter can handle port constraints and multiple 
68   * different NodePlacer instances and implementations at the same time. 
69   * <br>
70   * On another note, it also demonstrates how different ViewModes can be subclassed 
71   * or replaced to achieve a completely different application feel.
72   * <br>
73   * Usage: Use the panel on the left to change the layout settings such as port constraints
74   * or node placers for all nodes at a certain level of the tree simultaneously by pressing
75   * the "Apply" button. The panel in the lower left is a preview for the currently displayed
76   * settings. You can also change the settings for individual nodes by using their context
77   * menus.
78   *
79   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/cls_GenericTreeLayouter.html">Section Generic Tree Layout</a> in the yFiles for Java Developer's Guide
80   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/mvc_controller.html#cls_ViewMode">Section ViewMode Workings</a> in the yFiles for Java Developer's Guide
81   */
82  public class IncrementalTreeLayouterDemo extends DemoBase {
83    private static final Color[] layerColors = {Color.red, Color.orange, Color.yellow, Color.cyan, Color.green,
84        Color.blue};
85  
86    private EdgeMap targetPortMap;
87    private NodeMap nodePlacerMap;
88  
89    private PortAssignmentMoveSelectionMode paMode;
90    private double hDistance = 40.0;
91  
92    private double vDistance = 40.0;
93  
94    private GenericTreeLayouter treeLayouter;
95  
96    private DefaultNodePlacerConfigPanel configPanel;
97  
98    private List layerStyles = new ArrayList();
99  
100   {
101     layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD,
102         DefaultNodePlacer.ALIGNMENT_MEDIAN, 40.0, 40.0));
103     layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT,
104         DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET, 20.0, 40.0));
105     layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD,
106         DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET, DefaultNodePlacer.ROUTING_FORK_AT_ROOT, 10.0, 20.0));
107     layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD,
108         DefaultNodePlacer.ALIGNMENT_MEDIAN, 40.0, 40.0));
109   }
110 
111   public IncrementalTreeLayouterDemo() {
112     final Graph2D graph = view.getGraph2D();
113     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
114     defaultER.setArrow(Arrow.STANDARD);
115     ((PolyLineEdgeRealizer) defaultER).setSmoothedBends(true);
116     defaultER.setLineType(LineType.LINE_2);
117 
118     EdgeMap sourcePortMap = graph.createEdgeMap();
119     targetPortMap = graph.createEdgeMap();
120     NodeMap portAssignmentMap = graph.createNodeMap();
121     nodePlacerMap = graph.createNodeMap();
122     graph.addDataProvider(GenericTreeLayouter.NODE_PLACER_DPKEY, nodePlacerMap);
123     graph.addDataProvider(GenericTreeLayouter.PORT_ASSIGNMENT_DPKEY, portAssignmentMap);
124     graph.addDataProvider(GenericTreeLayouter.CHILD_COMPARATOR_DPKEY, new ChildEdgeComparatorProvider());
125     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, sourcePortMap);
126     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, targetPortMap);
127 
128 
129     paMode.setSpc(sourcePortMap);
130     paMode.setTpc(targetPortMap);
131 
132     treeLayouter = new GenericTreeLayouter();
133 
134     configPanel = new DefaultNodePlacerConfigPanel();
135     configPanel.adoptPlacerValues((NodePlacer) layerStyles.get(0));
136 
137     JPanel layerChooserPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
138     layerChooserPanel.add(new JLabel("Layer: "));
139     final JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1));
140     spinner.addChangeListener(new ChangeListener() {
141       public void stateChanged(ChangeEvent ce) {
142         final int layer = ((Number) spinner.getValue()).intValue() - 1;
143         while (layer >= layerStyles.size()) {
144           layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, 40.0, 40.0));
145         }
146         NodePlacer placer = (NodePlacer) layerStyles.get(layer);
147         configPanel.adoptPlacerValues(placer);
148       }
149     }
150     );
151     layerChooserPanel.add(spinner);
152 
153     configPanel.addChangeListener(new ChangeListener() {
154       public void stateChanged(ChangeEvent ce) {
155         final int layer = ((Number) spinner.getValue()).intValue() - 1;
156         while (layer >= layerStyles.size()) {
157           layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, 40.0, 40.0));
158         }
159         layerStyles.set(layer, configPanel.createPlacerCopy());
160       }
161     }
162     );
163 
164     JButton button = new JButton(new AbstractAction("Apply") {
165       public void actionPerformed(ActionEvent ae) {
166         final int layer = ((Number) spinner.getValue()).intValue() - 1;
167         NodePlacer placer = (NodePlacer) layerStyles.get(layer);
168         NodeList[] layers = Bfs.getLayers(graph, new NodeList(graph.firstNode()));
169         if (layer < layers.length) {
170           for (NodeCursor nc = layers[layer].nodes(); nc.ok(); nc.next()) {
171             nodePlacerMap.set(nc.node(), placer);
172           }
173           calcLayout();
174         }
175       }
176     });
177 
178     graph.addGraph2DSelectionListener(new Graph2DSelectionListener() {
179       public void onGraph2DSelectionEvent(Graph2DSelectionEvent ev) {
180         if (ev.isNodeSelection() && graph.isSelectionSingleton()) {
181           Node n = (Node) ev.getSubject();
182           int depth = 1;
183           while (n.inDegree() > 0) {
184             n = n.firstInEdge().source();
185             depth++;
186           }
187           spinner.setValue(new Integer(depth));
188         }
189       }
190     }
191     );
192     layerChooserPanel.add(button);
193 
194     JPanel rightPanel = new JPanel(new BorderLayout());
195     rightPanel.add(configPanel, BorderLayout.CENTER);
196     rightPanel.add(layerChooserPanel, BorderLayout.NORTH);
197 
198     JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, rightPanel, view);
199     sp.setOneTouchExpandable(true);
200     sp.setContinuousLayout(false);
201     contentPane.add(sp, BorderLayout.CENTER);
202     createSampleGraph(graph);
203   }
204 
205   final class ChildEdgeComparatorProvider extends DataProviderAdapter {
206     public Object get(Object forRootNode) {
207       NodePlacer placer = (NodePlacer) nodePlacerMap.get(forRootNode);
208       if (placer instanceof DefaultNodePlacer) {
209         return ((DefaultNodePlacer) placer).createComparator();
210       }
211       return null;
212     }
213   }
214 
215   private void createSampleGraph(Graph2D graph) {
216     graph.clear();
217     Node root = graph.createNode();
218     graph.getRealizer(root).setFillColor(layerColors[0]);
219     nodePlacerMap.set(root, layerStyles.get(0));
220     createChildren(graph, root, 4, 1, 2);
221     calcLayout();
222   }
223 
224   private void createChildren(Graph2D graph, Node root, int children, int layer, int layers) {
225     for (int i = 0; i < children; i++) {
226       Node child = graph.createNode();
227       graph.createEdge(root, child);
228       graph.getRealizer(child).setFillColor(layerColors[layer % layerColors.length]);
229       if (layerStyles.size() > layer) {
230         nodePlacerMap.set(child, layerStyles.get(layer));
231       }
232       if (layers > 0) {
233         createChildren(graph, child, children, layer + 1, layers - 1);
234       }
235     }
236   }
237 
238   protected boolean isDeletionEnabled() {
239     return false;
240   }
241 
242   protected void registerViewModes() {
243     EditMode editMode = new TreeCreateEditMode();
244     view.addViewMode(editMode);
245   }
246 
247 
248   public void calcLayout() {
249     if (!view.getGraph2D().isEmpty()) {
250       Cursor oldCursor = view.getViewCursor();
251       try {
252         view.setViewCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
253         view.applyLayoutAnimated(treeLayouter);
254       } finally {
255         view.setViewCursor(oldCursor);
256       }
257     }
258   }
259 
260   final class TreeCreateChildEdgeMode extends CreateChildEdgeMode {
261 
262     NodeRealizer activeDummyTargetRealizer;
263 
264     protected boolean acceptSourceNode(Node source, double x, double y) {
265       final boolean accept = super.acceptSourceNode(source, x, y);
266       activeDummyTargetRealizer = createChildNodeRealizer();
267       int depth = 1;
268       for (Node n = source; n.inDegree() > 0; n = n.firstInEdge().source()){
269               depth++;
270       }
271       activeDummyTargetRealizer.setFillColor(layerColors[depth % layerColors.length]);
272       return accept;
273     }
274 
275     protected NodeRealizer createDummyTargetNodeRealizer(double x, double y) {
276       return activeDummyTargetRealizer;
277     }
278 
279     protected void edgeCreated(Edge e) {
280       int depth = 1;
281       for (Node n = e.source(); n.inDegree() > 0; n = n.firstInEdge().source()) {
282         depth++;
283       }
284       Graph2D g = getGraph2D();
285       g.getRealizer(e.target()).setFillColor(layerColors[depth % layerColors.length]);
286       EdgeRealizer er = g.getRealizer(e);
287       if (nodePlacerMap.get(e.source()) == null) {
288         parseNodePlacement(g, e, er);
289       }
290       if (layerStyles.size() > depth) {
291         nodePlacerMap.set(e.target(), layerStyles.get(depth));
292       }
293       parseTargetPort(g, e, er);
294       g.unselectAll();
295       calcLayout();
296     }
297 
298     private void parseNodePlacement(Graph2D g, Edge e, EdgeRealizer er) {
299       YPoint firstPoint = er.bendCount() > 0 ? new YPoint(er.firstBend().getX(), er.firstBend().getY()) :
300           g.getTargetPointAbs(e);
301       NodeRealizer source = g.getRealizer(e.source());
302       double dx = firstPoint.x - source.getCenterX();
303       double dy = firstPoint.y - source.getCenterY();
304       final byte placement;
305       final byte alignment = DefaultNodePlacer.ALIGNMENT_MEDIAN;
306       final byte routing = DefaultNodePlacer.ROUTING_FORK;
307       if (Math.abs(dx) > Math.abs(dy)) {
308         if (dx > 0.0) {
309           placement = DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT;
310         } else {
311           placement = DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT;
312         }
313       } else {
314         if (dy > 0.0) {
315           placement = DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD;
316         } else {
317           placement = DefaultNodePlacer.PLACEMENT_HORIZONTAL_UPWARD;
318         }
319       }
320       nodePlacerMap.set(e.source(), new DefaultNodePlacer(placement, alignment, routing, hDistance, vDistance));
321     }
322 
323     private void parseTargetPort(Graph2D g, Edge e, EdgeRealizer er) {
324       if (er.bendCount() > 0) {
325         YPoint lastPoint = new YPoint(er.lastBend().getX(), er.lastBend().getY());
326         NodeRealizer target = g.getRealizer(e.target());
327         double dx = lastPoint.x - target.getCenterX();
328         double dy = lastPoint.y - target.getCenterY();
329         byte side = PortConstraint.ANY_SIDE;
330         if (Math.abs(dx) > Math.abs(dy)) {
331           if (dx > 0.0) {
332             side = PortConstraint.EAST;
333           } else {
334             side = PortConstraint.WEST;
335           }
336         } else {
337           if (dy > 0.0) {
338             side = PortConstraint.SOUTH;
339           } else {
340             side = PortConstraint.NORTH;
341           }
342         }
343         targetPortMap.set(e, PortConstraint.create(side));
344       }
345     }
346 
347     protected NodeRealizer createChildNodeRealizer() {
348       NodeRealizer retValue;
349       retValue = super.createChildNodeRealizer();
350       retValue.setLabelText("");
351       return retValue;
352     }
353 
354   }
355 
356 
357   final class TreeLayouterPopupMode extends PopupMode {
358     private JPopupMenu nodePlacementMenu;
359 
360     TreeLayouterPopupMode() {
361       nodePlacementMenu = new JPopupMenu();
362       JMenu alignment = new JMenu("Root node Alignment");
363       JMenu placement = new JMenu("Child Placement");
364       JMenu routing = new JMenu("Routing Style");
365 
366       nodePlacementMenu.add(placement);
367       nodePlacementMenu.add(alignment);
368       nodePlacementMenu.add(routing);
369 
370       placement.add(new PlacementAction("Horizontally Downwards", DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD));
371       placement.add(new PlacementAction("Horizontally Upwards", DefaultNodePlacer.PLACEMENT_HORIZONTAL_UPWARD));
372       placement.add(new PlacementAction("Vertically to Left", DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT));
373       placement.add(new PlacementAction("Vertically to right", DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT));
374 
375       alignment.add(new AlignmentAction("Offset Leading", DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET));
376       alignment.add(new AlignmentAction("Leading", DefaultNodePlacer.ALIGNMENT_LEADING));
377       alignment.add(new AlignmentAction("Centered", DefaultNodePlacer.ALIGNMENT_CENTER));
378       alignment.add(new AlignmentAction("Median", DefaultNodePlacer.ALIGNMENT_MEDIAN));
379       alignment.add(new AlignmentAction("Trailing", DefaultNodePlacer.ALIGNMENT_TRAILING));
380       alignment.add(new AlignmentAction("Offset Trailing", DefaultNodePlacer.ALIGNMENT_TRAILING_OFFSET));
381 
382       routing.add(new RoutingAction("Fork", DefaultNodePlacer.ROUTING_FORK));
383       routing.add(new RoutingAction("Fork at Root", DefaultNodePlacer.ROUTING_FORK_AT_ROOT));
384       routing.add(new RoutingAction("Poly Line", DefaultNodePlacer.ROUTING_POLY_LINE));
385       routing.add(new RoutingAction("Straight", DefaultNodePlacer.ROUTING_STRAIGHT));
386     }
387 
388     public JPopupMenu getNodePopup(final Node v) {
389       return nodePlacementMenu;
390     }
391 
392     public JPopupMenu getSelectionPopup(double x, double y) {
393       if (getGraph2D().selectedNodes().ok()) {
394         return nodePlacementMenu;
395       } else {
396         return null;
397       }
398     }
399 
400   }
401 
402   abstract class AssignLayouterAction extends AbstractAction {
403 
404     protected AssignLayouterAction(String name) {
405       super(name);
406     }
407 
408     public void actionPerformed(ActionEvent e) {
409       NodeList selectedNodes = new NodeList(IncrementalTreeLayouterDemo.this.view.getGraph2D().selectedNodes());
410 
411       NodePlacer placer = (NodePlacer) nodePlacerMap.get(selectedNodes.firstNode());
412       placer = getPlacer(placer);
413 
414       DefaultNodePlacer dnp = (DefaultNodePlacer) placer;
415       for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()) {
416         nodePlacerMap.set(nc.node(), new DefaultNodePlacer(
417             dnp.getChildPlacement(),
418             dnp.getRootAlignment(),
419             dnp.getRoutingStyle(),
420             dnp.getHorizontalDistance(),
421             dnp.getVerticalDistance()));
422       }
423       calcLayout();
424     }
425 
426     protected abstract NodePlacer getPlacer(NodePlacer placer);
427   }
428 
429   final class PlacementAction extends AssignLayouterAction {
430 
431     private byte newPlacement;
432 
433     public PlacementAction(String name, byte newPlacement) {
434       super(name);
435       this.newPlacement = newPlacement;
436     }
437 
438     protected NodePlacer getPlacer(NodePlacer placer) {
439       if (placer instanceof DefaultNodePlacer) {
440         placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
441         ((DefaultNodePlacer) placer).setChildPlacement(newPlacement);
442       } else {
443         placer = new DefaultNodePlacer(newPlacement, DefaultNodePlacer.ALIGNMENT_MEDIAN, hDistance, vDistance);
444       }
445       return placer;
446     }
447   }
448 
449   final class AlignmentAction extends AssignLayouterAction {
450     private byte newAlignment;
451 
452     public AlignmentAction(String name, byte newAlignment) {
453       super(name);
454       this.newAlignment = newAlignment;
455     }
456 
457     protected NodePlacer getPlacer(NodePlacer placer) {
458       if (placer instanceof DefaultNodePlacer) {
459         placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
460         ((DefaultNodePlacer) placer).setRootAlignment(newAlignment);
461       } else {
462         placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, newAlignment, hDistance,
463             vDistance);
464       }
465       return placer;
466     }
467   }
468 
469   final class RoutingAction extends AssignLayouterAction {
470     private byte newRouting;
471 
472     public RoutingAction(String name, byte newRouting) {
473       super(name);
474       this.newRouting = newRouting;
475     }
476 
477     protected NodePlacer getPlacer(NodePlacer placer) {
478       if (placer instanceof DefaultNodePlacer) {
479         placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
480         ((DefaultNodePlacer) placer).setRoutingStyle(newRouting);
481       } else {
482         placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD,
483             DefaultNodePlacer.ALIGNMENT_CENTER, newRouting, hDistance, vDistance);
484       }
485       return placer;
486     }
487   }
488 
489   final class TreeHotSpotMode extends HotSpotMode {
490     public void mouseReleasedLeft(double x, double y) {
491       super.mouseReleasedLeft(x, y);
492       calcLayout();
493     }
494   }
495 
496   final class TreeCreateEditMode extends EditMode {
497     TreeCreateEditMode() {
498       super();
499       setMoveSelectionMode(paMode = new TreePortAssignmentMode());
500       setCreateEdgeMode(new TreeCreateChildEdgeMode());
501       setHotSpotMode(new TreeHotSpotMode());
502       setPopupMode(new TreeLayouterPopupMode());
503     }
504 
505     public boolean doAllowNodeCreation() {
506       return getGraph2D().N() == 0;
507     }
508 
509     protected void nodeCreated(Node v) {
510       super.nodeCreated(v);
511       nodePlacerMap.set(v, configPanel.createPlacerCopy());
512     }
513 
514   }
515 
516   final class TreePortAssignmentMode extends PortAssignmentMoveSelectionMode {
517     TreePortAssignmentMode() {
518       super(null, null);
519     }
520 
521     protected boolean isPortReassignmentAllowed(Edge edge, boolean source) {
522       return !source;
523     }
524 
525 //    protected void portConstraintsUpdated(Edge onEdge)
526 //    {
527 //      calcLayout();
528 //    }
529 
530     protected void selectionMovedAction(double dx, double dy, double x, double y) {
531       super.selectionMovedAction(dx, dy, x, y);
532       calcLayout();
533     }
534 
535   }
536 
537   /**
538    * Launches this demo.
539    */
540   public static void main(String[] args) {
541     EventQueue.invokeLater(new Runnable() {
542       public void run() {
543         Locale.setDefault(Locale.ENGLISH);
544         initLnF();
545         (new IncrementalTreeLayouterDemo()).start("Incremental Tree Layouter Demo");
546       }
547     });
548   }
549 }
550