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