1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
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-2008 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.layout.hierarchic;
15  
16  import demo.view.DemoBase;
17  
18  import y.base.NodeCursor;
19  import y.base.Node;
20  import y.view.Graph2D;
21  import y.layout.hierarchic.IncrementalHierarchicLayouter;
22  import y.layout.hierarchic.incremental.SequenceConstraintFactory;
23  import y.layout.BufferedLayouter;
24  import y.io.GMLIOHandler;
25  
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Comparator;
29  import java.util.List;
30  import java.util.Random;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.net.URL;
34  import java.awt.event.ActionEvent;
35  import javax.swing.JToolBar;
36  import javax.swing.AbstractAction;
37  
38  /**
39   * Demonstrates how to apply sequence constraints when calculating hierarchical
40   * layouts. For hierarchical layouts, a sequence is the in-layer order of nodes,
41   * e.g. with layout direction from top to bottom, a sequence is the left to
42   * right order of nodes.
43   *
44   */
45  public class SequenceConstraintsDemo extends DemoBase {
46    /**
47     * Label text constant that marks a node as a head node, i.e. a node that
48     * should be placed at the start of its layer.
49     */
50    private static final String HEAD = "HEAD";
51    /**
52     * Label text constant that marks a node as a tail node, i.e. a node that
53     * should be placed at the end of its layer.
54     */
55    private static final String TAIL = "TAIL";
56  
57    private final Random rndm;
58  
59    public SequenceConstraintsDemo() {
60      rndm = new Random(666);
61      loadInitialGraph(view.getGraph2D());
62      generateRandomLabels(view.getGraph2D());
63      view.fitContent();
64      view.updateView();
65    }
66  
67    /**
68     * Calculates a hierarchical layout for the specified graph.
69     * The layout algorithm will sequence nodes according to the
70     * lexicographical order of their labels. To enforce this in-layer order,
71     * sequence constraints are used.
72     *
73     * @param graph   the <code>Graph2D</code> to laid out
74     */
75    private void doLayout( final Graph2D graph ) {
76      // classify nodes as "unlabeled", "labeled", HEAD, and TAIL 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 List heads = new ArrayList(graph.nodeCount());
84      final List tails = new ArrayList(graph.nodeCount());
85      for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
86        final String s = graph.getRealizer(nc.node()).getLabelText().trim();
87        if (s.length() > 0) {
88          if (HEAD.equalsIgnoreCase(s)) {
89            heads.add(nc.node());
90          } else if (TAIL.equalsIgnoreCase(s)) {
91            tails.add(nc.node());
92          } else {
93            labeled.add(nc.node());
94          }
95        }
96      }
97  
98      // sort the labeled nodes to get an order that can be easily modeled
99      // with consecutive "place after" constraints
100     Collections.sort(labeled, new Comparator() {
101       public int compare( final Object o1, final Object o2 ) {
102         final String s1 = graph.getRealizer(((Node) o1)).getLabelText();
103         final String s2 = graph.getRealizer(((Node) o2)).getLabelText();
104         return s1.compareTo(s2);
105       }
106     });
107 
108 
109     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
110 
111 
112     // create a constraint factory for our graph
113     final SequenceConstraintFactory scf = ihl.createSequenceConstraintFactory(graph);
114 
115     // create constraints for nodes with "normal" labels;
116     // these nodes shall be sequenced according to the lexicographical order
117     // of their labels
118     for (int i = 1, n = labeled.size(); i < n; ++i) {
119       scf.addPlaceNodeAfterConstraint(
120               (Node) labeled.get(i - 1), (Node) labeled.get(i));
121     }
122 
123     // create constraints for all nodes with HEAD labels;
124     // these nodes shall always be placed at the start of their layers
125     for (int i = 0, n = heads.size(); i < n; ++i) {
126       scf.addPlaceNodeAtHeadConstraint((Node) heads.get(i));
127     }
128 
129     // create constraints for all nodes with TAIL labels;
130     // these nodes shall always be placed at the end of their layers
131     for (int i = 0, n = tails.size(); i < n; ++i) {
132       scf.addPlaceNodeAtTailConstraint((Node) tails.get(i));
133     }
134 
135     (new BufferedLayouter(ihl)).doLayout(graph);
136 
137     // dispose the constraint factory to clear all previously specified
138     // constraints and prevent memory leaks
139     scf.dispose();
140   }
141 
142   /**
143    * Generates random node labels for the specified graph. Roughly 1/8
144    * of the nodes will be marked either as a <code>HEAD</code> or as a
145    * <code>TAIL</code> node.
146    */
147   private void generateRandomLabels( final Graph2D graph ) {
148     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
149       final String label;
150       if (rndm.nextDouble() < 0.125) {
151         label = rndm.nextDouble() < 0.5 ? HEAD : TAIL;
152       } else {
153         final char[] chars = new char[rndm.nextInt(3)];
154         for (int i = 0; i < chars.length; ++i) {
155           chars[i] = (char)(rndm.nextInt(26) + (int)'A');
156         }
157         label = new String(chars);
158       }
159       graph.getRealizer(nc.node()).setLabelText(label);
160     }
161   }
162 
163   /**
164    * Loads an initial sample graph.
165    */
166   private void loadInitialGraph( final Graph2D graph ) {
167     final String resource = "resource/small_ihl_sample.gml";
168     final URL url = getClass().getResource(resource);
169     if (url != null) {
170       try {
171         InputStream is = null;
172         try {
173           is = url.openStream();
174           (new GMLIOHandler()).read(graph, is);
175         } finally {
176           if (is != null) {
177             is.close();
178           }
179         }
180       } catch (IOException ioe) {
181         System.err.println("Could not read file \"" + resource + "\".");
182       }
183     } else {
184       System.err.println("Could not find file \"" + resource + "\".");
185     }
186   }
187 
188   protected JToolBar createToolBar() {
189     final JToolBar jtb = super.createToolBar();
190     jtb.addSeparator();
191     jtb.add(new AbstractAction("Layout") {
192       {
193         putValue(AbstractAction.SHORT_DESCRIPTION,
194                  "<html><head></head><body>" +
195                  "Applies a hierarchical layout." +
196                  "<p>" +
197                  "Nodes will be sequenced according to the lexicographical" +
198                  " order of their labels." +
199                  "<br>" +
200                  "Nodes with empty labels will be sequenced as the layout" +
201                  " algorithm deems appropriate." +
202                  "<br>" +
203                  "Finally, nodes labeled <code>HEAD</code> or" +
204                  " <code>TAIL</code> are sequenced at layer start or layer" +
205                  " end respectively." +
206                  "</p>" +
207                  "</body></html>");
208       }
209 
210       public void actionPerformed( final ActionEvent e ) {
211         doLayout(view.getGraph2D());
212         view.fitContent();
213         view.updateView();
214       }
215     });
216     jtb.add(new AbstractAction("New Labels") {
217       {
218         putValue(AbstractAction.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>HEAD</code> or as a <code>TAIL</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     initLnF();
239     (new SequenceConstraintsDemo()).start();
240   }
241 }
242