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