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