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.hierarchic;
15  
16  import demo.view.DemoBase;
17  
18  import y.base.Node;
19  import y.base.NodeCursor;
20  import y.layout.hierarchic.IncrementalHierarchicLayouter;
21  import y.layout.hierarchic.incremental.SequenceConstraintFactory;
22  import y.util.Comparators;
23  import y.view.Graph2D;
24  import y.view.Graph2DView;
25  
26  import javax.swing.AbstractAction;
27  import javax.swing.Action;
28  import javax.swing.JToolBar;
29  import java.awt.event.ActionEvent;
30  import java.awt.EventQueue;
31  import java.util.ArrayList;
32  import java.util.Comparator;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Random;
38  
39  /**
40   * Demonstrates how to apply sequence constraints when calculating hierarchical
41   * layouts. For hierarchical layouts, a sequence is the in-layer order of nodes,
42   * e.g. with layout direction from top to bottom, a sequence is the left to
43   * right order of nodes.
44   *
45   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/incremental_hierarchical_layouter.html#incremental_hierarchical_node_order_constraints">Section Constrained Node Sequencing</a> in the yFiles for Java Developer's Guide
46   */
47  public class SequenceConstraintsDemo extends DemoBase {
48    /**
49     * Label text constant that marks a node to be the first one in its layer.
50     */
51    private static final String FIRST = "FIRST";
52  
53    /**
54     * Label text constant that marks a node to be the last one in its layer.
55     */
56    private static final String LAST = "LAST";
57  
58    private final Random rndm;
59  
60    public SequenceConstraintsDemo() {
61      rndm = new Random(0);
62      loadGraph("resource/SequenceConstraintsDemo.graphml");    
63    }
64  
65    /**
66     * Calculates a hierarchical layout for the specified graph.
67     * The layout algorithm will sequence nodes according to the
68     * lexicographical order of their labels. To enforce this in-layer order,
69     * sequence constraints are used.
70     *
71     * @param view   the <code>Graph2DView</code> to use
72     */
73    private void doLayout(final Graph2DView view) {
74      final Graph2D graph = view.getGraph2D();
75  
76      // classify nodes as "unlabeled", "labeled", FIRST, and LAST nodes
77      // for labeled nodes we will later assign constraints, that force
78      // the layout algorithm to order these nodes according to the
79      // lexicographical order of their labels
80      // unlabeled nodes may be sequenced as the layout algorithm deems
81      // appropriate
82      final List labeled = new ArrayList(graph.nodeCount());
83      final Map label2nodes = new HashMap();
84      final List firsts = new ArrayList(graph.nodeCount());
85      final List lasts = new ArrayList(graph.nodeCount());
86      for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
87        final String s = graph.getRealizer(nc.node()).getLabelText().trim();
88        if (s.length() > 0) {
89          if (FIRST.equalsIgnoreCase(s)) {
90            firsts.add(nc.node());
91          } else if (LAST.equalsIgnoreCase(s)) {
92            lasts.add(nc.node());
93          } else {
94            final String text = graph.getLabelText(nc.node());
95            labeled.add(nc.node());
96            ArrayList list = (ArrayList) label2nodes.get(text);
97            if(list == null){
98              list = new ArrayList();
99              label2nodes.put(text, list);
100           }
101           list.add(nc.node());
102         }
103       }
104     }
105 
106     // sort the labeled nodes to get an order that can be easily modeled
107     // with consecutive "place after" constraints
108     Comparators.sort(labeled, new Comparator() {
109       public int compare(final Object o1, final Object o2) {
110         final String s1 = graph.getRealizer(((Node) o1)).getLabelText();
111         final String s2 = graph.getRealizer(((Node) o2)).getLabelText();
112         return s1.compareTo(s2);
113       }
114     });
115 
116 
117     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
118     ihl.setOrthogonallyRouted(true);
119     
120     // create a constraint factory for our graph
121     final SequenceConstraintFactory scf = ihl.createSequenceConstraintFactory(graph);
122 
123     // create constraints for nodes with "normal" labels;
124     // these nodes shall be sequenced according to the lexicographical order
125     // of their labels
126     String leftConstraint = null;
127     for (int i = 1; i < labeled.size(); ++i) {
128 
129       String label0 = graph.getLabelText((Node) labeled.get(i - 1));
130       String label1 = graph.getLabelText((Node) labeled.get(i));
131 
132       if (!(label1).equals(label0)) {
133         leftConstraint = label0;
134       }
135 
136       if (leftConstraint != null) {
137         ArrayList lastNodes = (ArrayList) label2nodes.get(leftConstraint);
138         for (int j = 0; j < lastNodes.size(); j++) {
139           scf.addPlaceNodeAfterConstraint(
140               lastNodes.get(j), labeled.get(i));
141         }
142       }
143     }
144 
145     // create constraints for all nodes with FIRST labels;
146     // these nodes shall always be placed at the start of their layers
147     for (int i = 0, n = firsts.size(); i < n; ++i) {
148       scf.addPlaceNodeAtHeadConstraint(firsts.get(i));
149     }
150 
151     // create constraints for all nodes with LAST labels;
152     // these nodes shall always be placed at the end of their layers
153     for (int i = 0, n = lasts.size(); i < n; ++i) {
154       scf.addPlaceNodeAtTailConstraint(lasts.get(i));
155     }
156 
157     view.applyLayoutAnimated(ihl);
158 
159     // dispose the constraint factory to clear all previously specified
160     // constraints and prevent memory leaks
161     scf.dispose();
162   }
163 
164   /**
165    * Generates random node labels for the specified graph. Roughly 1/8
166    * of the nodes will be marked either as a <code>FIRST</code> or as a
167    * <code>LAST</code> node.
168    */
169   private void generateRandomLabels(final Graph2D graph) {
170     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
171       final String label;
172       if (rndm.nextDouble() < 0.125) {
173         label = rndm.nextDouble() < 0.5 ? FIRST : LAST;
174       } else {
175         final char[] chars = new char[rndm.nextInt(2)];
176         for (int i = 0; i < chars.length; ++i) {
177           chars[i] = (char) (rndm.nextInt(26) + (int) 'A');
178         }
179         label = new String(chars);
180       }
181       graph.getRealizer(nc.node()).setLabelText(label);
182     }
183   }
184 
185   protected JToolBar createToolBar() {
186     final JToolBar jtb = super.createToolBar();
187     jtb.addSeparator();
188     jtb.add(createActionControl(new AbstractAction(
189             "Layout", SHARED_LAYOUT_ICON) {
190       {
191         putValue(Action.SHORT_DESCRIPTION,
192             "<html><head></head><body>" +
193                 "Applies a hierarchical layout." +
194                 "<p>" +
195                 "Nodes will be sequenced according to the lexicographical" +
196                 " order of their labels." +
197                 "<br>" +
198                 "Nodes with empty labels will be sequenced as the layout" +
199                 " algorithm deems appropriate." +
200                 "<br>" +
201                 "Finally, nodes labeled <code>FIRST</code> or" +
202                 " <code>LAST</code> are sequenced at layer start or layer" +
203                 " end respectively." +
204                 "</p>" +
205                 "</body></html>");
206       }
207 
208       public void actionPerformed(final ActionEvent e) {
209         doLayout(view);
210         view.fitContent();
211         view.updateView();
212       }
213     }));
214 
215     jtb.addSeparator();
216     jtb.add(new AbstractAction("Random Labels") {
217       {
218         putValue(Action.SHORT_DESCRIPTION,
219             "<html><head></head><body>" +
220                 "Generates random node labels." +
221                 "<p>" +
222                 " Roughly 1/8 of the nodes will be marked either as a" +
223                 " <code>FIRST</code> or as a <code>LAST</code> node." +
224                 "</p>" +
225                 "</body></html>");
226       }
227 
228       public void actionPerformed(final ActionEvent e) {
229         generateRandomLabels(view.getGraph2D());
230         view.fitContent();
231         view.updateView();
232       }
233     });
234     return jtb;
235   }
236 
237   public static void main(String[] args) {
238     EventQueue.invokeLater(new Runnable() {
239       public void run() {
240         Locale.setDefault(Locale.ENGLISH);
241         initLnF();
242         (new SequenceConstraintsDemo()).start();
243       }
244     });
245   }
246 }
247