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.DemoBase;
31  import demo.view.DemoDefaults;
32  
33  import y.base.Edge;
34  import y.base.EdgeCursor;
35  import y.base.EdgeMap;
36  import y.base.Graph;
37  import y.base.Node;
38  import y.base.NodeCursor;
39  import y.base.NodeMap;
40  import y.io.GraphMLIOHandler;
41  import y.io.graphml.KeyScope;
42  import y.io.graphml.KeyType;
43  import y.layout.hierarchic.IncrementalHierarchicLayouter;
44  import y.layout.hierarchic.TopologicalLayerer;
45  import y.layout.hierarchic.incremental.LayerConstraintFactory;
46  import y.layout.hierarchic.incremental.ConstraintIncrementalLayerer;
47  import y.util.GraphCopier;
48  import y.view.Arrow;
49  import y.view.EdgeRealizer;
50  import y.view.Graph2D;
51  import y.view.Graph2DClipboard;
52  
53  import javax.swing.AbstractAction;
54  import javax.swing.BorderFactory;
55  import javax.swing.JButton;
56  import javax.swing.JPanel;
57  import javax.swing.JScrollPane;
58  import javax.swing.JToolBar;
59  import java.awt.BorderLayout;
60  import java.awt.Color;
61  import java.awt.GridBagConstraints;
62  import java.awt.GridBagLayout;
63  import java.awt.Insets;
64  import java.awt.EventQueue;
65  import java.awt.event.ActionEvent;
66  import java.awt.event.ActionListener;
67  import java.util.HashMap;
68  import java.util.Locale;
69  import java.util.Map;
70  
71  /**
72   * Demo that shows how to apply layer constraints when calculating hierarchical layouts.
73   * <p> With the buttons on the left side of the GUI,
74   * various constraints can be set on the currently selected nodes (either absolute top/bottom level or relative layering
75   * constraints). The "Top-most"/"Bottom-most" buttons set absolute layering constraints, whereas the other buttons assign
76   * relative layering constraints. The top button ("Remove constraints") clears all constraints from the currently
77   * selected nodes.
78   * </p>
79   * <p>
80   * Additionally, a DataProvider is registered under the key <code>ConstraintIncrementalLayerer.EDGE_WEIGHTS_DPKEY</code>, and if a numeric edge label is set, that label gets set as
81   * value for that DataProvider.
82   * </p>
83   *
84   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/incremental_hierarchical_layouter#incremental_hierarchical_layer_assignment_constraints" target="_blank">Section Constrained Layer Assignment</a> in the yFiles for Java Developer's Guide
85   */
86  public class LayerConstraintsDemo extends DemoBase {
87  
88    public static final int TOP_LEVEL = 1;
89    public static final int BOTTOM_LEVEL = 5;
90  
91    private NodeMap levels;
92    private EdgeMap weights;
93  
94    public LayerConstraintsDemo() {
95      super();    
96      final Graph2D graph = view.getGraph2D();
97      EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
98      defaultER.setArrow(Arrow.STANDARD);
99  
100     levels = graph.createNodeMap();
101 
102     //additionally, register an edge weight map
103     weights = graph.createEdgeMap();
104     graph.addDataProvider(ConstraintIncrementalLayerer.EDGE_WEIGHTS_DPKEY, weights);
105 
106     JPanel left = new JPanel(new GridBagLayout());
107 
108     GridBagConstraints gbc = new GridBagConstraints();
109     gbc.fill = GridBagConstraints.BOTH;
110     gbc.anchor = GridBagConstraints.NORTHWEST;
111 
112     gbc.gridx = 0;
113     gbc.gridy = GridBagConstraints.RELATIVE;
114 
115     JPanel groupSpec;
116     groupSpec = new JPanel(new GridBagLayout());
117     groupSpec.setBorder(BorderFactory.createTitledBorder("Layering constraints"));
118 
119     // build the grouping mechanism
120 
121     Color[] groupColors = new Color[]{DemoDefaults.DEFAULT_CONTRAST_COLOR, Color.RED, Color.ORANGE, Color.yellow, new Color(204, 255, 0), Color.GREEN};
122     String[] groupLabels = new String[]{"Remove constraint", "Top-most level", "Above medium level", "Medium level",
123         "Below medium level",
124         "Bottom-most level"};
125     gbc.insets = new Insets(5, 0, 5, 0);
126     for (int i = 0; i < groupColors.length; i++) {
127       if (i > 1 && i < 5) {
128         gbc.insets = new Insets(0, 0, 0, 0);
129       } else {
130         gbc.insets = new Insets(5, 0, 5, 0);
131       }
132       GroupButton groupButton = new GroupButton(groupColors[i], groupLabels[i], i);
133       groupSpec.add(groupButton, gbc);
134     }
135     gbc.weightx = gbc.weighty = 1;
136     groupSpec.add(new JPanel(), gbc);
137     gbc.weightx = gbc.weighty = 0;
138     gbc.gridwidth = 2;
139     left.add(groupSpec, gbc);
140     gbc.gridwidth = 1;
141 
142     gbc.weighty = 1.0d;
143     left.add(new JPanel(), gbc);
144 
145     contentPane.add(new JScrollPane(left), BorderLayout.WEST);
146     
147     loadGraph("resource/LayerConstraintsDemo.graphml");
148   }
149     
150   protected GraphMLIOHandler createGraphMLIOHandler() {
151     GraphMLIOHandler ioh = super.createGraphMLIOHandler();
152     ioh.getGraphMLHandler().addOutputDataProvider("level", levels, KeyScope.NODE, KeyType.INT);
153     ioh.getGraphMLHandler().addInputDataAcceptor("level", levels, KeyScope.NODE, KeyType.INT);
154     return ioh;
155   }
156   
157   protected void configureDefaultRealizers() {
158     super.configureDefaultRealizers();
159     view.getGraph2D().getDefaultNodeRealizer().setFillColor(DemoDefaults.DEFAULT_CONTRAST_COLOR);
160   }
161 
162   protected JToolBar createToolBar() {
163     JToolBar toolBar = super.createToolBar();
164     toolBar.addSeparator();
165     toolBar.add(createActionControl(new LayoutAction()));
166     return toolBar;
167   }
168 
169   protected Graph2DClipboard getClipboard() {
170     final Graph2DClipboard clipboard = super.getClipboard();
171     clipboard.setCopyFactory(new LayerConstraintGraphCopyFactory(clipboard.getCopyFactory()));
172     return clipboard;
173   }
174 
175   /** this method assigns the level id and the corresponding color hint to the currently selected nodes */
176   protected void assignLevel(Color color, int index) {
177     Graph2D graph = view.getGraph2D();
178 
179     for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
180       Node n = nc.node();
181       // set the color hint
182       if (color != null) {
183         graph.getRealizer(n).setFillColor(color);
184       } else {
185         graph.getRealizer(n).setFillColor(graph.getDefaultNodeRealizer().getFillColor());
186       }
187       levels.setInt(n, index);
188     }
189     graph.updateViews();
190   }
191 
192   
193   // helper class
194   class GroupButton extends JButton implements ActionListener {
195     Color color;
196     int index;
197 
198     GroupButton(Color color, String groupLabel, int index) {
199       super("");
200       setText(groupLabel);
201       this.color = color;
202       this.index = index;
203       setBackground(color);
204       this.addActionListener(this);
205     }
206 
207     public void actionPerformed(ActionEvent e) {
208       LayerConstraintsDemo.this.assignLevel(color, index);
209     }
210   }
211 
212   class LayoutAction extends AbstractAction {
213     LayoutAction() {
214       super("Layout", SHARED_LAYOUT_ICON);
215     }
216 
217     public void actionPerformed(ActionEvent ev) {
218       Graph2D graph = view.getGraph2D();
219 
220 
221       doLayout(graph);
222       graph.updateViews();
223       view.fitContent();
224     }
225   }
226 
227   private void doLayout(Graph2D graph) {
228     IncrementalHierarchicLayouter hl = new IncrementalHierarchicLayouter();
229     hl.setFromScratchLayeringStrategy(IncrementalHierarchicLayouter.LAYERING_STRATEGY_HIERARCHICAL_TOPMOST);
230     hl.setOrthogonallyRouted(true);
231     LayerConstraintFactory lcf = hl.createLayerConstraintFactory(graph);
232     createConstraints(graph, lcf);
233 
234     TopologicalLayerer topologicalLayerer = new TopologicalLayerer();
235     topologicalLayerer.setRankingPolicy(TopologicalLayerer.NO_RERANKING);
236     hl.setFromScratchLayerer(topologicalLayerer);
237 
238     view.applyLayoutAnimated(hl);
239   }
240 
241   /**
242    * Assign constraints to nodes and edges
243    *
244    * @param graph
245    * @param cf
246    */
247   private void createConstraints(Graph2D graph, LayerConstraintFactory cf) {
248     Node flr, slr, thrdlr;
249     flr = slr = thrdlr = null;
250     for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
251       Node node = nodeCursor.node();
252       int index = levels.getInt(node);
253       switch (index) {
254         case TOP_LEVEL:
255           cf.addPlaceNodeAtTopConstraint(node);
256           break;
257         case BOTTOM_LEVEL:
258           cf.addPlaceNodeAtBottomConstraint(node);
259           break;
260         case TOP_LEVEL + 1:
261           if (flr == null) {
262             flr = node;
263           } else {
264             cf.addPlaceNodeInSameLayerConstraint(flr, node);
265           }
266           break;
267         case TOP_LEVEL + 2:
268           if (slr == null) {
269             slr = node;
270           } else {
271             cf.addPlaceNodeInSameLayerConstraint(slr, node);
272           }
273           break;
274         case TOP_LEVEL + 3:
275           if (thrdlr == null) {
276             thrdlr = node;
277           } else {
278             cf.addPlaceNodeInSameLayerConstraint(thrdlr, node);
279           }
280           break;
281         default:
282           break;
283       }
284       if (flr != null && slr != null) {
285         //place second layer below first layer
286         cf.addPlaceNodeBelowConstraint(flr, slr);
287       }
288       if (slr != null && thrdlr != null) {
289         //place thrd layer below second layer
290         cf.addPlaceNodeBelowConstraint(slr, thrdlr);
291       }
292       if (flr != null && thrdlr != null) {
293         //place first layer above 3rd layer
294         cf.addPlaceNodeAboveConstraint(thrdlr, flr);
295       }
296     }
297 
298     //assign weights from edge labels
299     for (EdgeCursor edgeCursor = graph.edges(); edgeCursor.ok(); edgeCursor.next()) {
300       Edge edge = edgeCursor.edge();
301       if (graph.getRealizer(edge).labelCount() > 0) {
302         String str = graph.getLabelText(edge);
303         try {
304           weights.setInt(edge, Integer.parseInt(str));
305         }
306         catch (NumberFormatException e) {
307           weights.setInt(edge, 1);
308         }
309       }
310       else {
311         weights.setInt(edge, 1);
312       }
313     }
314   }
315 
316 
317   /** Launches this demo. */
318   public static void main(String[] args) {
319     EventQueue.invokeLater(new Runnable() {
320       public void run() {
321         Locale.setDefault(Locale.ENGLISH);
322         initLnF();
323         (new LayerConstraintsDemo()).start("Layer Constraints Demo");
324       }
325     });
326   }
327 
328   /**
329    * This {@link y.util.GraphCopier.CopyFactory} handles the level of the nodes in the graphs for cut/copy/paste.
330    */
331   private class LayerConstraintGraphCopyFactory implements GraphCopier.CopyFactory {
332     private final GraphCopier.CopyFactory copyFactory;
333     private final HashMap node2level;
334 
335     public LayerConstraintGraphCopyFactory(GraphCopier.CopyFactory copyFactory) {
336       this.copyFactory = copyFactory;
337       node2level = new HashMap();
338     }
339 
340     public Node copyNode(Graph targetGraph, Node originalNode) {
341       return copyFactory.copyNode(targetGraph, originalNode);
342     }
343 
344     public Edge copyEdge(Graph targetGraph, Node newSource, Node newTarget, Edge originalEdge) {
345       return copyFactory.copyEdge(targetGraph, newSource, newTarget, originalEdge);
346     }
347 
348     public Graph createGraph() {
349       return copyFactory.createGraph();
350     }
351 
352     public void preCopyGraphData(Graph sourceGraph, Graph targetGraph) {
353       copyFactory.preCopyGraphData(sourceGraph, targetGraph);
354     }
355 
356     /**
357      * After copying the (sub-)graph, also the levels need to be stored/updated. That way, copies of nodes that
358      * have a certain layer constraint will get the same layer constraint.
359      */
360     public void postCopyGraphData(Graph sourceGraph, Graph targetGraph, Map nodeMap, Map edgeMap) {
361       copyFactory.postCopyGraphData(sourceGraph, targetGraph, nodeMap, edgeMap);
362 
363       // check if the source graph is the graph in the current view to see if it is a cut/copy or paste action
364       if (sourceGraph == view.getGraph2D()) {
365         // cut/copy
366         // store level information from the source nodes for the nodes in the copied subgraph
367         node2level.clear();
368         for (NodeCursor nodeCursor = sourceGraph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
369           final Node sourceNode = nodeCursor.node();
370           final Node targetNode = (Node) nodeMap.get(sourceNode);
371           if (targetNode != null) {
372             node2level.put(targetNode, levels.get(sourceNode));
373           }
374         }
375       } else {
376         // paste
377         // store level information of the source nodes for the nodes in the copied subgraph
378         for (NodeCursor nodeCursor = sourceGraph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
379           final Node sourceNode = nodeCursor.node();
380           final Node targetNode = (Node) nodeMap.get(sourceNode);
381           levels.set(targetNode, node2level.get(sourceNode));
382         }
383       }
384     }
385   }
386 
387 }
388