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.module;
15  
16  import y.module.LayoutModule;
17  import y.module.YModule;
18  
19  import y.base.DataProvider;
20  import y.base.Edge;
21  import y.base.EdgeCursor;
22  
23  import y.layout.CompositeLayoutStage;
24  import y.layout.LabelLayoutConstants;
25  import y.layout.LabelLayoutDataRefinement;
26  import y.layout.LabelLayoutTranslator;
27  import y.layout.LabelRanking;
28  import y.layout.LayoutOrientation;
29  import y.layout.LayoutStage;
30  import y.layout.OrientationLayouter;
31  import y.layout.PortConstraint;
32  import y.layout.PortConstraintKeys;
33  import y.layout.grouping.FixedGroupLayoutStage;
34  import y.layout.grouping.GroupNodeHider;
35  import y.layout.hierarchic.BFSLayerer;
36  import y.layout.hierarchic.ClassicLayerSequencer;
37  import y.layout.hierarchic.HierarchicGroupLayouter;
38  import y.layout.hierarchic.HierarchicLayouter;
39  import y.layout.hierarchic.LayerSequencer;
40  import y.layout.labeling.GreedyMISLabeling;
41  
42  import y.view.EdgeLabel;
43  import y.view.EdgeRealizer;
44  import y.view.Graph2D;
45  import y.view.Selections;
46  import y.view.SmartEdgeLabelModel;
47  import y.view.hierarchy.HierarchyManager;
48  
49  import y.option.OptionHandler;
50  import y.option.ConstraintManager;
51  import y.util.DataProviderAdapter;
52  
53  /**
54   * This module represents an interactive configurator and launcher for
55   * {@link y.layout.hierarchic.HierarchicLayouter}
56   * and {@link y.layout.hierarchic.HierarchicGroupLayouter}.
57   *
58   */
59  public class HierarchicLayoutModule extends LayoutModule
60  {  
61    private static final String EDGE_LABEL_MODEL = "EDGE_LABEL_MODEL";
62    private static final String EDGE_LABELING = "EDGE_LABELING";
63    private static final String LABELING = "LABELING";
64    private static final String REMOVE_FALSE_CROSSINGS = "REMOVE_FALSE_CROSSINGS";
65    private static final String USE_TRANSPOSITION = "USE_TRANSPOSITION";
66    private static final String WEIGHT_HEURISTIC = "WEIGHT_HEURISTIC";
67    private static final String NODE_ORDER = "NODE_ORDER";
68    private static final String RANDOMIZATION_ROUNDS = "RANDOMIZATION_ROUNDS";
69    private static final String RANKING_POLICY = "RANKING_POLICY";
70    private static final String NODE_RANK = "NODE_RANK";
71    private static final String ACT_ON_SELECTION_ONLY = "ACT_ON_SELECTION_ONLY";
72    private static final String BACKLOOP_ROUTING = "BACKLOOP_ROUTING";
73    private static final String EDGE_ROUTING = "EDGE_ROUTING";
74    private static final String NODE_PLACEMENT = "NODE_PLACEMENT";
75    private static final String ORIENTATION = "ORIENTATION";
76    private static final String MAXIMAL_DURATION = "MAXIMAL_DURATION";
77    private static final String MINIMAL_EDGE_DISTANCE = "MINIMAL_EDGE_DISTANCE";
78    private static final String MINIMAL_FIRST_SEGMENT_LENGTH = "MINIMAL_FIRST_SEGMENT_LENGTH";
79    private static final String MINIMAL_NODE_DISTANCE = "MINIMAL_NODE_DISTANCE";
80    private static final String MINIMAL_LAYER_DISTANCE = "MINIMAL_LAYER_DISTANCE";
81    private static final String LAYOUT = "LAYOUT";
82    private static final String HIERARCHIC = "HIERARCHIC";
83    private static final String FREE = "FREE";
84    private static final String SIDE_SLIDER = "SIDE_SLIDER";
85    private static final String CENTER_SLIDER = "CENTER_SLIDER";
86    private static final String AS_IS = "AS_IS";
87    private static final String BEST = "BEST";
88    private static final String GENERIC = "GENERIC";
89    private static final String NONE = "NONE";
90    private static final String ORTHOGONAL = "ORTHOGONAL";
91    private static final String POLYLINE = "POLYLINE";
92    private static final String TREE = "TREE";
93    private static final String LINEAR_SEGMENTS = "LINEAR_SEGMENTS";
94    private static final String PENDULUM = "PENDULUM";
95    private static final String MEDIAN = "MEDIAN";
96    private static final String BARYCENTER = "BARYCENTER";
97    private static final String SIMPLEX = "SIMPLEX";
98    private static final String MEDIAN_SIMPLEX = "MEDIAN_SIMPLEX";
99    private static final String TIGHT_TREE = "TIGHT_TREE";
100   private static final String DOWNSHIFT_NODES = "DOWNSHIFT_NODES";
101   private static final String NO_RERANKING = "NO_RERANKING";
102   private static final String BFS          = "BFS";
103   
104   private static final String RIGHT_TO_LEFT = "RIGHT_TO_LEFT";
105   private static final String BOTTOM_TO_TOP = "BOTTOM_TO_TOP";
106   private static final String LEFT_TO_RIGHT = "LEFT_TO_RIGHT";
107   private static final String TOP_TO_BOTTOM = "TOP_TO_BOTTOM";
108   
109   private static final String GROUPING      = "GROUPING";
110   private static final String GROUP_POLICY  = "GROUP_LAYOUT_POLICY";
111   private static final String IGNORE_GROUPS = "IGNORE_GROUPS";
112   private static final String LAYOUT_GROUPS = "LAYOUT_GROUPS";
113   private static final String FIX_GROUPS    = "FIX_GROUPS";
114   private static final String ENABLE_GLOBAL_SEQUENCING = "ENABLE_GLOBAL_SEQUENCING";
115   
116   
117   private static final  String[] orientEnum = {
118     TOP_TO_BOTTOM, 
119     LEFT_TO_RIGHT,
120     BOTTOM_TO_TOP,
121     RIGHT_TO_LEFT
122   };
123   
124   private static final String[] topoLayerPolicy = {
125     NO_RERANKING,
126     DOWNSHIFT_NODES,
127     TIGHT_TREE,
128     SIMPLEX,
129     AS_IS,
130     BFS
131   };
132   
133   private static final String[] weightHeuristic = { 
134     BARYCENTER,
135     MEDIAN
136   };
137   
138   private static final String[] layoutStyles = {
139     PENDULUM,
140     LINEAR_SEGMENTS,
141     POLYLINE,
142     TREE,
143     SIMPLEX,
144     MEDIAN_SIMPLEX,
145   };
146   
147   private static final String[] routingStyles = {
148     POLYLINE,
149     ORTHOGONAL
150   };
151 
152   private static final String[] edgeLabeling = {
153     NONE,
154     HIERARCHIC,
155     GENERIC
156   };
157   
158   private static final String[] edgeLabelModel = {
159     BEST,
160     AS_IS,
161     CENTER_SLIDER,
162     SIDE_SLIDER,
163     FREE,
164   };
165   
166   
167   private HierarchicGroupLayouter hierarchic;
168   
169   public HierarchicLayoutModule()
170   {
171     super (HIERARCHIC,"yFiles Layout Team",
172            "Sugiyama based layout");
173     setPortIntersectionCalculatorEnabled(true);
174   }
175   
176   public OptionHandler createOptionHandler()
177   {
178     createHierarchic();
179     
180     OptionHandler op = new OptionHandler(getModuleName());
181 
182     op.useSection(LAYOUT);
183     op.addInt(MINIMAL_LAYER_DISTANCE, (int)hierarchic.getMinimalLayerDistance());
184     op.addInt(MINIMAL_NODE_DISTANCE, (int)hierarchic.getMinimalNodeDistance());
185     op.addInt(MINIMAL_EDGE_DISTANCE, (int)hierarchic.getMinimalEdgeDistance());
186     op.addInt(MINIMAL_FIRST_SEGMENT_LENGTH, (int)hierarchic.getMinimalFirstSegmentLength());
187     op.addInt(MAXIMAL_DURATION,5);
188     op.addEnum(ORIENTATION, orientEnum, 0);
189     op.addEnum(NODE_PLACEMENT,layoutStyles ,hierarchic.getLayoutStyle());
190     op.addEnum(EDGE_ROUTING  ,routingStyles,hierarchic.getRoutingStyle());
191     op.addBool(BACKLOOP_ROUTING, false);
192     op.addBool(ACT_ON_SELECTION_ONLY,false);
193     
194     op.useSection(NODE_RANK);
195     op.addEnum(RANKING_POLICY, topoLayerPolicy, 2); 
196     
197     op.useSection(NODE_ORDER);
198     ClassicLayerSequencer sequencer = new ClassicLayerSequencer();
199     op.addEnum(WEIGHT_HEURISTIC, weightHeuristic, sequencer.getWeightHeuristic());
200     op.addBool(USE_TRANSPOSITION, sequencer.getUseTransposition());
201     op.addBool(REMOVE_FALSE_CROSSINGS, hierarchic.getRemoveFalseCrossings());
202     op.addInt(RANDOMIZATION_ROUNDS, sequencer.getRandomizationRounds());
203 
204     op.useSection(LABELING);
205     ConstraintManager cm = new ConstraintManager(op);
206     cm.setEnabledOnValueEquals(op.addEnum(EDGE_LABELING, edgeLabeling, 0), NONE,
207         op.addEnum(EDGE_LABEL_MODEL, edgeLabelModel, 0), true);
208 
209     op.useSection(GROUPING);
210     String[] gEnum = { LAYOUT_GROUPS, FIX_GROUPS, IGNORE_GROUPS };
211     op.addEnum(GROUP_POLICY, gEnum, 0);
212     op.addBool(ENABLE_GLOBAL_SEQUENCING, true);
213     return op;
214   }
215   
216   public void mainrun()
217   {
218     createHierarchic();
219     
220     final Graph2D graph = getGraph2D();
221     
222     OptionHandler op = getOptionHandler();
223     hierarchic.setRemoveFalseCrossings(op.getBool(REMOVE_FALSE_CROSSINGS));
224     hierarchic.setMaximalDuration(op.getInt(MAXIMAL_DURATION)*1000);
225     hierarchic.setMinimalNodeDistance(op.getInt(MINIMAL_NODE_DISTANCE));
226     hierarchic.setMinimalEdgeDistance(op.getInt(MINIMAL_EDGE_DISTANCE));
227     hierarchic.setMinimalFirstSegmentLength(op.getInt(MINIMAL_FIRST_SEGMENT_LENGTH));
228     hierarchic.setMinimalLayerDistance(op.getInt(MINIMAL_LAYER_DISTANCE));
229     
230     final OrientationLayouter ol = (OrientationLayouter)hierarchic.getOrientationLayouter();
231     if(op.get(ORIENTATION).equals(TOP_TO_BOTTOM))
232       ol.setOrientation(OrientationLayouter.TOP_TO_BOTTOM);
233     else if(op.get(ORIENTATION).equals(LEFT_TO_RIGHT))
234       ol.setOrientation(OrientationLayouter.LEFT_TO_RIGHT);
235     else if(op.get(ORIENTATION).equals(BOTTOM_TO_TOP))
236       ol.setOrientation(OrientationLayouter.BOTTOM_TO_TOP);
237     else if(op.get(ORIENTATION).equals(RIGHT_TO_LEFT))
238       ol.setOrientation(OrientationLayouter.RIGHT_TO_LEFT);
239 
240     hierarchic.setGlobalSequencingActive(op.getBool(GROUPING, ENABLE_GLOBAL_SEQUENCING));
241     
242     String el = op.getString(EDGE_LABELING);
243     if(!el.equals(NONE))
244     {
245       setupEdgeLabelModel(el, op.getString(EDGE_LABEL_MODEL));
246       if(el.equals(GENERIC))
247       {
248         GreedyMISLabeling la = new GreedyMISLabeling();
249         la.setPlaceNodeLabels(false);
250         la.setPlaceEdgeLabels(true);
251         la.setProfitModel(new LabelRanking());
252         hierarchic.setLabelLayouter(la);
253         hierarchic.setLabelLayouterEnabled(true);
254       }
255       else if(el.equals(HIERARCHIC))
256       {
257         CompositeLayoutStage ll = new CompositeLayoutStage();
258         ll.appendStage(new LabelLayoutTranslator());
259         ll.appendStage(new LabelLayoutDataRefinement());
260         hierarchic.setLabelLayouter(ll);
261         hierarchic.setLabelLayouterEnabled(true);
262       }
263     }
264     else
265     {
266       hierarchic.setLabelLayouterEnabled(false);
267     }
268     
269     
270     String ls = op.getString(NODE_PLACEMENT);
271     if(ls.equals(PENDULUM))
272       hierarchic.setLayoutStyle(HierarchicLayouter.PENDULUM);
273     else if(ls.equals(POLYLINE))
274       hierarchic.setLayoutStyle(HierarchicLayouter.POLYLINE);
275     else if(ls.equals(LINEAR_SEGMENTS))
276       hierarchic.setLayoutStyle(HierarchicLayouter.LINEAR_SEGMENTS);
277     else if(ls.equals(TREE))
278       hierarchic.setLayoutStyle(HierarchicLayouter.TREE);
279     else if(ls.equals(SIMPLEX))
280       hierarchic.setLayoutStyle(HierarchicLayouter.SIMPLEX);
281     else if(ls.equals(MEDIAN_SIMPLEX))
282       hierarchic.setLayoutStyle(HierarchicLayouter.MEDIAN_SIMPLEX);
283     String rs = op.getString(EDGE_ROUTING);
284     if(rs.equals(POLYLINE))
285       hierarchic.setRoutingStyle(HierarchicLayouter.ROUTE_POLYLINE);
286     else if(rs.equals(ORTHOGONAL))
287       hierarchic.setRoutingStyle(HierarchicLayouter.ROUTE_ORTHOGONAL);
288     
289        
290     hierarchic.setSubgraphLayouterEnabled(op.getBool(ACT_ON_SELECTION_ONLY));
291     
292     String rp = op.getString(RANKING_POLICY);
293     
294     if(rp.equals(AS_IS))
295       hierarchic.setLayeringStrategy(HierarchicLayouter.LAYERING_FROM_SKETCH);
296     else if(rp.equals(SIMPLEX))
297       hierarchic.setLayeringStrategy(HierarchicLayouter.LAYERING_HIERARCHICAL_OPTIMAL);
298     else if(rp.equals(NO_RERANKING))
299       hierarchic.setLayeringStrategy(HierarchicLayouter.LAYERING_HIERARCHICAL_TOPMOST);
300     else if (rp.equals(DOWNSHIFT_NODES))
301       hierarchic.setLayeringStrategy(HierarchicLayouter.LAYERING_HIERARCHICAL_DOWNSHIFT);
302     else if (rp.equals(TIGHT_TREE))
303       hierarchic.setLayeringStrategy(HierarchicLayouter.LAYERING_HIERARCHICAL_TIGHT_TREE);
304     else if(rp.equals(BFS))
305     {
306       hierarchic.setLayeringStrategy(HierarchicLayouter.LAYERING_BFS);
307       getGraph2D().addDataProvider(BFSLayerer.CORE_NODES, Selections.createSelectionNodeMap(getGraph2D()));
308     }
309     
310     String  wh = op.getString(WEIGHT_HEURISTIC);
311     
312     LayerSequencer layerSequencer = hierarchic.getLayerSequencer();
313     if (layerSequencer instanceof ClassicLayerSequencer){
314       ClassicLayerSequencer cls = (ClassicLayerSequencer)layerSequencer;
315       if(wh.equals(MEDIAN))
316         cls.setWeightHeuristic(ClassicLayerSequencer.MEDIAN_HEURISTIC);
317       else
318         cls.setWeightHeuristic(ClassicLayerSequencer.BARYCENTER_HEURISTIC);
319       cls.setUseTransposition(op.getBool(USE_TRANSPOSITION));
320       cls.setRandomizationRounds(op.getInt(NODE_ORDER, RANDOMIZATION_ROUNDS));
321       hierarchic.setLayerSequencer(cls);
322     }
323 
324     DataProvider dp = null;
325     
326     DataProvider oldSdp = graph.getDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
327     DataProvider oldTdp = graph.getDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
328 
329     if(op.getBool(BACKLOOP_ROUTING))
330     {
331       PortConstraint spc = null, tpc = null;
332       switch(ol.getOrientation()) {
333       case LayoutOrientation.TOP_TO_BOTTOM:
334         spc = PortConstraint.create(PortConstraint.SOUTH);
335         tpc = PortConstraint.create(PortConstraint.NORTH);
336         break;
337       case LayoutOrientation.LEFT_TO_RIGHT:
338         spc = PortConstraint.create(PortConstraint.EAST);
339         tpc = PortConstraint.create(PortConstraint.WEST);
340         break;
341       case LayoutOrientation.RIGHT_TO_LEFT:
342         spc = PortConstraint.create(PortConstraint.WEST);
343         tpc = PortConstraint.create(PortConstraint.EAST);
344         break;
345       case LayoutOrientation.BOTTOM_TO_TOP:
346         spc = PortConstraint.create(PortConstraint.NORTH);
347         tpc = PortConstraint.create(PortConstraint.SOUTH);
348         break;
349       }
350       DataProvider sdp = new BackloopConstraintDP(spc, oldSdp);
351       DataProvider tdp = new BackloopConstraintDP(tpc, oldTdp);
352       
353       if (oldSdp != null){
354         graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
355       }
356       if (oldTdp != null){
357         graph.removeDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
358       }
359       
360       graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY,sdp);
361       graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY,tdp);
362     }
363     
364     if(HierarchyManager.containsGroupNodes(graph))
365     {
366       LayoutStage preStage = null;
367       if(op.get(GROUP_POLICY).equals(IGNORE_GROUPS)) {
368         preStage = new GroupNodeHider();
369         hierarchic.prependStage(preStage);
370       } else {
371         if(op.get(GROUP_POLICY).equals(FIX_GROUPS))
372         {
373            FixedGroupLayoutStage fixedGroupLayoutStage = new FixedGroupLayoutStage();
374            if(op.get(EDGE_ROUTING).equals(ORTHOGONAL))
375            {
376              fixedGroupLayoutStage.setInterEdgeRoutingStyle(FixedGroupLayoutStage.ROUTING_STYLE_ORTHOGONAL);
377            }
378            preStage = fixedGroupLayoutStage;
379            hierarchic.prependStage(preStage);
380         }
381       }
382       
383       try
384       {
385         launchLayouter(hierarchic); 
386       }
387       finally
388       {
389         if(preStage != null)
390         {
391           hierarchic.removeStage(preStage);
392         }
393       }      
394     }
395     else
396     {
397       launchLayouter(hierarchic);
398     }
399     
400     if(op.getBool(BACKLOOP_ROUTING))
401     {
402       graph.removeDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY);
403       graph.removeDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY);
404       if (oldSdp != null){
405         graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, oldSdp);
406       }
407       if (oldTdp != null){
408         graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, oldTdp);
409       }
410     }
411     if (dp != null){
412       graph.removeDataProvider(ClassicLayerSequencer.GROUP_KEY);
413     }
414     
415     //cleanup BFSLayerer key if present
416     graph.removeDataProvider(BFSLayerer.CORE_NODES);
417   }
418   
419   static final class BackloopConstraintDP extends DataProviderAdapter
420   {
421     private PortConstraint pc;
422     private DataProvider delegate;
423     private static final PortConstraint anySide = PortConstraint.create(PortConstraint.ANY_SIDE);
424     BackloopConstraintDP(PortConstraint pc, DataProvider delegate)
425     {
426       this.pc = pc;
427       this.delegate = delegate;
428     }
429     
430     public Object get(Object o)
431     {
432       if (delegate != null){
433         Object delegateResult = delegate.get(o);
434         if (delegateResult != null){
435           return delegateResult;
436         }
437       } 
438       Edge e = (Edge)o;
439       if(e.isSelfLoop())
440       {
441         return anySide;
442       } else {
443         return pc;
444       }
445     }
446   }
447   
448   void setupEdgeLabelModel(String edgeLabeling, String edgeLabelModel)
449   {
450     if(edgeLabeling.equals(NONE) || edgeLabelModel.equals(AS_IS))
451     {
452       return; //nothing to do
453     }
454     
455     if(edgeLabelModel.equals(BEST))
456     {
457       if(edgeLabeling.equals(GENERIC))
458         edgeLabelModel = SIDE_SLIDER;
459       else if(edgeLabeling.equals(HIERARCHIC))
460         edgeLabelModel = FREE;
461     }
462     
463     byte model = EdgeLabel.SIDE_SLIDER;
464     int preferredSide = LabelLayoutConstants.PLACE_RIGHT_OF_EDGE;
465     if(edgeLabelModel.equals(CENTER_SLIDER))
466     {
467       model = EdgeLabel.CENTER_SLIDER;
468       preferredSide = LabelLayoutConstants.PLACE_ON_EDGE;
469     }
470     else if(edgeLabelModel.equals(FREE))
471     {
472       model = EdgeLabel.FREE;
473       preferredSide = LabelLayoutConstants.PLACE_ON_EDGE;
474     }
475     
476     Graph2D graph = getGraph2D();
477     for(EdgeCursor ec = graph.edges(); ec.ok(); ec.next())
478     {
479       Edge e = ec.edge();
480       EdgeRealizer er = graph.getRealizer(e);
481       for(int i = 0; i < er.labelCount(); i++)
482       {
483         EdgeLabel el = er.getLabel(i);
484         if (EdgeLabel.FREE != model || !(el.getLabelModel() instanceof SmartEdgeLabelModel))
485         {
486           // SmartEdgeLabelModel is an enhanced free model,
487           // therefore it is ok to stick with it instead of
488           // replacing it with FreeEdgeLabelModel
489           el.setModel(model);
490         }
491         int prefAlongEdge = el.getPreferredPlacement() & LabelLayoutConstants.PLACEMENT_ALONG_EDGE_MASK;
492         el.setPreferredPlacement((byte)(preferredSide | prefAlongEdge));
493       }
494     }
495   }
496   
497   
498   public void dispose()
499   {
500     hierarchic = null;
501   }
502   
503   private void createHierarchic()
504   {
505     if(hierarchic == null) 
506     {
507       hierarchic = new HierarchicGroupLayouter(); 
508     }
509   }
510 }
511 
512