1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.layout.hierarchic;
29  
30  import demo.view.DemoBase;
31  import demo.view.DemoDefaults;
32  
33  import y.base.DataMap;
34  import y.base.Node;
35  import y.base.NodeCursor;
36  import y.base.NodeList;
37  import y.base.NodeMap;
38  import y.layout.Layouter;
39  import y.layout.hierarchic.IncrementalHierarchicLayouter;
40  import y.layout.hierarchic.incremental.IncrementalHintsFactory;
41  import y.layout.hierarchic.incremental.NodeLayoutDescriptor;
42  import y.layout.hierarchic.incremental.RoutingStyle;
43  import y.layout.hierarchic.incremental.SimplexNodePlacer;
44  import y.layout.hierarchic.incremental.SwimLaneDescriptor;
45  import y.util.Maps;
46  import y.view.Arrow;
47  import y.view.EdgeRealizer;
48  import y.view.EditMode;
49  import y.view.Graph2D;
50  import y.view.PopupMode;
51  
52  import javax.swing.AbstractAction;
53  import javax.swing.JMenu;
54  import javax.swing.JPopupMenu;
55  import javax.swing.JToolBar;
56  import java.awt.Cursor;
57  import java.awt.EventQueue;
58  import java.awt.event.ActionEvent;
59  import java.util.Locale;
60  
61  /**
62   * <p>
63   * This simple demo shows how to use the swim lane feature of the 
64   * new {@link y.layout.hierarchic.IncrementalHierarchicLayouter}.
65   * </p>
66   * <p>
67   * It can either calculate a new layout or calculate a new layout given the current 
68   * sketch or incrementally layout selected nodes to an already existing graph whose
69   * layout is read from the current sketch.  
70   * </p>
71   * <p>
72   * Things to try:<br/>
73   * Create a graph and assign nodes to layers by editing the label of the nodes.
74   * Nodes with the same layer will be placed into the same swim lane. Swim lanes
75   * are sorted from left to right in ascending label order.
76   * <br/>
77   * Use the <b>Layout</b> button to lay it out from scratch.
78   * Modify the graph (move nodes and or bends), deselect all elements and 
79   * choose <b>Layout from Sketch</b> to recalculate the layout using the given sketch
80   * Add some nodes and connect them to the graph, select the newly added nodes
81   * and choose <b>Layout Incrementally</b> to incrementally "add" the selected 
82   * elements optimally into the existing graph.
83   * </p>
84   */
85  public class SimpleSwimlaneLayouterDemo extends DemoBase {
86    /** Used to store the hints for the incremental layout */
87    private DataMap hintMap;
88  
89    /** Used to store the swim lane information for each node */
90    private NodeMap swimLaneMap;
91  
92    /** the layouter and the hints factory */
93    private IncrementalHierarchicLayouter hierarchicLayouter;
94    private IncrementalHintsFactory hintsFactory;
95  
96    /** the drawable for the swimlanes */
97    private SwimlaneDrawable swimLaneDrawable;
98  
99    public SimpleSwimlaneLayouterDemo() {
100     this (null);
101   }
102 
103   public SimpleSwimlaneLayouterDemo(String helpFilePath) {
104     final Graph2D graph = view.getGraph2D();
105     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
106     defaultER.setArrow(Arrow.STANDARD);
107 
108     // create a map to store the hints for the incremental layout mechanism
109     hintMap = Maps.createHashedDataMap();
110     swimLaneMap = graph.createNodeMap();
111     graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, hintMap);
112     graph.addDataProvider(IncrementalHierarchicLayouter.SWIMLANE_DESCRIPTOR_DPKEY, swimLaneMap);
113 
114     // create the layouter
115     hierarchicLayouter = new IncrementalHierarchicLayouter();
116 
117     // set some defaults
118     hierarchicLayouter.getEdgeLayoutDescriptor().setMinimumFirstSegmentLength(15);
119     hierarchicLayouter.getEdgeLayoutDescriptor().setMinimumLastSegmentLength(20);
120     hierarchicLayouter.getEdgeLayoutDescriptor().setRoutingStyle(new RoutingStyle(RoutingStyle.EDGE_STYLE_OCTILINEAR));
121     hierarchicLayouter.getEdgeLayoutDescriptor().setMinimumDistance(10.0d);
122 
123     hierarchicLayouter.getNodeLayoutDescriptor().setLayerAlignment(0.5d);
124     hierarchicLayouter.setMinimumLayerDistance(30.0d);
125     hierarchicLayouter.getNodeLayoutDescriptor().setNodeLabelMode(
126         NodeLayoutDescriptor.NODE_LABEL_MODE_CONSIDER_FOR_DRAWING);
127     hierarchicLayouter.setConsiderNodeLabelsEnabled(true);
128     hierarchicLayouter.setBackloopRoutingEnabled(true);
129 
130     // set the node placer to barycenter mode so that the results are centered
131     // nicely in the swimlanes if there is more room 
132     ((SimplexNodePlacer) hierarchicLayouter.getNodePlacer()).setBaryCenterModeEnabled(false);
133 
134     // get a reference to a hints factory
135     hintsFactory = hierarchicLayouter.createIncrementalHintsFactory();
136 
137     // disable the component layouter (optional)
138     hierarchicLayouter.setComponentLayouterEnabled(false);
139 
140     // add a drawable to visualize the swim lane geometry
141     view.addBackgroundDrawable(swimLaneDrawable = new SwimlaneDrawable(view.getGraph2D(), swimLaneMap));
142 
143     loadGraph("resource/swimlane.graphml");    
144     DemoDefaults.applyRealizerDefaults(graph);
145     
146     addHelpPane(helpFilePath);
147 
148     calcLayout(hierarchicLayouter);
149   }
150 
151   protected void configureDefaultRealizers() {
152     super.configureDefaultRealizers();
153     view.getGraph2D().getDefaultNodeRealizer().setSize(30, 30);
154   }
155   
156   class LayoutFromSketchAction extends AbstractAction {
157     LayoutFromSketchAction() {
158       super("Layout From Sketch", SHARED_LAYOUT_ICON);
159     }
160 
161     public void actionPerformed(ActionEvent ev) {
162       calcIncrementalLayout(new NodeList().nodes());
163     }
164   }
165 
166   class LayoutIncrementallyAction extends AbstractAction {
167     LayoutIncrementallyAction() {
168       super("Layout Incrementally", SHARED_LAYOUT_ICON);
169     }
170 
171     public void actionPerformed(ActionEvent ev) {
172       calcIncrementalLayout(view.getGraph2D().selectedNodes());
173     }
174   }
175 
176   class LayoutAction extends AbstractAction {
177     LayoutAction() {
178       super("Layout", SHARED_LAYOUT_ICON);
179     }
180 
181     public void actionPerformed(ActionEvent ev) {
182       calcFreshLayout();
183     }
184   }
185 
186   protected JToolBar createToolBar() {
187     JToolBar tb = super.createToolBar();
188     tb.addSeparator();
189     tb.add(createActionControl(new LayoutAction()));
190     tb.add(createActionControl(new LayoutFromSketchAction()));
191     tb.add(createActionControl(new LayoutIncrementallyAction()));
192     return tb;
193   }
194 
195   public void calcFreshLayout() {
196     hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
197     calcLayout(hierarchicLayouter);
198   }
199 
200   public void updateSwimLanes() {
201     for (NodeCursor nc = view.getGraph2D().nodes(); nc.ok(); nc.next()) {
202       SwimLaneDescriptor sld = new SwimLaneDescriptor(view.getGraph2D().getLabelText(nc.node()));
203       sld.setLeftLaneInset(5);
204       sld.setRightLaneInset(5);
205       sld.setMinimumLaneWidth(100);
206       swimLaneMap.set(nc.node(), sld);
207     }
208   }
209 
210   public void calcIncrementalLayout(NodeCursor incrementalNodes) {
211     try {
212       // mark nodes as "new"
213       for (incrementalNodes.toFirst(); incrementalNodes.ok(); incrementalNodes.next()) {
214         hintMap.set(incrementalNodes.node(), hintsFactory.createLayerIncrementallyHint(incrementalNodes.node()));
215       }
216       // read the old nodes from the sketch
217       hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
218       // calculate the layout incrementally
219       calcLayout(hierarchicLayouter);
220     } finally {
221       // reset the marks
222       for (incrementalNodes.toFirst(); incrementalNodes.ok(); incrementalNodes.next()) {
223         hintMap.set(incrementalNodes.node(), null);
224       }
225     }
226   }
227 
228   protected void calcLayout(Layouter layouter) {
229     Graph2D graph = view.getGraph2D();
230     if (!graph.isEmpty()) {
231       Cursor oldCursor = view.getViewCursor();
232       try {
233         // associate swim lane descriptors with each node...
234         updateSwimLanes();
235         view.applyLayoutAnimated(layouter);
236       } finally {
237         view.setViewCursor(oldCursor);
238       }
239     }
240     swimLaneDrawable.updateLanes();
241     view.fitContent();
242     view.updateView();
243   }
244 
245   protected EditMode createEditMode() {
246     EditMode editMode = super.createEditMode();
247     editMode.setPopupMode(new PopupMode() {
248       public JPopupMenu getNodePopup(Node v) {
249         return createPopup();
250       }
251 
252       private JPopupMenu createPopup() {
253         JPopupMenu menu = new JPopupMenu();
254         JMenu laneMenu = new JMenu("Move to lane");
255         menu.add(laneMenu);
256         laneMenu.add(new MoveSelectedNodesToLayerAction(1));
257         laneMenu.add(new MoveSelectedNodesToLayerAction(2));
258         laneMenu.add(new MoveSelectedNodesToLayerAction(3));
259         laneMenu.add(new MoveSelectedNodesToLayerAction(4));
260         laneMenu.add(new MoveSelectedNodesToLayerAction(5));
261         laneMenu.add(new MoveSelectedNodesToLayerAction(6));
262         return menu;
263       }
264 
265       public JPopupMenu getSelectionPopup(double x, double y) {
266         return createPopup();
267       }
268     });
269     return editMode;
270   }
271 
272 
273   /**
274    * Launches this demo.
275    */
276   public static void main(String[] args) {
277     EventQueue.invokeLater(new Runnable() {
278       public void run() {
279         Locale.setDefault(Locale.ENGLISH);
280         initLnF();
281         (new SimpleSwimlaneLayouterDemo("resource/simpleswimlanelayouterdemohelp.html")).start("Simple Swimlane Layout Demo");
282       }
283     });
284   }
285 
286   private class MoveSelectedNodesToLayerAction extends AbstractAction {
287     private int layer;
288 
289     MoveSelectedNodesToLayerAction(int layer) {
290       super(String.valueOf(layer));
291       this.layer = layer;
292     }
293 
294     public void actionPerformed(ActionEvent e) {
295       for (NodeCursor nodeCursor = view.getGraph2D().selectedNodes(); nodeCursor.ok(); nodeCursor.next()) {
296         Node node = nodeCursor.node();
297         view.getGraph2D().setLabelText(node, String.valueOf(layer));
298       }
299       calcIncrementalLayout(view.getGraph2D().selectedNodes());
300     }
301   }
302 }