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.hierarchic;
15  
16  import demo.view.DemoBase;
17  import demo.view.DemoDefaults;
18  
19  import y.base.DataMap;
20  import y.base.Node;
21  import y.base.NodeCursor;
22  import y.base.NodeList;
23  import y.base.NodeMap;
24  import y.layout.Layouter;
25  import y.layout.hierarchic.IncrementalHierarchicLayouter;
26  import y.layout.hierarchic.incremental.IncrementalHintsFactory;
27  import y.layout.hierarchic.incremental.NodeLayoutDescriptor;
28  import y.layout.hierarchic.incremental.SimplexNodePlacer;
29  import y.layout.hierarchic.incremental.SwimLaneDescriptor;
30  import y.util.Maps;
31  import y.view.Arrow;
32  import y.view.EdgeRealizer;
33  import y.view.EditMode;
34  import y.view.Graph2D;
35  import y.view.PopupMode;
36  
37  import javax.swing.AbstractAction;
38  import javax.swing.JMenu;
39  import javax.swing.JPopupMenu;
40  import javax.swing.JToolBar;
41  import java.awt.Cursor;
42  import java.awt.EventQueue;
43  import java.awt.event.ActionEvent;
44  import java.util.Locale;
45  
46  /**
47   * <p>
48   * This simple demo shows how to use the swim lane feature of the 
49   * new {@link y.layout.hierarchic.IncrementalHierarchicLayouter}.
50   * </p>
51   * <p>
52   * It can either calculate a new layout or calculate a new layout given the current 
53   * sketch or incrementally layout selected nodes to an already existing graph whose
54   * layout is read from the current sketch. 
55   * </p>
56   * <p>
57   * Things to try:<br/>
58   * Create a graph and assign nodes to layers by editing the label of the nodes.
59   * Nodes with the same layer will be placed into the same swim lane. Swim lanes
60   * are sorted from left to right in ascending label order.
61   * <br/>
62   * Use the <b>Layout</b> button to lay it out from scratch.
63   * Modify the graph (move nodes and or bends), deselect all elements and 
64   * choose <b>Layout from Sketch</b> to recalculate the layout using the given sketch
65   * Add some nodes and connect them to the graph, select the newly added nodes
66   * and choose <b>Layout Incrementally</b> to incrementally "add" the selected 
67   * elements optimally into the existing graph.
68   * </p>
69   */
70  public class SimpleSwimlaneLayouterDemo extends DemoBase {
71    /** Used to store the hints for the incremental layout */
72    private DataMap hintMap;
73  
74    /** Used to store the swim lane information for each node */
75    private NodeMap swimLaneMap;
76  
77    /** the layouter and the hints factory */
78    private IncrementalHierarchicLayouter hierarchicLayouter;
79    private IncrementalHintsFactory hintsFactory;
80  
81    /** the drawable for the swimlanes */
82    private SwimlaneDrawable swimLaneDrawable;
83  
84    public SimpleSwimlaneLayouterDemo() {
85      final Graph2D graph = view.getGraph2D();
86      EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
87      defaultER.setArrow(Arrow.STANDARD);
88  
89      // create a map to store the hints for the incremental layout mechanism
90      hintMap = Maps.createHashedDataMap();
91      swimLaneMap = graph.createNodeMap();
92      graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, hintMap);
93      graph.addDataProvider(IncrementalHierarchicLayouter.SWIMLANE_DESCRIPTOR_DPKEY, swimLaneMap);
94  
95      // create the layouter
96      hierarchicLayouter = new IncrementalHierarchicLayouter();
97  
98      // set some defaults
99      hierarchicLayouter.getEdgeLayoutDescriptor().setMinimumFirstSegmentLength(15);
100     hierarchicLayouter.getEdgeLayoutDescriptor().setMinimumLastSegmentLength(20);
101     hierarchicLayouter.getEdgeLayoutDescriptor().setOrthogonallyRouted(true);
102     hierarchicLayouter.getEdgeLayoutDescriptor().setMinimumDistance(10.0d);
103 
104     hierarchicLayouter.getNodeLayoutDescriptor().setLayerAlignment(0.5d);
105     hierarchicLayouter.setMinimumLayerDistance(30.0d);
106     hierarchicLayouter.getNodeLayoutDescriptor().setNodeLabelMode(
107         NodeLayoutDescriptor.NODE_LABEL_MODE_CONSIDER_FOR_DRAWING);
108     hierarchicLayouter.setConsiderNodeLabelsEnabled(true);
109 
110     // set the node placer to barycenter mode so that the results are centered
111     // nicely in the swimlanes if there is more room 
112     ((SimplexNodePlacer) hierarchicLayouter.getNodePlacer()).setBaryCenterModeEnabled(true);
113 
114     // get a reference to a hints factory
115     hintsFactory = hierarchicLayouter.createIncrementalHintsFactory();
116 
117     // disable the component layouter (optional)
118     hierarchicLayouter.setComponentLayouterEnabled(false);
119 
120     // add a drawable to visualize the swim lane geometry
121     view.addBackgroundDrawable(swimLaneDrawable = new SwimlaneDrawable(view.getGraph2D(), swimLaneMap));
122 
123     loadGraph("resource/swimlane.graphml");    
124     DemoDefaults.applyRealizerDefaults(graph);
125     
126     calcLayout(hierarchicLayouter);
127   }
128 
129   protected void configureDefaultRealizers() {
130     super.configureDefaultRealizers();
131     view.getGraph2D().getDefaultNodeRealizer().setSize(30, 30);
132   }
133   
134   class LayoutFromSketchAction extends AbstractAction {
135     LayoutFromSketchAction() {
136       super("Layout From Sketch", SHARED_LAYOUT_ICON);
137     }
138 
139     public void actionPerformed(ActionEvent ev) {
140       calcIncrementalLayout(new NodeList().nodes());
141     }
142   }
143 
144   class LayoutIncrementallyAction extends AbstractAction {
145     LayoutIncrementallyAction() {
146       super("Layout Incrementally", SHARED_LAYOUT_ICON);
147     }
148 
149     public void actionPerformed(ActionEvent ev) {
150       calcIncrementalLayout(view.getGraph2D().selectedNodes());
151     }
152   }
153 
154   class LayoutAction extends AbstractAction {
155     LayoutAction() {
156       super("Layout", SHARED_LAYOUT_ICON);
157     }
158 
159     public void actionPerformed(ActionEvent ev) {
160       calcFreshLayout();
161     }
162   }
163 
164   protected JToolBar createToolBar() {
165     JToolBar tb = super.createToolBar();
166     tb.addSeparator();
167     tb.add(createActionControl(new LayoutAction()));
168     tb.add(createActionControl(new LayoutFromSketchAction()));
169     tb.add(createActionControl(new LayoutIncrementallyAction()));
170     return tb;
171   }
172 
173   public void calcFreshLayout() {
174     hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
175     calcLayout(hierarchicLayouter);
176   }
177 
178   public void updateSwimLanes() {
179     for (NodeCursor nc = view.getGraph2D().nodes(); nc.ok(); nc.next()) {
180       SwimLaneDescriptor sld = new SwimLaneDescriptor(view.getGraph2D().getLabelText(nc.node()));
181       sld.setLeftLaneInset(5);
182       sld.setRightLaneInset(5);
183       sld.setMinimumLaneWidth(100);
184       swimLaneMap.set(nc.node(), sld);
185     }
186   }
187 
188   public void calcIncrementalLayout(NodeCursor incrementalNodes) {
189     try {
190       // mark nodes as "new"
191       for (incrementalNodes.toFirst(); incrementalNodes.ok(); incrementalNodes.next()) {
192         hintMap.set(incrementalNodes.node(), hintsFactory.createLayerIncrementallyHint(incrementalNodes.node()));
193       }
194       // read the old nodes from the sketch
195       hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
196       // calculate the layout incrementally
197       calcLayout(hierarchicLayouter);
198     } finally {
199       // reset the marks
200       for (incrementalNodes.toFirst(); incrementalNodes.ok(); incrementalNodes.next()) {
201         hintMap.set(incrementalNodes.node(), null);
202       }
203     }
204   }
205 
206   protected void calcLayout(Layouter layouter) {
207     Graph2D graph = view.getGraph2D();
208     if (!graph.isEmpty()) {
209       Cursor oldCursor = view.getCanvasComponent().getCursor();
210       try {
211         // associate swim lane descriptors with each node...
212         updateSwimLanes();
213         view.applyLayoutAnimated(layouter);
214       } finally {
215         view.getCanvasComponent().setCursor(oldCursor);
216       }
217     }
218     swimLaneDrawable.updateLanes();
219     view.fitContent();
220     view.updateView();
221   }
222 
223   protected EditMode createEditMode() {
224     EditMode editMode = super.createEditMode();
225     editMode.setPopupMode(new PopupMode() {
226       public JPopupMenu getNodePopup(Node v) {
227         return createPopup();
228       }
229 
230       private JPopupMenu createPopup() {
231         JPopupMenu menu = new JPopupMenu();
232         JMenu laneMenu = new JMenu("Move to lane");
233         menu.add(laneMenu);
234         laneMenu.add(new MoveSelectedNodesToLayerAction(1));
235         laneMenu.add(new MoveSelectedNodesToLayerAction(2));
236         laneMenu.add(new MoveSelectedNodesToLayerAction(3));
237         laneMenu.add(new MoveSelectedNodesToLayerAction(4));
238         laneMenu.add(new MoveSelectedNodesToLayerAction(5));
239         laneMenu.add(new MoveSelectedNodesToLayerAction(6));
240         return menu;
241       }
242 
243       public JPopupMenu getSelectionPopup(double x, double y) {
244         return createPopup();
245       }
246     });
247     return editMode;
248   }
249 
250 
251   /**
252    * Launches this demo.
253    */
254   public static void main(String[] args) {
255     EventQueue.invokeLater(new Runnable() {
256       public void run() {
257         Locale.setDefault(Locale.ENGLISH);
258         initLnF();
259         (new SimpleSwimlaneLayouterDemo()).start("Simple Swimlane Layout Demo");
260       }
261     });
262   }
263 
264   private class MoveSelectedNodesToLayerAction extends AbstractAction {
265     private int layer;
266 
267     MoveSelectedNodesToLayerAction(int layer) {
268       super(String.valueOf(layer));
269       this.layer = layer;
270     }
271 
272     public void actionPerformed(ActionEvent e) {
273       for (NodeCursor nodeCursor = view.getGraph2D().selectedNodes(); nodeCursor.ok(); nodeCursor.next()) {
274         Node node = nodeCursor.node();
275         view.getGraph2D().setLabelText(node, String.valueOf(layer));
276       }
277       calcIncrementalLayout(view.getGraph2D().selectedNodes());
278     }
279   }
280 }