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.mixed;
29  
30  import demo.view.hierarchy.GroupingDemo;
31  import y.base.DataProvider;
32  import y.base.Edge;
33  import y.base.EdgeCursor;
34  import y.base.EdgeList;
35  import y.base.EdgeMap;
36  import y.layout.LayoutGraph;
37  import y.layout.LayoutTool;
38  import y.layout.Layouter;
39  import y.layout.ParallelEdgeLayouter;
40  import y.layout.circular.CircularLayouter;
41  import y.layout.grouping.RecursiveGroupLayouter;
42  import y.layout.hierarchic.IncrementalHierarchicLayouter;
43  import y.layout.organic.SmartOrganicLayouter;
44  import y.layout.orthogonal.OrthogonalGroupLayouter;
45  import y.layout.router.polyline.EdgeLayoutDescriptor;
46  import y.layout.router.polyline.EdgeRouter;
47  import y.util.DataProviderAdapter;
48  import y.view.Graph2D;
49  import y.view.Graph2DLayoutExecutor;
50  import y.view.Graph2DView;
51  import y.view.Graph2DViewActions;
52  
53  import javax.swing.AbstractAction;
54  import javax.swing.Action;
55  import javax.swing.ActionMap;
56  import javax.swing.JComboBox;
57  import javax.swing.JMenu;
58  import javax.swing.JToggleButton;
59  import javax.swing.JToolBar;
60  import java.awt.EventQueue;
61  import java.awt.event.ActionEvent;
62  import java.awt.event.ActionListener;
63  import java.util.Locale;
64  
65  /**
66   * Shows the Recursive Group Layouter. The content of each group node is recursively laid out with the specified
67   * layouter, i.e., the layouter is applied to each group node separately. Note that the
68   * {@link y.layout.grouping.RecursiveGroupLayouter}
69   * also supports to specify different layout algorithms for different group nodes, see {@link MixedLayoutDemo}.
70   */
71  public class RecursiveLayoutDemo extends GroupingDemo {
72    private static final int TYPE_ORTHOGONAL = 0;
73    private static final int TYPE_HIERARCHIC = 1;
74    private static final int TYPE_CIRCULAR = 2;
75    private static final int TYPE_ORGANIC = 3;
76  
77    private int layoutType;
78    private boolean useInterEdgeRouter;
79  
80    public RecursiveLayoutDemo() {
81      this(null);
82    }
83  
84    public RecursiveLayoutDemo(final String helpFilePath) {
85      super();
86      addHelpPane(helpFilePath);
87    }
88  
89    protected void loadInitialGraph() {
90      loadGraph("resource/recursive.graphml");
91    }
92  
93    /**
94     * Adds an extra layout action to the toolbar
95     */
96    protected JToolBar createToolBar() {
97      layoutType = TYPE_ORTHOGONAL;
98      final JComboBox layoutTypeSelection = new JComboBox(
99          new String[]{"Orthogonal Style", "Hierarchic Style", "Circular Style", "Organic Style"});
100     layoutTypeSelection.setSelectedIndex(layoutType);
101     layoutTypeSelection.setMaximumSize(layoutTypeSelection.getPreferredSize());
102     layoutTypeSelection.addActionListener(new ActionListener() {
103       public void actionPerformed(ActionEvent e) {
104         switch (layoutTypeSelection.getSelectedIndex()) {
105           default:
106           case 0:
107             layoutType = TYPE_ORTHOGONAL;
108             break;
109           case 1:
110             layoutType = TYPE_HIERARCHIC;
111             break;
112           case 2:
113             layoutType = TYPE_CIRCULAR;
114             break;
115           case 3:
116             layoutType = TYPE_ORGANIC;
117             break;
118         }
119         doLayout();
120       }
121     });
122 
123     useInterEdgeRouter = true;
124     final JToggleButton toggleRouteInterEdges = new JToggleButton("Inter-Edge Routing", useInterEdgeRouter);
125     toggleRouteInterEdges.addActionListener(new ActionListener() {
126       public void actionPerformed(ActionEvent e) {
127         useInterEdgeRouter = toggleRouteInterEdges.isSelected();
128         doLayout();
129       }
130     });
131 
132     final Action layoutAction = new AbstractAction(
133             "Layout", SHARED_LAYOUT_ICON) {
134       public void actionPerformed(ActionEvent e) {
135         doLayout();
136       }
137     };
138 
139     JToolBar toolBar = super.createToolBar();
140     toolBar.addSeparator();
141     toolBar.add(createActionControl(layoutAction));
142     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
143     toolBar.add(layoutTypeSelection);
144     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
145     toolBar.add(toggleRouteInterEdges);
146     return toolBar;
147   }
148 
149   /**
150    * Register key bindings for our custom actions.
151    */
152   protected void registerViewActions() {
153     super.registerViewActions();
154 
155     ActionMap actionMap = view.getCanvasComponent().getActionMap();
156     actionMap.put(Graph2DViewActions.CLOSE_GROUPS, new MyCloseGroupsAction(view));
157     actionMap.put(Graph2DViewActions.OPEN_FOLDERS, new MyOpenFoldersAction(view));
158   }
159 
160   /**
161    * Populates the "Grouping" menu with grouping specific actions.
162    */
163   protected void populateGroupingMenu(JMenu hierarchyMenu) {
164     // Predefined actions for open/close groups
165     registerAction(hierarchyMenu, Graph2DViewActions.CLOSE_GROUPS, true);
166     registerAction(hierarchyMenu, Graph2DViewActions.OPEN_FOLDERS, true);
167 
168     hierarchyMenu.addSeparator();
169 
170     // Predefined actions for group/fold/ungroup
171     registerAction(hierarchyMenu, Graph2DViewActions.GROUP_SELECTION, true);
172     registerAction(hierarchyMenu, Graph2DViewActions.UNGROUP_SELECTION, true);
173     registerAction(hierarchyMenu, Graph2DViewActions.FOLD_SELECTION, true);
174   }
175 
176   /**
177    * Performs the common behavior and applies a layout afterwards.
178    */
179   class MyCloseGroupsAction extends Graph2DViewActions.CloseGroupsAction {
180 
181     MyCloseGroupsAction(Graph2DView view) {
182       super(view);
183     }
184 
185     public void actionPerformed(ActionEvent e) {
186       super.actionPerformed(e);
187       doLayout();
188     }
189   }
190 
191   /**
192    * Performs the common behavior and applies a layout afterwards.
193    */
194   class MyOpenFoldersAction extends Graph2DViewActions.OpenFoldersAction {
195 
196     MyOpenFoldersAction(Graph2DView view) {
197       super(view);
198     }
199 
200     public void actionPerformed(ActionEvent e) {
201       super.actionPerformed(e);
202       doLayout();
203     }
204   }
205 
206   void doLayout() {
207     final RecursiveGroupLayouter rgl;
208     final Layouter coreLayout;
209 
210     if (layoutType == TYPE_ORTHOGONAL) {
211       coreLayout = new OrthogonalGroupLayouter();
212       rgl = createOrthogonalRecursiveGroupLayout(coreLayout, EdgeLayoutDescriptor.MONOTONIC_NONE);
213 
214     } else if (layoutType == TYPE_HIERARCHIC) {
215       final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
216       ihl.setOrthogonallyRouted(true);
217       coreLayout = ihl;
218       rgl = createOrthogonalRecursiveGroupLayout(coreLayout, EdgeLayoutDescriptor.MONOTONIC_VERTICAL);
219 
220     } else if (layoutType == TYPE_ORGANIC) {
221       final SmartOrganicLayouter sol = new SmartOrganicLayouter();
222       sol.setMultiThreadingAllowed(true);
223       coreLayout = sol;
224       rgl = createRecursiveGroupLayout(coreLayout);
225       rgl.setAutoAssignPortCandidatesEnabled(true);
226 
227     } else {
228       final CircularLayouter cl = new CircularLayouter();
229       cl.setParallelEdgeLayouter(createParallelEdgeLayouter());
230 
231       coreLayout = cl;
232       rgl = createRecursiveGroupLayout(coreLayout);
233     }
234 
235     final Graph2D graph = view.getGraph2D();
236     try {
237       // map each group node to its corresponding layout algorithm
238       graph.addDataProvider(RecursiveGroupLayouter.GROUP_NODE_LAYOUTER_DPKEY, new DataProviderAdapter() {
239         public Object get(Object dataHolder) {
240           return coreLayout;
241         }
242       });
243 
244       new Graph2DLayoutExecutor().doLayout(view, rgl);
245       view.fitContent();
246 
247     } finally {
248       graph.removeDataProvider(RecursiveGroupLayouter.GROUP_NODE_LAYOUTER_DPKEY);
249     }
250   }
251 
252   RecursiveGroupLayouter createOrthogonalRecursiveGroupLayout(Layouter coreLayout, final byte monotonicPathType) {
253     final RecursiveGroupLayouter rgl = new RecursiveGroupLayouter(coreLayout) {
254       protected void routeInterEdges(LayoutGraph graph, EdgeList interEdges) {
255         if (useInterEdgeRouter) {
256           DataProvider selectedEdges = graph.getDataProvider(Layouter.SELECTED_EDGES); //backup selected edges
257 
258           EdgeMap edge2IsInterEdge = graph.createEdgeMap();
259           for (EdgeCursor ec = interEdges.edges(); ec.ok(); ec.next()) {
260             edge2IsInterEdge.setBool(ec.edge(), true);
261           }
262           graph.addDataProvider(Layouter.SELECTED_EDGES, edge2IsInterEdge);
263 
264           //route inter-edges
265           EdgeRouter oer = createOrthogonalEdgeRouter();
266           if (monotonicPathType != EdgeLayoutDescriptor.MONOTONIC_NONE) {
267             oer.getDefaultEdgeLayoutDescriptor().setMonotonicPathRestriction(monotonicPathType);
268           }
269           oer.doLayout(graph);
270 
271           //restore originally selected edges
272           if (selectedEdges != null) {
273             graph.addDataProvider(Layouter.SELECTED_EDGES, selectedEdges);
274           } else {
275             graph.removeDataProvider(Layouter.SELECTED_EDGES);
276           }
277           graph.disposeEdgeMap(edge2IsInterEdge);
278         } else {
279           super.routeInterEdges(graph, interEdges);
280         }
281       }
282     };
283 
284     rgl.setConsiderSketchEnabled(true);
285 
286     return rgl;
287   }
288 
289   RecursiveGroupLayouter createRecursiveGroupLayout(Layouter coreLayout) {
290     RecursiveGroupLayouter rgl = new RecursiveGroupLayouter(coreLayout) {
291       protected void routeInterEdges(LayoutGraph graph, EdgeList interEdges) {
292         if (useInterEdgeRouter) {
293           //reset paths of inter-edges
294           EdgeMap edge2IsInterEdge = graph.createEdgeMap();
295           for (EdgeCursor ec = interEdges.edges(); ec.ok(); ec.next()) {
296             Edge e = ec.edge();
297             edge2IsInterEdge.setBool(e, true);
298             LayoutTool.resetPath(graph, e);
299           }
300 
301           //layout parallel edges
302           graph.addDataProvider(ParallelEdgeLayouter.SCOPE_DPKEY, edge2IsInterEdge);
303           createParallelEdgeLayouter().doLayout(graph);
304           graph.removeDataProvider(ParallelEdgeLayouter.SCOPE_DPKEY);
305           graph.disposeEdgeMap(edge2IsInterEdge);
306         } else {
307           super.routeInterEdges(graph, interEdges);
308         }
309       }
310     };
311 
312     rgl.setConsiderSketchEnabled(true);
313 
314     return rgl;
315   }
316 
317   static EdgeRouter createOrthogonalEdgeRouter() {
318     EdgeRouter oer = new EdgeRouter();
319     oer.setReroutingEnabled(true);
320     oer.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
321     return oer;
322   }
323 
324   static ParallelEdgeLayouter createParallelEdgeLayouter() {
325     ParallelEdgeLayouter pel = new ParallelEdgeLayouter();
326     pel.setLineDistance(10.0);
327     pel.setJoinEndsEnabled(true);
328     return pel;
329   }
330 
331   /**
332    * Launches this demo.
333    */
334   public static void main(String[] args) {
335     EventQueue.invokeLater(new Runnable() {
336       public void run() {
337         Locale.setDefault(Locale.ENGLISH);
338         initLnF();
339         new RecursiveLayoutDemo("resource/recursivelayouthelp.html").start();
340       }
341     });
342   }
343 }
344