1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
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-2008 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.layout.hierarchic;
15  
16  import y.base.DataProvider;
17  import y.base.Node;
18  import y.base.NodeCursor;
19  import y.base.NodeMap;
20  import y.layout.hierarchic.IncrementalHierarchicLayouter;
21  import y.layout.hierarchic.incremental.SwimLaneDescriptor;
22  import y.view.EditMode;
23  import y.view.Graph2D;
24  import y.view.HitInfo;
25  import y.view.ShapeNodeRealizer;
26  import y.view.ViewMode;
27  import y.view.NodeRealizer;
28  import y.view.hierarchy.HierarchyEditMode;
29  import y.view.hierarchy.DefaultHierarchyGraphFactory;
30  
31  import java.awt.Color;
32  import java.awt.event.ActionEvent;
33  import java.util.Iterator;
34  import java.util.TreeSet;
35  import javax.swing.AbstractAction;
36  import javax.swing.JToolBar;
37  
38  /**
39   * This demo shows the effect of combining
40   * <code>IncrementalHierarchicLayouter</code>'s support for grouping and
41   * swim lanes.
42   * <p>
43   * Things to try:
44   * </p>
45   * <ul>
46   *   <li>
47   *     Drag a node or set of nodes into another swim lane.
48   *     This will automatically trigger an incremental layout calculation.
49   *   </li>
50   *   <li>
51   *     Create a new node. It will be assigned to either a new swim lane if
52   *     created to the left or right of the exisiting lanes or to the lane in
53   *     which the node's center lies.
54   *     This will automatically trigger an incremental layout calculation.
55   *   </li>
56   *   <li>
57   *     Open/close folder/group nodes. Upon closing a group node, the resulting
58   *     folder node will be assigned to the minimum swim lane of the group's
59   *     child nodes.
60   *     This will automatically trigger an incremental layout calculation.
61   *   </li>
62   * </ul>
63   *
64   */
65  public class SwimLaneGroupDemo extends IncrementalHierarchicGroupDemo {
66    private final SwimLaneDrawable swimLaneDrawable;
67  
68    public SwimLaneGroupDemo() {
69      final Graph2D graph = view.getGraph2D();
70      swimLaneDrawable = new SwimLaneDrawable(graph, createSwimLaneMap(graph));
71      swimLaneDrawable.setEvenLaneColor(new Color(0, 130, 183));
72      swimLaneDrawable.setOddLaneColor(new Color(17, 144, 126));
73  
74      view.addBackgroundDrawable(swimLaneDrawable);
75      view.addViewMode(new SwimLaneViewMode());
76  
77      configureRealizers(graph);
78  
79      loadInitialGraph();
80    }
81  
82    /**
83     * Creates a sample graph to display initially.
84     */
85    protected void loadInitialGraph() {
86      final Graph2D graph = view.getGraph2D();
87      graph.clear();
88  
89      if (swimLaneDrawable != null) {
90        swimLaneDrawable.updateLanes();
91      }
92  
93      if (layouter != null && hierarchy != null) {
94        final Node n00 = graph.createNode();
95        final Node n01 = graph.createNode();
96        final Node g02 = hierarchy.createGroupNode(graph);
97        final Node g03 = hierarchy.createGroupNode(graph);
98        final Node g04 = hierarchy.createGroupNode(graph);
99        final Node n05 = graph.createNode();
100       final Node n06 = graph.createNode();
101       final Node n07 = graph.createNode();
102       final Node g08 = hierarchy.createGroupNode(graph);
103       final Node n09 = graph.createNode();
104       final Node n10 = graph.createNode();
105       final Node n11 = graph.createNode();
106       final Node g12 = hierarchy.createGroupNode(graph);
107       final Node n13 = graph.createNode();
108       final Node n14 = graph.createNode();
109       final Node n15 = graph.createNode();
110       final Node n16 = graph.createNode();
111       final Node n17 = graph.createNode();
112       final Node g18 = hierarchy.createGroupNode(graph);
113       final Node n19 = graph.createNode();
114       final Node n20 = graph.createNode();
115       final Node n21 = graph.createNode();
116       final Node n22 = graph.createNode();
117       final Node n23 = graph.createNode();
118 
119       hierarchy.setParentNode(g03, g02);
120       hierarchy.setParentNode(g04, g02);
121       hierarchy.setParentNode(n05, g02);
122       hierarchy.setParentNode(n06, g02);
123       hierarchy.setParentNode(n07, g02);
124 
125       hierarchy.setParentNode(g08, g03);
126       hierarchy.setParentNode(n09, g03);
127       hierarchy.setParentNode(n10, g03);
128       hierarchy.setParentNode(n11, g03);
129 
130       hierarchy.setParentNode(g12, g08);
131       hierarchy.setParentNode(n13, g08);
132       hierarchy.setParentNode(n14, g08);
133 
134       hierarchy.setParentNode(n15, g12);
135       hierarchy.setParentNode(n16, g12);
136       hierarchy.setParentNode(n17, g12);
137 
138       hierarchy.setParentNode(g18, g04);
139       hierarchy.setParentNode(n19, g04);
140       hierarchy.setParentNode(n20, g04);
141 
142       hierarchy.setParentNode(n21, g18);
143       hierarchy.setParentNode(n22, g18);
144       hierarchy.setParentNode(n23, g18);
145 
146       hierarchy.createEdge(n00, n01);
147       hierarchy.createEdge(n01, n06);
148       hierarchy.createEdge(n06, n07);
149       hierarchy.createEdge(n06, n05);
150       hierarchy.createEdge(n06, n20);
151       hierarchy.createEdge(n07, n11);
152       hierarchy.createEdge(n09, n05);
153       hierarchy.createEdge(n10, n05);
154       hierarchy.createEdge(n11, n09);
155       hierarchy.createEdge(n11, n14);
156       hierarchy.createEdge(n13, n09);
157       hierarchy.createEdge(n14, n13);
158       hierarchy.createEdge(n14, n15);
159       hierarchy.createEdge(n15, n13);
160       hierarchy.createEdge(n15, n17);
161       hierarchy.createEdge(n16, n13);
162       hierarchy.createEdge(n17, n16);
163       hierarchy.createEdge(n19, n05);
164       hierarchy.createEdge(n20, n19);
165       hierarchy.createEdge(n20, n21);
166       hierarchy.createEdge(n21, n22);
167       hierarchy.createEdge(n21, n23);
168       hierarchy.createEdge(n21, n05);
169       hierarchy.createEdge(n22, n05);
170       hierarchy.createEdge(n23, n05);
171 
172       final NodeMap swimLaneNm = createSwimLaneMap(graph);
173       swimLaneNm.set(n00, createSwimLaneDescriptor(9));
174       swimLaneNm.set(n01, createSwimLaneDescriptor(6));
175       // g02
176       // g03
177       // g04
178       swimLaneNm.set(n05, createSwimLaneDescriptor(6));
179       swimLaneNm.set(n06, createSwimLaneDescriptor(6));
180       swimLaneNm.set(n07, createSwimLaneDescriptor(2));
181       // g08
182       swimLaneNm.set(n09, createSwimLaneDescriptor(2));
183       swimLaneNm.set(n10, createSwimLaneDescriptor(1));
184       swimLaneNm.set(n11, createSwimLaneDescriptor(2));
185       // g12
186       swimLaneNm.set(n13, createSwimLaneDescriptor(3));
187       swimLaneNm.set(n14, createSwimLaneDescriptor(3));
188       swimLaneNm.set(n15, createSwimLaneDescriptor(4));
189       swimLaneNm.set(n16, createSwimLaneDescriptor(3));
190       swimLaneNm.set(n17, createSwimLaneDescriptor(5));
191       // g18
192       swimLaneNm.set(n19, createSwimLaneDescriptor(8));
193       swimLaneNm.set(n20, createSwimLaneDescriptor(7));
194       swimLaneNm.set(n21, createSwimLaneDescriptor(7));
195       swimLaneNm.set(n22, createSwimLaneDescriptor(7));
196       swimLaneNm.set(n23, createSwimLaneDescriptor(7));
197 
198       initLabels(graph, swimLaneNm);
199 
200       layout();
201     }
202 
203     view.fitContent();
204     view.getGraph2D().updateViews();
205   }
206 
207   /**
208    * Updates node labels to display either group or folder state or
209    * for normal nodes the <code>String</code> representation of the client
210    * object of the swim lane to which they are assigned.
211    */
212   private void initLabels( final Graph2D graph, final NodeMap node2sld ) {
213     int grp = 0;
214     int fldr = 0;
215     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
216       if (hierarchy.isNormalNode(nc.node())) {
217         final SwimLaneDescriptor sld =
218                 (SwimLaneDescriptor) node2sld.get(nc.node());
219         if (sld != null) {
220           graph.getRealizer(nc.node()).setLabelText(
221                   String.valueOf(sld.getClientObject()));
222         }
223       } else if (hierarchy.isGroupNode(nc.node())) {
224         graph.getRealizer(nc.node()).setLabelText(
225                 "Group " + (++grp));
226       } else if (hierarchy.isFolderNode(nc.node())) {
227         graph.getRealizer(nc.node()).setLabelText(
228                 "Folder " + (++fldr));
229       }
230     }
231   }
232 
233   /**
234    * Updates node labels of normal nodes to display the computed index (+1)
235    * of the swim lane to which they are assigned.
236    */
237   private void initLabelsByIndex( final Graph2D graph, final NodeMap node2sld ) {
238     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
239       if (!hierarchy.isNormalNode(nc.node())) {
240         continue;
241       }
242 
243       final SwimLaneDescriptor sld = (SwimLaneDescriptor) node2sld.get(nc.node());
244       graph.getRealizer(nc.node()).setLabelText(
245               sld != null ? String.valueOf(sld.getComputedLaneIndex() + 1) : "");
246     }
247   }
248 
249   /**
250    * Factory method that returns a suitable swim lane descriptor map for
251    * the specified graph.
252    */
253   private NodeMap createSwimLaneMap( final Graph2D graph ) {
254     final DataProvider swimLaneDp =
255             graph.getDataProvider(
256                     IncrementalHierarchicLayouter.SWIMLANE_DESCRIPTOR_DPKEY);
257     if (swimLaneDp instanceof NodeMap) {
258       return (NodeMap)swimLaneDp;
259     } else {
260       final NodeMap swimLaneNm = graph.createNodeMap();
261       graph.addDataProvider(
262               IncrementalHierarchicLayouter.SWIMLANE_DESCRIPTOR_DPKEY,
263               swimLaneNm);
264       return swimLaneNm;
265     }
266   }
267 
268   /**
269    * Factory method for configured <code>SwimLaneDescriptor</code>s.
270    */
271   private SwimLaneDescriptor createSwimLaneDescriptor( final int i ) {
272     final Integer key = new Integer(i);
273     final SwimLaneDescriptor sld = new SwimLaneDescriptor(key);
274     sld.setLeftLaneInset(5);
275     sld.setRightLaneInset(5);
276     return sld;
277   }
278 
279   /**
280    * Determines a suitable swim lane for the specified coordinate.
281    * If the specified set of swim lane descriptors is empty, a new
282    * swim lane descriptor is created and returned.
283    */
284   private SwimLaneDescriptor closestSwimLane(
285           final double x,
286           final TreeSet swimLanes
287   ) {
288     if (swimLanes.isEmpty()) {
289       return createSwimLaneDescriptor(1);
290     }
291 
292     final SwimLaneDescriptor first = (SwimLaneDescriptor) swimLanes.first();
293     final SwimLaneDescriptor last = (SwimLaneDescriptor) swimLanes.last();
294 
295     SwimLaneDescriptor result = null;
296     if (x < first.getComputedLanePosition()) {
297       final int id = ((Integer) first.getClientObject()).intValue() - 1;
298       result = createSwimLaneDescriptor(id);
299     } else if (x > last.getComputedLanePosition() + last.getComputedLaneWidth()) {
300       final int id = ((Integer) last.getClientObject()).intValue() + 1;
301       result = createSwimLaneDescriptor(id);
302     } else {
303       for (Iterator it = swimLanes.iterator(); it.hasNext();) {
304         final SwimLaneDescriptor sld = (SwimLaneDescriptor) it.next();
305         if (sld.getComputedLanePosition() <= x &&
306             x < sld.getComputedLanePosition() + sld.getComputedLaneWidth()) {
307           result = sld;
308           break;
309         }
310       }
311     }
312 
313     return result;
314   }
315 
316   private SwimLaneDescriptor getMinimalSLD( final Node node ) {
317     if (hierarchy.isGroupNode(node)) {
318       SwimLaneDescriptor sld = null;
319       for (NodeCursor nc = hierarchy.getChildren(node); nc.ok(); nc.next()) {
320         final SwimLaneDescriptor _sld = getMinimalSLD(nc.node());
321         if (sld == null) {
322           if (_sld != null) {
323             sld = _sld;
324           }
325         } else {
326           if (_sld != null && sld.compareTo(_sld) > 0) {
327             sld = _sld;
328           }
329         }
330       }
331       return sld;
332     } else {
333       return (SwimLaneDescriptor)createSwimLaneMap(view.getGraph2D()).get(node);
334     }
335   }
336 
337 
338   /*
339    * #####################################################################
340    * overriden methods
341    * #####################################################################
342    */
343 
344   void layoutIncrementally() {
345     super.layoutIncrementally();
346     swimLaneDrawable.updateLanes();
347     view.getGraph2D().updateViews();
348   }
349 
350   void layout() {
351     super.layout();
352     swimLaneDrawable.updateLanes();
353     view.getGraph2D().updateViews();
354   }
355 
356   protected void openFolder( final Node folderNode ) {
357     createSwimLaneMap(view.getGraph2D()).set(folderNode, null);
358     super.openFolder(folderNode);
359   }
360 
361   protected void closeGroup( final Node groupNode ) {
362     createSwimLaneMap(view.getGraph2D()).set(groupNode, getMinimalSLD(groupNode));
363     super.closeGroup(groupNode);
364   }
365 
366   public void navigateToInnerGraph( final Node folderNode ) {
367   }
368 
369   public void navigateToParentGraph() {
370   }
371 
372 
373   /*
374    * #####################################################################
375    * GUI
376    * #####################################################################
377    */
378 
379   protected JToolBar createToolBar() {
380     final JToolBar jtb = super.createToolBar();
381     for (int i = jtb.getComponentCount(), n = i - 4; i --> n;) {
382       jtb.remove(i);
383     }
384     jtb.addSeparator();
385     jtb.add(new AbstractAction("New Layout") {
386       public void actionPerformed( ActionEvent e ) {
387         layout();
388       }
389     });
390     return jtb;
391   }
392 
393   protected EditMode createEditMode() {
394     final EditMode oldMode = super.createEditMode();
395     final EditMode newMode = new SwimLaneHierarchyEditMode();
396     newMode.assignNodeLabel(false);
397     newMode.setPopupMode(oldMode.getPopupMode());
398     return newMode;
399   }
400 
401   void configureRealizers( final Graph2D graph ) {
402     final ShapeNodeRealizer snr = new ShapeNodeRealizer();
403     snr.setShapeType(ShapeNodeRealizer.ROUND_RECT);
404     snr.setFillColor(new Color(255, 204, 0));
405 
406     graph.setDefaultNodeRealizer(snr);
407 
408     final DefaultHierarchyGraphFactory hgf =
409             (DefaultHierarchyGraphFactory) hierarchy.getGraphFactory();
410 
411     final NodeRealizer gnr = hgf.getDefaultGroupNodeRealizer();
412     gnr.setFillColor(new Color(202, 236, 255, 132));
413     gnr.getLabel().setBackgroundColor(new Color(153, 204, 255, 204));
414     if (gnr instanceof ShapeNodeRealizer) {
415       ((ShapeNodeRealizer) gnr).setShapeType(ShapeNodeRealizer.ROUND_RECT);
416     }
417     final NodeRealizer fnr = hgf.getDefaultFolderNodeRealizer();
418     fnr.setFillColor(new Color(242, 240, 216, 204));
419     fnr.getLabel().setBackgroundColor(new Color(183, 182, 158, 204));
420     if (fnr instanceof ShapeNodeRealizer) {
421       ((ShapeNodeRealizer) fnr).setShapeType(ShapeNodeRealizer.ROUND_RECT);
422     }
423   }
424 
425 
426   /*
427    * #####################################################################
428    * main
429    * #####################################################################
430    */
431 
432   public static void main( String[] args ) {
433     initLnF();
434     (new SwimLaneGroupDemo()).start();
435   }
436 
437 
438   /**
439    * <code>ViewMode</code> that reassigns nodes to new swim lanes upon
440    * node drag operations and triggers an incremental layout calculation.
441    */
442   class SwimLaneViewMode extends ViewMode {
443     private boolean dragging;
444     private boolean hasHitNodes;
445 
446     SwimLaneViewMode() {
447       this.dragging = false;
448       this.hasHitNodes = false;
449     }
450 
451     public void mouseDraggedLeft( final double x, final double y ) {
452       dragging = true;
453     }
454 
455     public void mousePressedLeft( final double x, final double y ) {
456       final HitInfo info = new HitInfo(view, x, y, true, HitInfo.NODE);
457       hasHitNodes = info.hasHitNodes();
458     }
459 
460     public void mouseReleasedLeft( final double x, final double y ) {
461       if (dragging && hasHitNodes) {
462         final Graph2D graph = view.getGraph2D();
463 
464         final TreeSet swimLanes = new TreeSet();
465         final NodeMap node2sld = createSwimLaneMap(graph);
466         for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
467           final SwimLaneDescriptor sld = (SwimLaneDescriptor) node2sld.get(nc.node());
468           if (sld != null) {
469             swimLanes.add(sld);
470           }
471         }
472 
473         if (assignSwimLanes(graph, graph.selectedNodes(), swimLanes, node2sld)) {
474           layoutIncrementally();
475           initLabelsByIndex(graph, node2sld);
476         }
477       }
478       hasHitNodes = false;
479       dragging = false;
480     }
481 
482     private boolean assignSwimLanes(
483             final Graph2D graph,
484             final NodeCursor nodes,
485             final TreeSet swimLanes,
486             final NodeMap node2sld
487     ) {
488       boolean dirty = false;
489       for (; nodes.ok(); nodes.next()) {
490         final Node node = nodes.node();
491         if (hierarchy.isGroupNode(node)) {
492           dirty |= assignSwimLanes(
493                   graph, hierarchy.getChildren(node), swimLanes, node2sld);
494         } else {
495           final SwimLaneDescriptor sld =
496                   closestSwimLane(graph.getCenterX(node), swimLanes);
497           if (sld != null) {
498             node2sld.set(node, sld);
499             dirty = true;
500           }
501         }
502       }
503       return dirty;
504     }
505   }
506 
507   /**
508    * <code>EditMode</code> extension that assigns newly created nodes to an
509    * appropriate swim lane and triggers an incremental layout calculation.
510    */
511   class SwimLaneHierarchyEditMode extends HierarchyEditMode {
512     protected Node createNode( final Graph2D graph, final double x, final double y ) {
513       final Node node = super.createNode(graph, x, y);
514       final TreeSet swimLanes = new TreeSet();
515       final NodeMap node2sld = createSwimLaneMap(graph);
516       for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
517         final SwimLaneDescriptor sld = (SwimLaneDescriptor) node2sld.get(nc.node());
518         if (sld != null) {
519           swimLanes.add(sld);
520         }
521       }
522 
523       final SwimLaneDescriptor sld = closestSwimLane(x, swimLanes);
524       if (sld != null) {
525         graph.setSelected(node, true);
526         node2sld.set(node, sld);
527         layoutIncrementally();
528         graph.setSelected(node, false);
529         initLabelsByIndex(graph, node2sld);
530       }
531 
532       return node;
533     }
534   }
535 }
536