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.module.HierarchicLayoutModule;
17  import demo.view.DemoBase;
18  import y.base.Edge;
19  import y.base.EdgeCursor;
20  import y.base.EdgeMap;
21  import y.base.Node;
22  import y.base.NodeCursor;
23  import y.base.NodeMap;
24  import y.layout.LabelLayoutConstants;
25  import y.layout.PortConstraint;
26  import y.layout.PortConstraintConfigurator;
27  import y.layout.PortConstraintKeys;
28  import y.layout.hierarchic.ClassicLayerSequencer;
29  import y.view.Arrow;
30  import y.view.EdgeLabel;
31  import y.view.EdgeRealizer;
32  import y.view.EditMode;
33  import y.view.Graph2D;
34  import y.view.Graph2DSelectionEvent;
35  import y.view.Graph2DSelectionListener;
36  import y.view.PortAssignmentMoveSelectionMode;
37  
38  import javax.swing.AbstractAction;
39  import javax.swing.BorderFactory;
40  import javax.swing.ButtonGroup;
41  import javax.swing.ButtonModel;
42  import javax.swing.JButton;
43  import javax.swing.JCheckBox;
44  import javax.swing.JComboBox;
45  import javax.swing.JPanel;
46  import javax.swing.JRadioButton;
47  import javax.swing.JToolBar;
48  import javax.swing.JScrollPane;
49  import java.awt.BorderLayout;
50  import java.awt.Color;
51  import java.awt.GridBagLayout;
52  import java.awt.GridBagConstraints;
53  import java.awt.event.ActionEvent;
54  import java.awt.event.ActionListener;
55  
56  /**
57   * This Demo shows how HierarchicLayouter can handle port constraints,
58   * how it can take edge labels into consideration when laying out
59   * a graph and how to specify groups of nodes, which will be placed next to
60   * each other on each layer.
61   * <br>
62   * <br>
63   * <b>Usage:</b>
64   * <br>
65   * After starting the demo create a graph manually in the graph editor
66   * pane. Now click on the "Layout" Button in the toolbar to 
67   * start the hierarchic layouter. 
68   * <br>
69   * Additional port constraints can be specified for HierarchicLayouter.
70   * A port constraint expresses on what side of a node the source and/or 
71   * target point of an edge should connect. To add a port constraint to an edge 
72   * first select the edge by clicking on it. Then choose the desired source and target 
73   * port constraint for the selected edge with the radiobutton panel on the left of
74   * the graph pane. Selecting "South" from the "Source Port" choice for example 
75   * means the edge selected edge should connect to the bottom side of the source node.
76   * Alternatively once can simply move or create the first or last bend of an edge.
77   * A visual clue will appear that determines the PortConstraint.
78   * <br>
79   * A port constraint that is marked as "strong", means that the associated 
80   * port coordinate given to the layouter will not be modified by the layouter.  
81   * <br>
82   * After the port constraints have been set up it is time to press the 
83   * "layout" button again. Now the resulting layout is a hierarchic layout
84   * that obeys the additional port constraints.
85   * <br>
86   * To activate the edge labeling feature check the box named "Label Edges" just below
87   * the port constraint selector pane.
88   * If this feature is turned on then on there will be edge labels visible that
89   * display the type of port constraint activated for the source and target port
90   * of each edge. Now by pressing the "Layout" button again the resulting layout 
91   * will consider these edge labels as well. One can see that none of the labels 
92   * overlap and that the size of the layout has increased.
93   * <br>
94   * The toolbar button "Option..." allows to specify diverse layout paramters 
95   * for the layouter. Not all of these options are important to this demo.
96   * Noteworthy options for this demo are "Edge Routing: Orthogonal" and in tab 
97   * "Node Rank" the ranking policy "From Sketch".
98   * <br>
99   * "Edge Routing: Orthogonal" has the effect of routing all edges orthogonally, 
100  * i.e. by using only horizontal and vertical line segments.
101  * <br>
102  * "Ranking Policy: From Sketch" has the effect that the layer partitions will be
103  * established by the given drawing heuristically (looking at the y-coordinates 
104  * of the nodes). By this option it is possible to put nodes in the same layer that
105  * are connected by an edge.
106  * <br>
107  * <br>
108  * Node Groups:
109  * By clicking on one of the colored buttons in the left bar, the currently selected
110  * nodes will be assigned to the corresponding group. This grouping will be
111  * indicated by the node color. When being layouted, nodes on the same layer
112  * having the same group (color) will be placed next to each other as a compound
113  * group.
114  * <br>
115  * <br>
116  * API usage:
117  * <br>
118  * Each port constraint is expressed
119  * by an object of type PortConstraint. The side of the port can be specified by
120  * one of the side specifiers NORTH, SOUTH, EAST, WEST of class PortConstraint.
121  * Portcontraint data gets passed to HierarchicLayouter by binding named DataProviders
122  * to the input graph. Use PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY to register
123  * a data provider that returns a PortConstraint object for each source port of an edge
124  * and use PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY to register
125  * a data provider that returns a PortConstraint object for each target port of an edge.
126  * <br>
127  * The internal hierarchic labeling gets activated by associating an array of 
128  * type LabelLayoutData to each edge of the graph. This is done by registering
129  * a Dataprovider with the key LabelLayoutData.EDGE_LABEL_LAYOUT_KEY to the 
130  * input graph. Alternatively one can use the layout stage
131  * y.layout.LabelLayoutTranslator as the LabelLayouter of HierarchicLayouter.
132  * In the latter case the inital label information will be automatically 
133  * read from the EdgeLabelLayout information of the input graph 
134  * ( getEdgeLabelLayout(Edge e) ). It will also be written back to the 
135  * EdgeLabelLayout of the graph.  
136  * <br>
137  * Groupings of nodes are expressed through the means of integer values.
138  * Each node *can* be assigned an integer value, which determines the group
139  * to which it belongs. The layouter will query the ClassicalLayerSequencer.GROUP_KEY
140  * NodeDataProvider from the graph and will layout the nodes 
141  */
142 
143 public class HierarchicLayouterDemo extends DemoBase
144 {
145   private static final String EDGE_LABELING = "EDGE_LABELING";
146   private static final String HIERARCHIC = "HIERARCHIC";
147   private static final String NONE = "NONE";
148 
149   private PortSpec  sourceSpec, targetSpec;
150   private EdgeMap sourceGroupIdMap, targetGroupIdMap;
151   private JCheckBox labelBox;
152   private EdgeMap   sourcePortMap;
153   private EdgeMap   targetPortMap;
154 
155   private HierarchicLayoutModule layoutModule;
156   
157   private NodeMap groupMap;
158   private PortAssignmentMoveSelectionMode paMode;
159   
160   public HierarchicLayouterDemo()
161   {
162     final Graph2D graph = view.getGraph2D();
163     groupMap = graph.createNodeMap();
164     graph.addDataProvider(ClassicLayerSequencer.GROUP_KEY, groupMap);
165     EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
166     defaultER.setArrow(Arrow.STANDARD);
167 
168     sourcePortMap = graph.createEdgeMap();
169     targetPortMap = graph.createEdgeMap();
170     sourceGroupIdMap = graph.createEdgeMap();
171     targetGroupIdMap = graph.createEdgeMap();
172     graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, sourceGroupIdMap);
173     graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, targetGroupIdMap);
174     graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY,sourcePortMap);
175     graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY,targetPortMap);
176     
177     paMode.setSpc(sourcePortMap);
178     paMode.setTpc(targetPortMap);
179     
180 
181     JPanel left = new JPanel(new GridBagLayout());
182     GridBagConstraints gbc = new GridBagConstraints();
183     gbc.fill = GridBagConstraints.BOTH;
184     gbc.anchor = GridBagConstraints.NORTHWEST;
185     gbc.gridx = 0;
186 
187     left.add(new JButton(new AbstractAction("Parse Ports"){
188       public void actionPerformed(ActionEvent ae){
189         new PortConstraintConfigurator()
190           .createPortConstraintsFromSketch(graph, sourcePortMap, targetPortMap);
191       }
192     }), gbc);
193 
194     gbc.gridx = 1;
195 
196     labelBox = new JCheckBox("Label Edges");
197     labelBox.addActionListener(new ActionListener() {
198         public void actionPerformed(ActionEvent ev)
199         {
200           setLabelEdges(labelBox.isSelected());
201         }
202       });
203 
204     left.add(labelBox);
205 
206     sourceSpec = new PortSpec("Source Port", sourcePortMap, sourceGroupIdMap);
207     targetSpec = new PortSpec("Target Port", targetPortMap, targetGroupIdMap);
208     gbc.gridx = 0;
209     gbc.gridy = 1;
210     left.add(sourceSpec, gbc);
211     gbc.gridx = 1;
212     left.add(targetSpec, gbc);
213     gbc.gridx = 0;
214     gbc.gridy = GridBagConstraints.RELATIVE;
215 
216     JPanel groupSpec;
217     groupSpec = new JPanel(new GridBagLayout());
218     groupSpec.setBorder(BorderFactory.createTitledBorder("Node Groups"));
219 
220     // build the grouping mechanism
221     Color[] groupColors = new Color[]{ null, Color.blue, Color.yellow, Color.red };
222     
223     for (int i = 0; i < groupColors.length; i++){
224       JButton groupButton = new GroupButton(groupColors[i], i);
225       groupSpec.add(groupButton, gbc);
226     }
227     gbc.weightx = gbc.weighty = 1;
228     groupSpec.add(new JPanel(), gbc);
229     gbc.weightx = gbc.weighty = 0;
230     gbc.gridwidth = 2;
231     left.add(groupSpec, gbc);
232     gbc.gridwidth = 1;
233 
234     gbc.weighty = 1.0d;
235     left.add(new JPanel(), gbc);
236 
237     contentPane.add(new JScrollPane(left), BorderLayout.WEST);
238     
239     graph.addGraph2DSelectionListener(new Graph2DSelectionListener() {
240         public void onGraph2DSelectionEvent(Graph2DSelectionEvent ev)
241         {
242           if(ev.getSubject() instanceof Edge)
243           {
244             Edge e = (Edge)ev.getSubject();
245             if(ev.getGraph2D().isSelected(e))
246             {
247               PortConstraint pc = getSPC(e);
248               sourceSpec.setSide(pc.getSide());
249               sourceSpec.setStrong(pc.isStrong());
250               sourceSpec.setGroupId(sourceGroupIdMap.get(e));
251               pc = getTPC(e);
252               targetSpec.setSide(pc.getSide());
253               targetSpec.setStrong(pc.isStrong());
254               targetSpec.setGroupId(targetGroupIdMap.get(e));
255             }
256           }
257         }
258       });
259     
260     layoutModule = new HierarchicLayoutModule();
261   }
262   
263   protected JToolBar createToolBar()
264   {
265     JToolBar bar = super.createToolBar();
266     bar.add(new LayoutAction());
267     bar.add(new OptionAction());
268     return bar;
269   }
270   
271   /**
272    * this method assigns the group id and the corresponding color hint
273    * to the currently selected nodes
274    */
275   protected void assignGroup(Color color, int index){
276     Graph2D graph = view.getGraph2D();
277     for(NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next())
278     {
279       Node n = nc.node();
280       if (color == null){
281         color = graph.getDefaultNodeRealizer().getFillColor();
282         // unset the actual group index
283         groupMap.set(n, null);
284       } else {
285         // set the actual group index
286         groupMap.setInt(n, index);
287       }
288       // set the color hint
289       graph.getRealizer(n).setFillColor(color);
290       
291     }
292     graph.updateViews();
293   }
294   
295   
296   // helper class
297   class GroupButton extends JButton implements ActionListener{
298     Color color;
299     int index;
300     GroupButton(Color color, int index){
301       super("");
302       setText(index > 0? "Group "+index : "No Group");
303       this.color = color;
304       this.index = index;
305       setBackground(color);
306       this.addActionListener(this);
307     }
308     
309     public void actionPerformed(ActionEvent e)
310     {
311       HierarchicLayouterDemo.this.assignGroup(color, index);
312     }
313   }
314   
315   class LayoutAction extends AbstractAction
316   {
317     LayoutAction()
318     {
319       super("Layout");
320     }
321     
322     public void actionPerformed(ActionEvent ev)
323     {
324       
325       Graph2D graph = view.getGraph2D();
326       //for(EdgeCursor ec = graph.edges(); ec.ok(); ec.next())
327       //{
328       //  Edge e = ec.edge();
329       //  EdgeRealizer er = graph.getRealizer(e);
330         //if(er.labelCount() >= 2)
331         //{
332         //  er.getLabel(0).setPreferredPlacement((byte)(EdgeLabel.PLACE_AT_SOURCE | EdgeLabel.PLACE_RIGHT_OF_EDGE));
333         //  er.getLabel(1).setPreferredPlacement((byte)(EdgeLabel.PLACE_AT_TARGET | EdgeLabel.PLACE_RIGHT_OF_EDGE));
334         //}
335       //}
336       layoutModule.start(graph);
337     }
338   }
339   
340   class OptionAction extends AbstractAction
341   {
342     OptionAction()
343     {
344       super("Option...");
345     }
346     
347     public void actionPerformed(ActionEvent ev)
348     {
349       layoutModule.getOptionHandler().showEditor();
350     }
351   }
352 
353   
354       
355   PortConstraint getSPC(Edge e)
356   {
357     PortConstraint pc = (e != null) ? (PortConstraint)sourcePortMap.get(e) : null;
358     
359     if (pc == null) pc = PortConstraint.create(PortConstraint.ANY_SIDE);
360     
361     return pc;
362   }
363   
364   PortConstraint getTPC(Edge e)
365   {
366     PortConstraint pc = (e != null) ? (PortConstraint)targetPortMap.get(e) : null;
367 
368     if (pc == null) pc = PortConstraint.create(PortConstraint.ANY_SIDE);
369     
370     return pc;
371   }
372   
373  void setPC(EdgeMap portMap,EdgeMap groupMap, byte side, boolean strong, Object groupId)
374   {
375     Graph2D graph = view.getGraph2D();
376     PortConstraint pc = PortConstraint.create(side, strong);
377     String preText = pc.toString();
378     if (groupId != null){
379       preText = preText + '[' + groupId + ']';
380     }
381     for(EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next())
382     {
383       Edge e = ec.edge();
384       portMap.set(e,pc);
385       getSPC(e);
386       getTPC(e);
387       groupMap.set(e, groupId);
388       if(graph.getRealizer(e).labelCount() >= 2)
389       {
390         String text = portMap == sourcePortMap ? "source " + preText: "target" + preText;
391         graph.getRealizer(e).getLabel(portMap == sourcePortMap ? 0 : 1).setText(text);
392       }
393     }
394     graph.updateViews();
395   }
396   
397   /**
398    * Launches this demo.
399    */
400   public static void main(String args[])
401   {
402     initLnF();
403     HierarchicLayouterDemo demo = new HierarchicLayouterDemo();
404     
405     demo.start("Hierarchic Layouter Demo");
406   }
407   
408   
409   
410   class PortSpec extends JPanel
411   {
412     JRadioButton anySideB, northB, southB, eastB, westB;
413     JCheckBox cb;
414     JComboBox box;
415     EdgeMap portMap;
416     EdgeMap groupMap;
417     
418     Object[] items = new Object[]{"no Group",new Integer(1), new Integer(2), new Integer(3), new Integer(4),
419                                   new Integer(5), new Integer(6), new Integer(7)};
420     
421     PortSpec(String title, EdgeMap portMap, EdgeMap groupMap)
422     {
423       super(new GridBagLayout());
424       GridBagConstraints gbc = new GridBagConstraints();
425       gbc.fill = GridBagConstraints.BOTH;
426       gbc.gridx = 0;
427       gbc.anchor = GridBagConstraints.NORTHWEST;
428       this.setBorder(BorderFactory.createTitledBorder(title));
429       this.portMap = portMap;
430       this.groupMap = groupMap;
431       final ButtonGroup bg = new ButtonGroup();
432       
433       
434       anySideB = new JRadioButton("ANY_SIDE");
435       northB = new JRadioButton("NORTH");
436       southB = new JRadioButton("SOUTH");
437       eastB = new JRadioButton("EAST");
438       westB = new JRadioButton("WEST");
439       anySideB.setSelected(true);
440       
441       cb = new JCheckBox("Strong");
442       
443       
444       box = new JComboBox(items);
445       
446       ActionListener rl = new ActionListener() {
447         public void actionPerformed(ActionEvent ev) {
448           ButtonModel bm = bg.getSelection();
449           byte side = PortConstraint.ANY_SIDE;
450           if ( bm == southB.getModel() ) {
451             side = PortConstraint.SOUTH;
452           } else if ( bm == northB.getModel() ) {
453             side = PortConstraint.NORTH;
454           } else if ( bm == eastB.getModel() ) {
455             side = PortConstraint.EAST;
456           } else if ( bm == westB.getModel() ) side = PortConstraint.WEST;
457           boolean strong = cb.isSelected();
458           Object groupId = box.getSelectedItem();
459           if (groupId instanceof String){
460             groupId = null;
461           }
462           setPC(PortSpec.this.portMap, PortSpec.this.groupMap, side, strong, groupId);
463         }
464       };
465       
466       addButton(anySideB, bg, rl, gbc);
467       addButton(northB, bg, rl, gbc);
468       addButton(southB, bg, rl, gbc);
469       addButton(eastB, bg, rl, gbc);
470       addButton(westB, bg, rl, gbc);
471       this.add(cb, gbc);
472       this.add(box, gbc);
473       box.addActionListener(rl);
474       cb.addActionListener(rl);
475     }
476     
477     void addButton(JRadioButton b, ButtonGroup bg, ActionListener rl, GridBagConstraints gbc)
478     {
479       bg.add(b);
480       this.add(b, gbc);
481       b.addActionListener(rl);
482     }
483     
484     void setSide(byte side)
485     {
486       switch(side) {
487       case PortConstraint.ANY_SIDE:
488         anySideB.setSelected(true);
489         break;
490       case PortConstraint.NORTH:
491         northB.setSelected(true);
492         break;
493       case PortConstraint.SOUTH:
494         southB.setSelected(true);
495         break;
496       case PortConstraint.WEST:
497         westB.setSelected(true);
498         break;
499       case PortConstraint.EAST:
500         eastB.setSelected(true);
501         break;
502       }
503     }
504     
505     void setStrong(boolean strong){
506       cb.setSelected(strong);
507     }
508     
509     void setGroupId(Object id){
510       if (id == null){
511         box.setSelectedIndex(0);
512       } else {
513         box.setSelectedItem(id);
514       }
515     }
516   }
517   
518   void setLabelEdges(boolean labelEdges)
519   {
520     Graph2D graph = view.getGraph2D();
521     
522     if(labelEdges)
523     {
524       addLabels(graph.getDefaultEdgeRealizer());
525       for(EdgeCursor ec = graph.edges(); ec.ok();ec.next())
526       {
527         addLabels(graph.getRealizer(ec.edge()));
528       }
529       layoutModule.getOptionHandler().set(EDGE_LABELING,HIERARCHIC);
530     }
531     else
532     {
533       removeLabels(graph.getDefaultEdgeRealizer());
534       for(EdgeCursor ec = graph.edges(); ec.ok();ec.next())
535       {
536         removeLabels(graph.getRealizer(ec.edge()));
537       } 
538       layoutModule.getOptionHandler().set(EDGE_LABELING, NONE);
539     }
540     
541     view.updateView();
542     
543   }
544   
545   
546   void addLabels(EdgeRealizer er)
547   {
548     removeLabels(er);
549     
550     EdgeLabel el = new EdgeLabel(getSPC(er.getEdge()).toString());
551     el.setModel(EdgeLabel.CENTER_SLIDER);
552     el.setPosition(EdgeLabel.SCENTR);
553     el.setPreferredPlacement(LabelLayoutConstants.PLACE_AT_SOURCE);
554     er.addLabel(el);
555     
556 
557     el = new EdgeLabel(getTPC(er.getEdge()).toString());  
558     el.setModel(EdgeLabel.CENTER_SLIDER);
559     el.setPosition(EdgeLabel.TCENTR);
560     el.setPreferredPlacement(LabelLayoutConstants.PLACE_AT_TARGET);
561     er.addLabel(el);
562 
563     el = new EdgeLabel("Center");  
564     el.setModel(EdgeLabel.CENTER_SLIDER);
565     el.setPosition(EdgeLabel.CENTER);
566     el.setPreferredPlacement(LabelLayoutConstants.PLACE_AT_CENTER);
567     er.addLabel(el);
568   }
569   
570   void removeLabels(EdgeRealizer er)
571   {
572     while(er.labelCount() > 0) er.removeLabel(er.getLabel(er.labelCount()-1));
573   }
574 
575   protected void registerViewModes() {
576     EditMode mode = new EditMode();
577     mode.setMoveSelectionMode(paMode = new PortAssignmentMoveSelectionMode(null, null));
578     view.addViewMode( mode );
579   }
580 }
581