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 y.base.Node;
31  import y.base.NodeMap;
32  import y.base.YList;
33  import y.base.NodeCursor;
34  import y.util.Comparators;
35  import y.view.Graph2D;
36  import y.view.NodeRealizer;
37  import y.view.Graph2DLayoutExecutor;
38  import y.view.Graph2DViewActions;
39  import y.view.hierarchy.GroupLayoutConfigurator;
40  import y.layout.grouping.RecursiveGroupLayouter;
41  import y.layout.grouping.Grouping;
42  import y.layout.Layouter;
43  import y.layout.PortCandidate;
44  import y.layout.LayoutOrientation;
45  import y.layout.LayoutMultiplexer;
46  import y.layout.LayoutGraph;
47  import y.layout.tree.TreeReductionStage;
48  import y.layout.tree.TreeLayouter;
49  import y.layout.hierarchic.IncrementalHierarchicLayouter;
50  import y.layout.hierarchic.GivenLayersLayerer;
51  import y.layout.hierarchic.incremental.NodeLayoutDescriptor;
52  import y.layout.hierarchic.incremental.HierarchicLayouter;
53  import y.layout.hierarchic.incremental.LayerConstraintFactory;
54  import y.util.DataProviderAdapter;
55  import y.util.DataProviders;
56  import y.util.Maps;
57  
58  import javax.swing.AbstractButton;
59  import javax.swing.Action;
60  import javax.swing.JToggleButton;
61  import javax.swing.JToolBar;
62  import javax.swing.AbstractAction;
63  import javax.swing.ActionMap;
64  import java.awt.event.ActionEvent;
65  import java.awt.EventQueue;
66  import java.util.Arrays;
67  import java.util.Comparator;
68  import java.util.Locale;
69  
70  import demo.view.hierarchy.GroupingDemo;
71  
72  /**
73   * Shows how to use the Recursive Group Layouter to apply distinct layout styles to different group nodes.
74   * <p/>
75   * Table-like Example. A table-like structure: each group represents a table, the regular nodes represent the table
76   * rows, and edges are connected to specific rows. The rows are sorted according to their y-coordinate in the initial
77   * drawing.
78   * <p/>
79   * Three-Tier Example. Distinct layouts of elements assigned to different tiers. Each group can be assigned to either
80   * the left, right or middle tier (depending on a group's label). '<code>left</code>' groups are drawn using a
81   * TreeLayouter with orientation left-to-right. Analogously, '<code>right</code>' groups are drawn using a TreeLayouter
82   * with orientation right-to-left. Elements not labeled 'left' or 'right' are laid out in the middle using a hierarchic
83   * layout with orientation left-to-right. Note that groups not labeled 'left' or 'right' are handled non-recursively.
84   */
85  public class MixedLayoutDemo extends GroupingDemo {
86    static final int TABLE_MODE = 0;
87    static final int THREE_TIER_MODE = 1;
88  
89    private static final byte COMMON_NODE = 0;
90    private static final byte LEFT_TREE_GROUP_NODE = 1;
91    private static final byte LEFT_TREE_CONTENT_NODE = 2;
92    private static final byte RIGHT_TREE_GROUP_NODE = 3;
93    private static final byte RIGHT_TREE_CONTENT_NODE = 4;
94  
95    int mode;
96    boolean fromSketch;
97  
98    public MixedLayoutDemo() {
99      this(null);
100   }
101 
102   public MixedLayoutDemo( final String helpFilePath ) {
103     super();
104     addHelpPane(helpFilePath);
105   }
106 
107   protected void loadInitialGraph() {
108     loadGraph("resource/threetier.graphml");
109   }
110 
111   protected String[] getExampleResources() {
112     return new String[]{"resource/threetier.graphml", "resource/table.graphml"};
113   }
114 
115   protected JToolBar createToolBar() {
116     final AbstractAction fromSketchAction = new AbstractAction("From Sketch") {
117       public void actionPerformed(ActionEvent e) {
118         fromSketch = ((AbstractButton) e.getSource()).isSelected();
119       }
120     };
121     fromSketchAction.putValue(Action.SHORT_DESCRIPTION, "Toggles the 'From Sketch' mode of the layout");
122 
123     JToolBar toolBar = super.createToolBar();
124     toolBar.addSeparator();
125     toolBar.add(createActionControl(new LayoutAction()));
126     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
127     toolBar.add(new JToggleButton(fromSketchAction));
128     return toolBar;
129   }
130 
131   protected void loadGraph(String resourceString) {
132     mode = "resource/threetier.graphml".equals(resourceString) ? THREE_TIER_MODE : TABLE_MODE;
133     super.loadGraph(resourceString);
134   }
135 
136   /**
137    * Register key bindings for our custom actions.
138    */
139   protected void registerViewActions() {
140     super.registerViewActions();
141 
142     ActionMap actionMap = view.getCanvasComponent().getActionMap();
143     actionMap.put(Graph2DViewActions.CLOSE_GROUPS, new MyCloseGroupsAction());
144     actionMap.put(Graph2DViewActions.OPEN_FOLDERS, new MyOpenFoldersAction());
145   }
146 
147   //action performs common behavior and applies a layout afterwards
148   class MyCloseGroupsAction extends Graph2DViewActions.CloseGroupsAction {
149     public void actionPerformed(ActionEvent e) {
150       super.actionPerformed(e);
151       doLayout();
152     }
153   }
154 
155   //action performs common behavior and applies a layout afterwards
156   class MyOpenFoldersAction extends Graph2DViewActions.OpenFoldersAction {
157     public void actionPerformed(ActionEvent e) {
158       super.actionPerformed(e);
159       doLayout();
160     }
161   }
162 
163   /**
164    * Layout action that configures and launches a layout algorithm.
165    */
166   class LayoutAction extends AbstractAction {
167     LayoutAction() {
168       super("Layout", SHARED_LAYOUT_ICON);
169     }
170 
171     public void actionPerformed(ActionEvent e) {
172       doLayout();
173     }
174   }
175 
176   private void doLayout() {
177     if (mode == THREE_TIER_MODE) {
178       applyThreeTierLayout();
179     } else {
180       applyTableLayout();
181     }
182   }
183 
184   /** Configures and invokes the table layout algorithm */
185   void applyTableLayout() {
186     Graph2D graph = view.getGraph2D();
187 
188     //set up port candidates for edges (edges should be attached to the left/right side of the corresponding nodes)
189     YList candidates = new YList();
190     candidates.add(PortCandidate.createCandidate(PortCandidate.WEST));
191     candidates.add(PortCandidate.createCandidate(PortCandidate.EAST));
192     graph.addDataProvider(PortCandidate.SOURCE_PCLIST_DPKEY, DataProviders.createConstantDataProvider(candidates));
193     graph.addDataProvider(PortCandidate.TARGET_PCLIST_DPKEY, DataProviders.createConstantDataProvider(candidates));
194 
195     //configure layout algorithms
196     final RowLayouter rowLayouter = new RowLayouter(); //used for layouting the nodes (rows) within the group nodes (tables)
197 
198     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter(); //used for the core layout
199     ihl.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
200     if (fromSketch) {
201       ihl.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
202     }
203     ihl.setOrthogonallyRouted(true);
204 
205     //map each group node to its corresponding layout algorithm
206     graph.addDataProvider(RecursiveGroupLayouter.GROUP_NODE_LAYOUTER_DPKEY, new DataProviderAdapter() {
207       public Object get(Object dataHolder) {
208         return rowLayouter;
209       }
210     });
211 
212     //prepare grouping information
213     GroupLayoutConfigurator glc = new GroupLayoutConfigurator(graph);
214     try {
215       glc.prepareAll();
216 
217       //do layout
218       RecursiveGroupLayouter rgl = new RecursiveGroupLayouter(ihl);
219       rgl.setAutoAssignPortCandidatesEnabled(true);
220       rgl.setConsiderSketchEnabled(true);
221       new Graph2DLayoutExecutor().doLayout(view, rgl);
222     } finally {
223       //dispose
224       glc.restoreAll();
225       graph.removeDataProvider(PortCandidate.SOURCE_PCLIST_DPKEY);
226       graph.removeDataProvider(PortCandidate.TARGET_PCLIST_DPKEY);
227       graph.removeDataProvider(LayoutMultiplexer.LAYOUTER_DPKEY);
228     }
229 
230     view.updateView();
231     view.fitContent();
232   }
233 
234   /**
235    * Determines the type of a node (used for the subgraph layout demo).
236    */
237   private static byte getType(Node n, Grouping grouping, Graph2D graph) {
238     if (grouping.isGroupNode(n)) {
239       NodeRealizer realizer = graph.getRealizer(n);
240       if ("left".equals(realizer.getLabelText())) {
241         return LEFT_TREE_GROUP_NODE;
242       } else if ("right".equals(realizer.getLabelText())) {
243         return RIGHT_TREE_GROUP_NODE;
244       } else {
245         return COMMON_NODE;
246       }
247     } else {
248       Node groupNode = grouping.getParent(n);
249       if (groupNode != null) {
250         NodeRealizer realizer = graph.getRealizer(groupNode);
251         if ("left".equals(realizer.getLabelText())) {
252           return LEFT_TREE_CONTENT_NODE;
253         } else if ("right".equals(realizer.getLabelText())) {
254           return RIGHT_TREE_CONTENT_NODE;
255         } else {
256           return COMMON_NODE;
257         }
258       } else {
259         NodeRealizer realizer = graph.getRealizer(n);
260         if ("left".equals(realizer.getLabelText())) {
261           return LEFT_TREE_GROUP_NODE;
262         } else if ("right".equals(realizer.getLabelText())) {
263           return RIGHT_TREE_GROUP_NODE;
264         } else {
265           return COMMON_NODE;
266         }
267       }
268     }
269   }
270 
271   /**
272    * Configures and invokes a layout algorithm
273    */
274   void applyThreeTierLayout() {
275     final Graph2D graph = view.getGraph2D();
276 
277     //configure the different layout settings
278     final TreeReductionStage leftToRightTreeLayouter = new TreeReductionStage();
279     leftToRightTreeLayouter.setNonTreeEdgeRouter(leftToRightTreeLayouter.createStraightlineRouter());
280     TreeLayouter tl1 = new TreeLayouter();
281     tl1.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
282     tl1.setLayoutStyle(TreeLayouter.ORTHOGONAL_STYLE);
283     leftToRightTreeLayouter.setCoreLayouter(tl1);
284 
285     final TreeReductionStage rightToLeftTreeLayouter = new TreeReductionStage();
286     rightToLeftTreeLayouter.setNonTreeEdgeRouter(rightToLeftTreeLayouter.createStraightlineRouter());
287     TreeLayouter tl2 = new TreeLayouter();
288     tl2.setLayoutOrientation(LayoutOrientation.RIGHT_TO_LEFT);
289     tl2.setLayoutStyle(TreeLayouter.ORTHOGONAL_STYLE);
290     rightToLeftTreeLayouter.setCoreLayouter(tl2);
291 
292     final IncrementalHierarchicLayouter partitionLayouter = new IncrementalHierarchicLayouter(); //configure the core layout
293     partitionLayouter.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
294     if (fromSketch) {
295       partitionLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
296     }
297 
298     GroupLayoutConfigurator glc = new GroupLayoutConfigurator(graph); //prepare the grouping information
299     glc.prepareAll();
300     final Grouping grouping = new Grouping(graph);
301 
302     if (!fromSketch) {
303       //insert layer constraints to guarantee the desired placement for "left" and "right" group nodes
304       LayerConstraintFactory lcf = partitionLayouter.createLayerConstraintFactory(graph);
305       for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
306         Node n = nc.node();
307         byte type = getType(n, grouping, graph);
308         if (type == LEFT_TREE_GROUP_NODE) {
309           lcf.addPlaceNodeAtTopConstraint(n);
310         } else if (type == RIGHT_TREE_GROUP_NODE) {
311           lcf.addPlaceNodeAtBottomConstraint(n);
312         }
313       }
314     }
315 
316     //align tree group nodes within their layer
317     NodeMap node2LayoutDescriptor = Maps.createHashedNodeMap();
318     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
319       Node n = nc.node();
320       byte type = getType(n, grouping, graph);
321       if (type == LEFT_TREE_GROUP_NODE) {
322         NodeLayoutDescriptor nld = new NodeLayoutDescriptor();
323         nld.setLayerAlignment(1.0d);
324         node2LayoutDescriptor.set(n, nld);
325       } else if (type == RIGHT_TREE_GROUP_NODE) {
326         NodeLayoutDescriptor nld = new NodeLayoutDescriptor();
327         nld.setLayerAlignment(0.0d);
328         node2LayoutDescriptor.set(n, nld);
329       }
330     }
331     graph.addDataProvider(HierarchicLayouter.NODE_LAYOUT_DESCRIPTOR_DPKEY, node2LayoutDescriptor);
332 
333     //map each group node to the layout algorithm that should be used for its content
334     graph.addDataProvider(RecursiveGroupLayouter.GROUP_NODE_LAYOUTER_DPKEY, new DataProviderAdapter() {
335       public Object get(Object dataHolder) {
336         byte type = getType((Node) dataHolder, grouping, graph);
337         if (type == LEFT_TREE_GROUP_NODE) {
338           return leftToRightTreeLayouter;
339         } else if (type == RIGHT_TREE_GROUP_NODE) {
340           return rightToLeftTreeLayouter;
341         } else {
342           return null; //handled non-recursive
343         }
344       }
345     });
346 
347     //each edge should be attached to the left or right side of the corresponding node
348     final YList candidates = new YList();
349     candidates.add(PortCandidate.createCandidate(PortCandidate.WEST));
350     candidates.add(PortCandidate.createCandidate(PortCandidate.EAST));
351     graph.addDataProvider(PortCandidate.SOURCE_PCLIST_DPKEY, DataProviders.createConstantDataProvider(candidates));
352     graph.addDataProvider(PortCandidate.TARGET_PCLIST_DPKEY, DataProviders.createConstantDataProvider(candidates));
353 
354     //launch layout algorithm
355     RecursiveGroupLayouter rgl = new RecursiveGroupLayouter(partitionLayouter);
356 
357     try {
358       rgl.setAutoAssignPortCandidatesEnabled(true);
359       rgl.setConsiderSketchEnabled(true);
360 
361       new Graph2DLayoutExecutor().doLayout(view, rgl);
362 
363     } finally {
364       //dispose
365       grouping.dispose();
366       glc.restoreAll();
367       graph.removeDataProvider(PortCandidate.SOURCE_PCLIST_DPKEY);
368       graph.removeDataProvider(PortCandidate.TARGET_PCLIST_DPKEY);
369       graph.removeDataProvider(LayoutMultiplexer.LAYOUTER_DPKEY);
370       graph.removeDataProvider(HierarchicLayouter.NODE_LAYOUT_DESCRIPTOR_DPKEY);
371       graph.removeDataProvider(GivenLayersLayerer.LAYER_ID_KEY);
372     }
373 
374     view.updateView();
375     view.fitContent();
376   }
377 
378   /**
379    * Layouts the nodes (rows) within the group nodes (tables).
380    */
381   static class RowLayouter implements Layouter {
382     private static final double DISTANCE = 5.0;
383 
384     public boolean canLayout(LayoutGraph graph) {
385       return graph.edgeCount() == 0;
386     }
387 
388     public void doLayout(final LayoutGraph graph) {
389       Node[] rows = graph.getNodeArray();
390       Arrays.sort(rows, new Comparator() {
391         public int compare(Object o1, Object o2) {
392           return Comparators.compare(graph.getCenterY((Node) o1), graph.getCenterY((Node) o2));
393         }
394       });
395 
396       double currentY = 0.0;
397       for (int i = 0; i < rows.length; i++) {
398         //set layout of row
399         graph.setLocation(rows[i], 0.0, currentY);
400         currentY += graph.getHeight(rows[i]) + DISTANCE;
401       }
402     }
403   }
404 
405   /**
406    * Launches this demo.
407    */
408   public static void main(String[] args) {
409     EventQueue.invokeLater(new Runnable() {
410       public void run() {
411         Locale.setDefault(Locale.ENGLISH);
412         initLnF();
413         (new MixedLayoutDemo("resource/mixedlayouthelp.html")).start();
414       }
415     });
416   }
417 }
418