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 y.view.Graph2DLayoutExecutor;
32  import y.view.Graph2D;
33  import y.view.EdgeRealizer;
34  import y.view.ViewMode;
35  import y.view.LineType;
36  import y.layout.hierarchic.IncrementalHierarchicLayouter;
37  import y.layout.hierarchic.incremental.SimplexNodePlacer;
38  import y.layout.Layouter;
39  import y.layout.LayoutGraph;
40  import y.base.EdgeCursor;
41  import y.base.Edge;
42  import y.base.EdgeMap;
43  import y.base.Node;
44  import y.base.NodeList;
45  import y.base.EdgeList;
46  import y.option.OptionHandler;
47  import y.option.OptionItem;
48  import y.option.TableEditorFactory;
49  import y.option.Editor;
50  import y.util.Maps;
51  import y.util.GraphHider;
52  import y.algo.ShortestPaths;
53  import y.algo.Paths;
54  import y.algo.Cycles;
55  
56  import javax.swing.BorderFactory;
57  import javax.swing.JToolBar;
58  import javax.swing.AbstractAction;
59  import javax.swing.JComponent;
60  import javax.swing.JSplitPane;
61  import java.awt.event.ActionEvent;
62  import java.awt.event.MouseEvent;
63  import java.awt.EventQueue;
64  import java.awt.Color;
65  import java.awt.Dimension;
66  import java.awt.BorderLayout;
67  import java.beans.PropertyChangeListener;
68  import java.beans.PropertyChangeEvent;
69  import java.util.Locale;
70  
71  /**
72   * This demo presents the critical path feature of the hierarchic layouter. The layouter tries to vertically align each node pair
73   * that is connected by an edge marked as "critical". This feature can be utilized to highlight different edge paths that are relevant for a user.
74   * <p>
75   * The demo allows to manually mark/unmark critical edges by selecting some edges and, then, pressing button "Mark Selected Edges"/"Unmark Selected Edges".
76   * Critical edges are colored red, common edges are colored black. The current state of selected edges can be toggled by double-clicking.
77   * </p><p>
78   * Pressing the "Apply Layout" button calculates a new layout of the current graph.
79   * </p><p>
80   * Pressing button "Mark Longest Path" allows to automatically select all edges that belong to a longest path of the graph.
81   * If two nodes of the graph are marked as selected, pressing button "Mark Path Between Two Nodes" selects all edges
82   * of the shortest-path between this nodes.
83   * </p>
84   *
85  
86   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/incremental_hierarchical_layouter#incremental_hierarchical_critical_paths" target="_blank">Section Emphasizing Critical Paths</a> in the yFiles for Java Developer's Guide
87   */
88  public class CriticalPathDemo extends DemoBase {
89    private static final Color COLOR_CRITICAL_EDGE = Color.RED;
90    private static final Color COLOR_COMMON_EDGE = Color.BLACK;
91  
92    private boolean backloopRoutingEnabled;
93    private boolean edgeStraighteningOptimizationEnabled;
94    private boolean useOrthogonalEdgeRoutes;
95    private int minimalNodeDistance;
96    private int minimalLayerDistance;
97  
98    public CriticalPathDemo() {
99      this(null);
100   }
101 
102   public CriticalPathDemo(final String helpFilePath) {
103     final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createOptionTable(createOptionHandler()),
104         view);
105     splitPane.setBorder(BorderFactory.createEmptyBorder());
106     contentPane.add(splitPane, BorderLayout.CENTER);
107     addHelpPane(helpFilePath);
108     loadInitialGraph();
109   }
110 
111   /** Adds an extra layout action to the toolbar */
112   protected JToolBar createToolBar() {
113     JToolBar bar = super.createToolBar();   
114     bar.addSeparator();
115     bar.add(createActionControl(new LayoutAction()));
116     bar.addSeparator();
117     bar.add(new MarkSelectionAction());
118     bar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
119     bar.add(new UnmarkSelectionAction());
120     bar.addSeparator();
121     bar.add(new MarkLongestPath());
122     bar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
123     bar.add(new MarkShortestPathBetweenNodes());
124     return bar;
125   }
126 
127   private void loadInitialGraph() {
128     loadGraph("resource/critical_path.graphml");    
129   }
130 
131 
132   protected OptionHandler createOptionHandler() {
133     final OptionHandler layoutOptionHandler = new OptionHandler("Option Table");
134 
135     minimalLayerDistance = 60;
136     OptionItem minimalLayerDistanceItem = layoutOptionHandler.addInt("Minimal Layer Distance", minimalLayerDistance);
137     minimalLayerDistanceItem.addPropertyChangeListener(new PropertyChangeListener() {
138       public void propertyChange(PropertyChangeEvent evt) {
139         minimalLayerDistance = layoutOptionHandler.getInt("Minimal Layer Distance");
140       }
141     });
142 
143     minimalNodeDistance = 30;
144     OptionItem minimalNodeDistanceItem = layoutOptionHandler.addInt("Minimal Node Distance", minimalNodeDistance);
145     minimalNodeDistanceItem.addPropertyChangeListener(new PropertyChangeListener() {
146       public void propertyChange(PropertyChangeEvent evt) {
147         minimalNodeDistance = layoutOptionHandler.getInt("Minimal Node Distance");
148       }
149     });
150 
151     useOrthogonalEdgeRoutes = true;
152     OptionItem useOrthogonalEdgeRoutesItem = layoutOptionHandler.addBool("Use Orthogonal Edge Routes", useOrthogonalEdgeRoutes);
153     useOrthogonalEdgeRoutesItem.addPropertyChangeListener(new PropertyChangeListener() {
154       public void propertyChange(PropertyChangeEvent evt) {
155         useOrthogonalEdgeRoutes = layoutOptionHandler.getBool("Use Orthogonal Edge Routes");
156       }
157     });
158 
159     backloopRoutingEnabled = true;
160     OptionItem backloopRoutingEnabledItem = layoutOptionHandler.addBool("Enable Backloop Routing", backloopRoutingEnabled);
161     backloopRoutingEnabledItem.addPropertyChangeListener(new PropertyChangeListener() {
162       public void propertyChange(PropertyChangeEvent evt) {
163         backloopRoutingEnabled = layoutOptionHandler.getBool("Enable Backloop Routing");
164       }
165     });
166 
167     edgeStraighteningOptimizationEnabled = true;
168     OptionItem edgeStraighteningOptimizationEnabledItem = layoutOptionHandler.addBool("Enable Edge Straightening", edgeStraighteningOptimizationEnabled);
169     edgeStraighteningOptimizationEnabledItem.addPropertyChangeListener(new PropertyChangeListener() {
170       public void propertyChange(PropertyChangeEvent evt) {
171         edgeStraighteningOptimizationEnabled = layoutOptionHandler.getBool("Enable Edge Straightening");
172       }
173     });
174 
175     return layoutOptionHandler;
176   }
177 
178   class LayoutAction extends AbstractAction {
179     LayoutAction() {
180       super("Layout", SHARED_LAYOUT_ICON);
181     }
182 
183     public void actionPerformed(ActionEvent e) {
184       //determine critical edges
185       Graph2D g = view.getGraph2D();
186       EdgeMap edge2CriticalValue = Maps.createHashedEdgeMap();
187       for (EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
188         Edge edge = ec.edge();        
189         if (isCritical(edge, g)) {
190           edge2CriticalValue.setDouble(edge, 1.0);
191         } else {
192           edge2CriticalValue.setDouble(edge, 0.0);
193         }
194       }
195 
196       //register critical edges
197       g.addDataProvider(IncrementalHierarchicLayouter.CRITICAL_EDGE_DPKEY, edge2CriticalValue);
198 
199       try {
200         Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
201         executor.getLayoutMorpher().setSmoothViewTransform(true);
202         executor.getLayoutMorpher().setEasedExecution(true);
203         executor.doLayout(view, getHierarchicLayouter());
204       } finally {
205 
206         g.removeDataProvider(IncrementalHierarchicLayouter.CRITICAL_EDGE_DPKEY);
207       }
208     }
209   }
210 
211   private void markAsCriticalEdge(Edge e, Graph2D g) {
212     EdgeRealizer eRealizer = g.getRealizer(e);
213     eRealizer.setLineColor(COLOR_CRITICAL_EDGE);
214     eRealizer.setLineType(LineType.LINE_2);
215   }
216 
217   private void unmarkEdge(Edge e,Graph2D g) {
218     EdgeRealizer eRealizer = g.getRealizer(e);
219     eRealizer.setLineColor(COLOR_COMMON_EDGE);
220     eRealizer.setLineType(LineType.LINE_1);
221   }
222 
223   private boolean isCritical(Edge e, Graph2D g) {
224     EdgeRealizer eRealizer = g.getRealizer(e);
225     return (eRealizer.getLineColor() == COLOR_CRITICAL_EDGE);
226   }
227 
228   class MarkSelectionAction extends AbstractAction {
229     MarkSelectionAction() {
230       super(" Mark Selected Edges ");
231     }
232 
233     public void actionPerformed(ActionEvent e) {
234       Graph2D g = view.getGraph2D();
235       g.firePreEvent();
236       g.backupRealizers();
237       try {
238         for (EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
239           Edge edge = ec.edge();
240           if (g.isSelected(edge)) {
241             markAsCriticalEdge(edge, g);
242           }
243         }
244         g.updateViews();
245       } finally {
246         g.firePostEvent();
247       }
248     }
249   }
250 
251   class UnmarkSelectionAction extends AbstractAction {
252     UnmarkSelectionAction() {
253       super(" Unmark Selected Edges ");
254     }
255 
256     public void actionPerformed(ActionEvent e) {
257       Graph2D g = view.getGraph2D();
258       g.firePreEvent();
259       g.backupRealizers();
260       try {
261         for (EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
262           Edge edge = ec.edge();
263           if (g.isSelected(edge)) {
264             unmarkEdge(edge, g);
265           }
266         }
267         g.updateViews();
268       } finally {
269         g.firePostEvent();
270       }
271     }
272   }
273 
274   class MarkShortestPathBetweenNodes extends AbstractAction {
275     MarkShortestPathBetweenNodes() {
276       super(" Mark Path Between Two Nodes ");
277     }
278 
279     public void actionPerformed(ActionEvent ae) {
280       Graph2D g = view.getGraph2D();
281       g.firePreEvent();
282       g.backupRealizers();
283       try {
284         NodeList selectedNodes = new NodeList(g.selectedNodes());
285         if (!selectedNodes.isEmpty()) {
286           EdgeMap path = Maps.createHashedEdgeMap();
287           Node n1 = selectedNodes.firstNode();
288           Node n2 = selectedNodes.lastNode();
289           ShortestPaths.findShortestUniformPaths(g, n1, n2, true, path);
290           if (!foundPath(g, path)) {
291             ShortestPaths.findShortestUniformPaths(g, n2, n1, true, path);
292           }
293           for (EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
294             Edge e = ec.edge();
295             if (path.getBool(e)) {
296               markAsCriticalEdge(e, g);
297             } else {
298               unmarkEdge(e, g);
299             }
300           }
301           g.updateViews();
302         }
303       } finally {
304         g.firePostEvent();
305       }
306     }
307   }
308 
309   class MarkLongestPath extends AbstractAction {
310     MarkLongestPath() {
311       super(" Mark Longest Path ");
312     }
313 
314     public void actionPerformed(ActionEvent ae) {
315       Graph2D g = view.getGraph2D();
316       g.firePreEvent();
317       g.backupRealizers();
318       try {
319         //reset marks
320         for (EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
321           unmarkEdge(ec.edge(), g);
322         }
323 
324         //make acyclic
325         final EdgeMap cycleEdges = Maps.createIndexEdgeMap(new boolean[g.E()]);
326         Cycles.findCycleEdges(g, cycleEdges);
327         GraphHider hider = new GraphHider(g);
328         hider.hide(new EdgeList(g.edges(), cycleEdges));
329 
330         //mark edges of longest path
331         for (EdgeCursor ec = Paths.findLongestPath(g).edges(); ec.ok(); ec.next()) {
332           markAsCriticalEdge(ec.edge(), g);
333         }
334         hider.unhideAll();
335         g.updateViews();
336       } finally {
337         g.firePostEvent();
338       }
339     }
340   }
341 
342   private boolean foundPath(LayoutGraph g, EdgeMap path) {
343     for(EdgeCursor ec = g.edges(); ec.ok(); ec.next()) {
344       if(path.getBool(ec.edge())) {
345         return true;
346       }
347     }
348     return false;
349   }
350   
351   private JComponent createOptionTable(OptionHandler oh) {
352     //Create editor and add associate Option Handler with the editor
353     TableEditorFactory tef = new TableEditorFactory();
354     oh.setAttribute(TableEditorFactory.ATTRIBUTE_INFO_POSITION, TableEditorFactory.InfoPosition.NONE);
355     final Editor editor = tef.createEditor(oh);
356 
357     JComponent optionPane = editor.getComponent();
358     optionPane.setPreferredSize(new Dimension(200, 100));
359     optionPane.setMinimumSize(new Dimension(200, 100));
360     optionPane.setMaximumSize(new Dimension(250, 100));
361     return optionPane;
362   }
363 
364   private Layouter getHierarchicLayouter() {
365     IncrementalHierarchicLayouter layouter = new IncrementalHierarchicLayouter();
366     layouter.setBackloopRoutingEnabled(backloopRoutingEnabled);
367     if(layouter.getNodePlacer() instanceof SimplexNodePlacer) {
368       ((SimplexNodePlacer) layouter.getNodePlacer()).setEdgeStraighteningOptimizationEnabled(edgeStraighteningOptimizationEnabled);
369     }
370     layouter.setOrthogonallyRouted(useOrthogonalEdgeRoutes);
371     layouter.setMinimumLayerDistance(minimalLayerDistance);
372     layouter.setNodeToNodeDistance(minimalNodeDistance);
373     return layouter;
374   }
375 
376    protected void registerViewModes() {
377     super.registerViewModes();
378     view.addViewMode(new ViewMode() {
379       /** A mouse button get clicked */
380       public void mouseClicked(MouseEvent e) {
381         super.mouseClicked(e);
382         Graph2D g = view.getGraph2D();
383         if (e.getClickCount() == 2) {
384           final EdgeCursor selectedEdges = g.selectedEdges();
385           if (selectedEdges != null && selectedEdges.size() > 0) {
386             //Toggle color for all selected edges
387             for (; selectedEdges.ok(); selectedEdges.next()) {              
388               if(isCritical(selectedEdges.edge(), g)) {
389                 unmarkEdge(selectedEdges.edge(), g);
390               } else {
391                 markAsCriticalEdge(selectedEdges.edge(), g);
392               }
393             }
394           }
395           view.updateView();
396         }
397       }
398     });
399   }
400 
401   /** Launches this demo. */
402   public static void main(String[] args) {
403     EventQueue.invokeLater(new Runnable() {
404       public void run() {
405         Locale.setDefault(Locale.ENGLISH);
406         initLnF();
407         (new CriticalPathDemo("resource/criticalpathhelp.html")).start("Critical Path Demo");
408       }
409     });
410   }
411 }
412