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.hierarchy.GroupingDemo;
31  import y.base.DataMap;
32  import y.base.EdgeCursor;
33  import y.base.Node;
34  import y.base.NodeCursor;
35  import y.base.NodeList;
36  import y.layout.hierarchic.IncrementalHierarchicLayouter;
37  import y.layout.hierarchic.incremental.IncrementalHintsFactory;
38  import y.option.ConstraintManager;
39  import y.option.OptionHandler;
40  import y.option.OptionItem;
41  import y.util.Maps;
42  import y.view.Graph2D;
43  import y.view.Graph2DLayoutExecutor;
44  import y.view.Graph2DViewActions;
45  
46  import javax.swing.AbstractAction;
47  import javax.swing.Action;
48  import javax.swing.JLabel;
49  import javax.swing.JToolBar;
50  import java.awt.EventQueue;
51  import java.awt.event.ActionEvent;
52  import java.awt.event.ActionListener;
53  import java.util.Locale;
54  
55  
56  /**
57   * This demo showcases how IncrementalHierarchicLayouter can be used to fully or incrementally
58   * layout hierarchically nested graphs. The demo supports automatic relayout after expanding folder nodes,
59   * collapsing group nodes. Furthermore it provides toolbar buttons that
60   * trigger full layout and incremental relayout. A settings dialog for group layout options is provided as well.
61   * In incremental layout mode all selected elements are added incrementally to the existing layout.
62   *
63   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/incremental_hierarchical_layouter#ihl_hierarchically_organized_graphs" target="_blank">Section Layout of Grouped Graphs</a> in the yFiles for Java Developer's Guide
64   */
65  public class IncrementalHierarchicGroupDemo extends GroupingDemo {
66  
67    IncrementalHierarchicLayouter layouter;
68    OptionHandler groupLayoutOptions;
69  
70    public IncrementalHierarchicGroupDemo() {
71  
72      //configure layout algorithm
73      layouter = new IncrementalHierarchicLayouter();
74      layouter.setOrthogonallyRouted(true);
75      layouter.setRecursiveGroupLayeringEnabled(false);
76  
77      //prepare option handler for group layout options
78      Object[] groupStrategyEnum = {"Global Layering", "Recursive Layering"};
79      Object[] groupAlignmentEnum = {"Top", "Center", "Bottom"};
80      groupLayoutOptions = new OptionHandler("Layout Properties");
81      ConstraintManager cm = new ConstraintManager(groupLayoutOptions);
82      OptionItem gsi = groupLayoutOptions.addEnum("Group Layering Strategy", groupStrategyEnum, 0);
83      OptionItem eci = groupLayoutOptions.addBool("Enable Compact Layering", true);
84      OptionItem gai = groupLayoutOptions.addEnum("Group Alignment", groupAlignmentEnum, 0);
85      cm.setEnabledOnValueEquals(gsi, "Recursive Layering", eci);
86      cm.setEnabledOnValueEquals(gsi, "Recursive Layering", gai);
87      cm.setEnabledOnCondition(cm.createConditionValueEquals(gsi, "Recursive Layering").and(
88          cm.createConditionValueEquals(eci, Boolean.TRUE).inverse()), gai);
89  
90      view.fitContent();
91    }
92  
93    /**
94     * Register custom open folder and close group actions that adjust the layout of the graph.
95     */
96    protected void registerViewActions() {
97      super.registerViewActions();
98      view.getCanvasComponent().getActionMap().put(Graph2DViewActions.CLOSE_GROUPS, new CloseGroupsAndLayoutAction());
99      view.getCanvasComponent().getActionMap().put(Graph2DViewActions.OPEN_FOLDERS, new OpenFoldersAndLayoutAction());
100   }
101 
102   /**
103    * Expand a folder node. After expanding the folder node, an incremental layout is automatically triggered.
104    * For this, the expanded node and all of its descendants will be treated as incremental elements.
105    */
106   class OpenFoldersAndLayoutAction extends Graph2DViewActions.OpenFoldersAction {
107 
108     OpenFoldersAndLayoutAction() {
109       super(IncrementalHierarchicGroupDemo.this.view);
110     }
111 
112     public void openFolder(Node folderNode, Graph2D graph) {
113       NodeList children = new NodeList(graph.getHierarchyManager().getInnerGraph(folderNode).nodes());
114       super.openFolder(folderNode, graph);
115 
116       // create storage for both nodes and edges
117       DataMap incrementalElements = Maps.createHashedDataMap();
118       // configure the mode
119       final IncrementalHintsFactory ihf = layouter.createIncrementalHintsFactory();
120 
121       for (NodeCursor nc = children.nodes(); nc.ok(); nc.next()) {
122         incrementalElements.set(nc.node(), ihf.createLayerIncrementallyHint(nc.node()));
123       }
124 
125       layoutIncrementally(incrementalElements);
126 
127       graph.setSelected(folderNode, true);
128       graph.updateViews();
129      
130     }
131   }
132 
133   /**
134    * Collapse a group node. After collapsing the group node, an incremental layout is automatically triggered.
135    * For this, the collapsed node is treated as an incremental element.
136    */
137   class CloseGroupsAndLayoutAction extends Graph2DViewActions.CloseGroupsAction {
138 
139     CloseGroupsAndLayoutAction() {
140       super(IncrementalHierarchicGroupDemo.this.view);
141     }
142 
143     public void closeGroup(Node groupNode, Graph2D graph) {
144       super.closeGroup(groupNode, graph);
145 
146       // create storage for both nodes and edges
147       DataMap incrementalElements = Maps.createHashedDataMap();
148       // configure the mode
149       final IncrementalHintsFactory ihf = layouter.createIncrementalHintsFactory();
150 
151       for (EdgeCursor ec = groupNode.edges(); ec.ok(); ec.next()) {
152         incrementalElements.set(ec.edge(), ihf.createSequenceIncrementallyHint(ec.edge()));
153       }
154 
155       layoutIncrementally(incrementalElements);
156 
157       graph.updateViews();      
158     }
159   }
160   
161   
162   /**
163    * Loads the initial graph
164    */
165   protected void loadInitialGraph() {
166     loadGraph("resource/IncrementalHierarchicGroupDemo.graphml");
167   }
168 
169   /**
170    * Creates the toolbar for the demo.
171    */
172   protected JToolBar createToolBar() {
173     JToolBar toolBar = super.createToolBar();    
174     addLayoutActions(toolBar);    
175     return toolBar;
176   }
177 
178   protected void addLayoutActions(JToolBar toolBar) {
179     final Action incrementalLayoutAction = new AbstractAction(
180             "Incremental", SHARED_LAYOUT_ICON) {
181       public void actionPerformed(ActionEvent e) {
182         layoutIncrementally();
183       }
184     };
185 
186     final Action layoutAction = new AbstractAction(
187             "Complete", SHARED_LAYOUT_ICON) {
188       public void actionPerformed(ActionEvent e) {
189         layout();
190       }
191     };
192 
193     final Action propertiesAction = new AbstractAction(
194             "Settings...", getIconResource("resource/properties.png")) {
195       public void actionPerformed(ActionEvent e) {
196         final ActionListener layoutListener = new ActionListener() {
197           public void actionPerformed(ActionEvent e) {
198             layout();
199           }
200         };
201         OptionSupport.showDialog(groupLayoutOptions, layoutListener, false, view.getFrame());
202         configureGroupLayout();
203       }
204     };
205 
206     toolBar.addSeparator();
207     toolBar.add(new JLabel("Layout: "));
208     toolBar.add(createActionControl(incrementalLayoutAction));
209     toolBar.add(createActionControl(layoutAction));
210     toolBar.add(createActionControl(propertiesAction));
211   }
212 
213   /**
214    * Configures the layouter options relevant for grouping.
215    */
216   void configureGroupLayout() {
217     Object gsi = groupLayoutOptions.get("Group Layering Strategy");
218     if ("Recursive Layering".equals(gsi)) {
219       layouter.setRecursiveGroupLayeringEnabled(true);
220     } else if ("Global Layering".equals(gsi)) {
221       layouter.setRecursiveGroupLayeringEnabled(false);
222     }
223 
224     layouter.setGroupCompactionEnabled(groupLayoutOptions.getBool("Enable Compact Layering"));
225 
226     Object gai = groupLayoutOptions.get("Group Alignment");
227     if ("Top".equals(gai)) {
228       layouter.setGroupAlignmentPolicy(IncrementalHierarchicLayouter.POLICY_ALIGN_GROUPS_TOP);
229     } else if ("Center".equals(gai)) {
230       layouter.setGroupAlignmentPolicy(IncrementalHierarchicLayouter.POLICY_ALIGN_GROUPS_CENTER);
231     }
232     if ("Bottom".equals(gai)) {
233       layouter.setGroupAlignmentPolicy(IncrementalHierarchicLayouter.POLICY_ALIGN_GROUPS_BOTTOM);
234     }
235   }
236 
237   /**
238    * Performs incremental layout. All selected elements will be treated incrementally.
239    */
240   void layoutIncrementally() {
241     Graph2D graph = view.getGraph2D();
242 
243     // create storage for both nodes and edges
244     DataMap incrementalElements = Maps.createHashedDataMap();
245     // configure the mode
246     final IncrementalHintsFactory ihf = layouter.createIncrementalHintsFactory();
247 
248     for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
249       incrementalElements.set(nc.node(), ihf.createLayerIncrementallyHint(nc.node()));
250     }
251 
252     for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
253       incrementalElements.set(ec.edge(), ihf.createSequenceIncrementallyHint(ec.edge()));
254     }
255 
256     layoutIncrementally(incrementalElements);
257   }
258 
259   /**
260    * Performs incremental layout. The given data map indicates the elements to treat incrementally.
261    */
262   void layoutIncrementally(DataMap incrementalElements) {
263     Graph2D graph = view.getGraph2D();
264 
265     layouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
266 
267     graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, incrementalElements);
268     try {
269       final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor();
270       layoutExecutor.getLayoutMorpher().setSmoothViewTransform(true);
271       layoutExecutor.doLayout(view, layouter);
272     } finally {
273       graph.removeDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY);
274     }
275   }
276 
277 
278   /**
279    * Performs global layout. The new layout can strongly differ from the existing layout.
280    */
281   void layout() {
282     layouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
283     final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor();
284     layoutExecutor.getLayoutMorpher().setSmoothViewTransform(true);
285     layoutExecutor.doLayout(view, layouter);
286   }
287 
288   /**
289    * Launches this demo.
290    * @param args ignored.
291    */
292   public static void main(String[] args) {
293     EventQueue.invokeLater(new Runnable() {
294       public void run() {
295         Locale.setDefault(Locale.ENGLISH);
296         initLnF();
297         (new IncrementalHierarchicGroupDemo()).start();
298       }
299     });
300   }
301 }
302