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.module;
29  
30  import y.module.LayoutModule;
31  import y.module.YModule;
32  
33  import y.base.DataMap;
34  import y.base.DataProvider;
35  import y.base.Edge;
36  import y.base.EdgeCursor;
37  import y.base.EdgeMap;
38  import y.base.Node;
39  import y.layout.AbstractLayoutStage;
40  import y.layout.CanonicMultiStageLayouter;
41  import y.layout.ComponentLayouter;
42  import y.layout.EdgeBundleDescriptor;
43  import y.layout.EdgeBundling;
44  import y.layout.EdgeLabelLayout;
45  import y.layout.LayoutGraph;
46  import y.layout.LayoutOrientation;
47  import y.layout.Layouter;
48  import y.layout.grouping.Grouping;
49  import y.layout.labeling.GreedyMISLabeling;
50  import y.layout.labeling.MISLabelingAlgorithm;
51  import y.layout.labeling.SALabeling;
52  import y.layout.router.OrganicEdgeRouter;
53  import y.layout.router.StraightLineEdgeRouter;
54  import y.layout.router.polyline.EdgeRouter;
55  import y.layout.tree.ARTreeLayouter;
56  import y.layout.tree.HVTreeLayouter;
57  import y.layout.tree.TreeLayouter;
58  import y.layout.tree.TreeReductionStage;
59  import y.option.ConstraintManager;
60  import y.option.EnumOptionItem;
61  import y.option.OptionHandler;
62  import y.option.OptionItem;
63  import y.util.DataProviderAdapter;
64  import y.util.GraphHider;
65  import y.util.Maps;
66  import y.view.Graph2D;
67  import y.view.Graph2DView;
68  
69  import java.awt.Dimension;
70  
71  /**
72   * This module represents an interactive configurator and launcher for {@link y.layout.tree.TreeLayouter},
73   * {@link y.layout.tree.ARTreeLayouter} and {@link y.layout.tree.HVTreeLayouter}.
74   *
75   *
76   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/tree_layouter#tree_layouter" target="_blank">Section Tree Layout</a> in the yFiles for Java Developer's Guide
77   */
78  public class TreeLayoutModule extends LayoutModule {
79  
80    //// Module 'Treelike Layout'
81    protected static final String MODULE_TREE = "TREE";
82    
83    //// Section 'General'
84    protected static final String SECTION_GENERAL = "GENERAL";
85    // Section 'General' items
86    protected static final String ITEM_LAYOUT_STYLE = "LAYOUT_STYLE";
87    protected static final String VALUE_DIRECTED = "DIRECTED";
88    protected static final String VALUE_HV = "HV";
89    protected static final String VALUE_AR = "AR";
90    protected static final String ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES = "ROUTING_STYLE_FOR_NON_TREE_EDGES";
91    protected static final String VALUE_ROUTE_ORGANIC = "ROUTE_ORGANIC";
92    protected static final String VALUE_ROUTE_ORTHOGONAL = "ROUTE_ORTHOGONAL";
93    protected static final String VALUE_ROUTE_STRAIGHTLINE = "ROUTE_STRAIGHTLINE";
94    protected static final String VALUE_ROUTE_BUNDLED = "ROUTE_BUNDLED";
95    protected static final String EDGE_BUNDLING_STRENGTH = "EDGE_BUNDLING_STRENGTH";
96  
97    protected static final String ITEM_ACT_ON_SELECTION_ONLY = "ACT_ON_SELECTION_ONLY";
98    //// Section 'Directed'
99    protected static final String SECTION_DIRECTED = "DIRECTED";
100   // Section 'Directed' items
101   protected static final String ITEM_MINIMAL_NODE_DISTANCE = "MINIMAL_NODE_DISTANCE";
102   protected static final String ITEM_MINIMAL_LAYER_DISTANCE = "MINIMAL_LAYER_DISTANCE";
103   protected static final String ITEM_ORIENTATION = "ORIENTATION";
104   protected static final String VALUE_TOP_TO_BOTTOM = "TOP_TO_BOTTOM";
105   protected static final String VALUE_LEFT_TO_RIGHT = "LEFT_TO_RIGHT";
106   protected static final String VALUE_BOTTOM_TO_TOP = "BOTTOM_TO_TOP";
107   protected static final String VALUE_RIGHT_TO_LEFT = "RIGHT_TO_LEFT";
108   protected static final String ITEM_PORT_STYLE = "PORT_STYLE";
109   protected static final String VALUE_NODE_CENTER_PORTS = "NODE_CENTER";
110   protected static final String VALUE_BORDER_CENTER_PORTS = "BORDER_CENTER";
111   protected static final String VALUE_BORDER_DISTRIBUTED_PORTS = "BORDER_DISTRIBUTED";
112   protected static final String VALUE_PORT_CONSTRAINTS_AWARE = "PORT_CONSTRAINTS_AWARE";
113   protected static final String ITEM_INTEGRATED_NODE_LABELING = "INTEGRATED_NODE_LABELING";
114   protected static final String ITEM_INTEGRATED_EDGE_LABELING = "INTEGRATED_EDGE_LABELING";
115   protected static final String ITEM_ORTHOGONAL_EDGE_ROUTING = "ORTHOGONAL_EDGE_ROUTING";
116   protected static final String ITEM_BUS_ALIGNMENT = "BUS_ALIGNMENT";
117   protected static final String ITEM_VERTICAL_ALIGNMENT = "VERTICAL_ALIGNMENT";
118   protected static final String VALUE_ALIGNMENT_TOP = "ALIGNMENT_TOP";
119   protected static final String VALUE_ALIGNMENT_CENTER = "ALIGNMENT_CENTER";
120   protected static final String VALUE_ALIGNMENT_BOTTOM = "ALIGNMENT_BOTTOM";
121   protected static final String ITEM_CHILD_PLACEMENT_POLICY = "CHILD_PLACEMENT_POLICY";
122   protected static final String VALUE_SIBLINGS_ON_SAME_LAYER = "SIBLINGS_ON_SAME_LAYER";
123   protected static final String VALUE_ALL_LEAVES_ON_SAME_LAYER = "ALL_LEAVES_ON_SAME_LAYER";
124   protected static final String VALUE_LEAVES_STACKED = "LEAVES_STACKED";
125   protected static final String VALUE_LEAVES_STACKED_LEFT = "LEAVES_STACKED_LEFT";
126   protected static final String VALUE_LEAVES_STACKED_RIGHT = "LEAVES_STACKED_RIGHT";
127   protected static final String ITEM_ENFORCE_GLOBAL_LAYERING = "ENFORCE_GLOBAL_LAYERING";
128   //// Section 'Horizontal-Vertical'
129   protected static final String SECTION_HV = "HV";
130   // Section 'Horizontal-Vertical' items
131   protected static final String ITEM_HORIZONTAL_SPACE_HV = "HORIZONTAL_SPACE";
132   protected static final String ITEM_VERTICAL_SPACE_HV = "VERTICAL_SPACE";
133   //// Section 'Compact'
134   protected static final String SECTION_AR = "AR";
135   // Section 'Compact' items
136   protected static final String ITEM_HORIZONTAL_SPACE_AR = "HORIZONTAL_SPACE";
137   protected static final String ITEM_VERTICAL_SPACE_AR = "VERTICAL_SPACE";
138   protected static final String ITEM_BEND_DISTANCE = "BEND_DISTANCE";
139   protected static final String ITEM_USE_VIEW_ASPECT_RATIO = "USE_VIEW_ASPECT_RATIO";
140   protected static final String ITEM_ASPECT_RATIO = "ASPECT_RATIO";
141 
142   /**
143    * Creates an instance of this module.
144    */
145   public TreeLayoutModule() {
146     super(MODULE_TREE);
147     setPortIntersectionCalculatorEnabled(true);
148   }
149 
150   /**
151    * Creates an OptionHandler and adds the option items used by this module.
152    * @return the created <code>OptionHandler</code> providing module related options
153    */
154   protected OptionHandler createOptionHandler() {
155     final OptionHandler options = new OptionHandler(getModuleName());
156     final ConstraintManager optionConstraints = new ConstraintManager(options);
157     // Defaults provider
158     final TreeLayouter defaultsTree = new TreeLayouter();
159     final HVTreeLayouter defaultsHv = new HVTreeLayouter();
160     final ARTreeLayouter defaultsAr = new ARTreeLayouter();
161     ((ComponentLayouter) defaultsHv.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
162     ((ComponentLayouter) defaultsAr.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
163 
164     //// Section 'General'
165     options.useSection(SECTION_GENERAL);
166     // Populate section
167     options.addEnum(ITEM_LAYOUT_STYLE, new String[]{
168             VALUE_DIRECTED,
169             VALUE_HV,
170             VALUE_AR
171     }, 0);
172     final EnumOptionItem itemNonTreeEdgeRouting = options.addEnum(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES, new String[]{
173             VALUE_ROUTE_ORGANIC,
174             VALUE_ROUTE_ORTHOGONAL,
175             VALUE_ROUTE_STRAIGHTLINE,
176             VALUE_ROUTE_BUNDLED
177     }, 0);
178     options.addBool(ITEM_ACT_ON_SELECTION_ONLY, false);
179 
180     final OptionItem itemBundlingStrength = options.addDouble(EDGE_BUNDLING_STRENGTH, 0.99, 0.0, 1.0);
181 
182     final ConstraintManager.Condition bundlingCondition =
183             optionConstraints.createConditionValueEquals(itemNonTreeEdgeRouting, VALUE_ROUTE_BUNDLED);
184     optionConstraints.setEnabledOnCondition(bundlingCondition, itemBundlingStrength);
185 
186     //// Section 'Directed'
187     options.useSection(SECTION_DIRECTED);
188     // Populate section
189     options.addInt(ITEM_MINIMAL_NODE_DISTANCE, (int) defaultsTree.getMinimalNodeDistance(), 1, 100);
190     options.addInt(ITEM_MINIMAL_LAYER_DISTANCE, (int) defaultsTree.getMinimalLayerDistance(), 10, 300);
191     options.addEnum(ITEM_ORIENTATION, new String[]{
192             VALUE_TOP_TO_BOTTOM,
193             VALUE_LEFT_TO_RIGHT,
194             VALUE_BOTTOM_TO_TOP,
195             VALUE_RIGHT_TO_LEFT
196     }, 0);
197     options.addEnum(ITEM_PORT_STYLE, new String[]{
198             VALUE_NODE_CENTER_PORTS,
199             VALUE_BORDER_CENTER_PORTS,
200             VALUE_BORDER_DISTRIBUTED_PORTS,
201             VALUE_PORT_CONSTRAINTS_AWARE,
202     }, 0);
203     options.addBool(ITEM_INTEGRATED_NODE_LABELING, false);
204     options.addBool(ITEM_INTEGRATED_EDGE_LABELING, false);
205     final OptionItem itemEdgeRouting = options.addBool(ITEM_ORTHOGONAL_EDGE_ROUTING, false);
206     final OptionItem itemBusAlignment = options.addEnum(ITEM_BUS_ALIGNMENT, new String[]{
207         VALUE_ALIGNMENT_TOP,
208         VALUE_ALIGNMENT_CENTER,
209         VALUE_ALIGNMENT_BOTTOM,
210     }, 1);
211     options.addEnum(ITEM_VERTICAL_ALIGNMENT, new String[]{
212         VALUE_ALIGNMENT_TOP,
213         VALUE_ALIGNMENT_CENTER,
214         VALUE_ALIGNMENT_BOTTOM,
215     }, 1);
216     final OptionItem itemChildPlacement = options.addEnum(ITEM_CHILD_PLACEMENT_POLICY, new String[]{
217         VALUE_SIBLINGS_ON_SAME_LAYER,
218         VALUE_ALL_LEAVES_ON_SAME_LAYER,
219         VALUE_LEAVES_STACKED,
220         VALUE_LEAVES_STACKED_LEFT,
221         VALUE_LEAVES_STACKED_RIGHT
222     }, 0);
223     final OptionItem itemGlobalLayering = options.addBool(ITEM_ENFORCE_GLOBAL_LAYERING, false);
224     // Enable/disable items depending on specific values
225     final ConstraintManager.Condition conditionBusAlignment = 
226         optionConstraints.createConditionValueEquals(itemEdgeRouting, Boolean.TRUE)
227             .and(optionConstraints.createConditionValueEquals(itemGlobalLayering, Boolean.TRUE)
228                 .or(optionConstraints.createConditionValueEquals(itemChildPlacement, VALUE_ALL_LEAVES_ON_SAME_LAYER)));
229     optionConstraints.setEnabledOnCondition(conditionBusAlignment, itemBusAlignment);
230 
231     //// Section 'Horizontal-Vertical'
232     options.useSection(SECTION_HV);
233     // Populate section
234     options.addInt(ITEM_HORIZONTAL_SPACE_HV, (int) defaultsHv.getHorizontalSpace());
235     options.addInt(ITEM_VERTICAL_SPACE_HV, (int) defaultsHv.getVerticalSpace());
236 
237     //// Section 'Compact'
238     options.useSection(SECTION_AR);
239     // Populate section
240     options.addInt(ITEM_HORIZONTAL_SPACE_AR, (int) defaultsAr.getHorizontalSpace());
241     options.addInt(ITEM_VERTICAL_SPACE_AR, (int) defaultsAr.getVerticalSpace());
242     options.addInt(ITEM_BEND_DISTANCE, (int) defaultsAr.getBendDistance());
243     final OptionItem itemUseViewAspectRatio = options.addBool(ITEM_USE_VIEW_ASPECT_RATIO, true);
244     final OptionItem itemAspectRatio = options.addDouble(ITEM_ASPECT_RATIO, defaultsAr.getAspectRatio());
245     // Enable/disable items depending on specific values
246     optionConstraints.setEnabledOnValueEquals(itemUseViewAspectRatio, Boolean.FALSE, itemAspectRatio);
247     
248     return options;
249   }
250 
251   /**
252    * Main module execution routine.
253    * Launches the module's underlying algorithm on the module's graph based on user options.
254    */
255   protected void mainrun() {
256     final CanonicMultiStageLayouter layouter;
257     
258     final OptionHandler options = getOptionHandler();
259     final Graph2D graph = getGraph2D();
260 
261     String style = options.getString(ITEM_LAYOUT_STYLE);
262     if (VALUE_HV.equals(style)) {
263       final HVTreeLayouter hv = new HVTreeLayouter();
264       configure(hv, options);
265       layouter = hv;
266     } else if (VALUE_AR.equals(style)) {
267       final ARTreeLayouter ar = new ARTreeLayouter();
268       configure(ar, options);
269       layouter = ar;
270     } else {
271       // VALUE_DIRECTED.equals(style)
272       final TreeLayouter tree = new TreeLayouter();
273       configure(tree, options);
274       layouter = tree;
275     }
276     
277     final TreeReductionStage trs = new TreeReductionStage();
278     configure(trs, options);
279 
280     //prepending is safer for edge label handling of non-tre edges
281     // the disadvantage is that parallel edges might not get handled by ParallelEdgeLayouter
282     layouter.prependStage(trs);
283     layouter.prependStage(new HandleEdgesBetweenGroupsStage()); //required to prevent WrongGraphStructure-Exception which may be thrown by TreeLayouter if there are edges between group nodes
284 
285     prepareGraph(graph, options);
286     try {
287       launchLayouter(layouter);
288     } finally {
289       restoreGraph(graph, options);
290     }
291   }
292 
293   /**
294    * Prepares a <code>graph</code> depending on the given options for the
295    * module's layout algorithm.
296    * <br>
297    * Additional resources created by this method have to be freed up by calling
298    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
299    * layout calculation.  
300    * @param graph the graph to be prepared
301    * @param options the options for the module's layout algorithm
302    */
303   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
304     String style = options.getString(ITEM_LAYOUT_STYLE);
305     if (VALUE_HV.equals(style)) {
306       // backup existing data providers to prevent loss of user settings
307       backupDataProvider(graph, HVTreeLayouter.SUBTREE_ORIENTATION);
308       graph.addDataProvider(HVTreeLayouter.SUBTREE_ORIENTATION, new DataProviderAdapter() {
309         public Object get(Object node) {
310           if (graph.isSelected((Node) node)) {
311             return HVTreeLayouter.VERTICAL_SUBTREE;
312           } else {
313             return HVTreeLayouter.HORIZONTAL_SUBTREE;
314           }
315         }
316       });
317       
318     } else if (VALUE_AR.equals(style)) {
319       // backup existing data providers to prevent loss of user settings
320       backupDataProvider(graph, ARTreeLayouter.ROUTING_POLICY);
321       graph.addDataProvider(ARTreeLayouter.ROUTING_POLICY, new DataProviderAdapter() {
322         public Object get( Object node ) {
323           if (graph.isSelected((Node) node)) {
324             return ARTreeLayouter.ROUTING_HORIZONTAL;
325           } else {
326             return ARTreeLayouter.ROUTING_VERTICAL;
327           }
328         }
329       });
330     }
331   }
332 
333   /**
334    * Restores the given <code>graph</code> by freeing up resources created by
335    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
336    * @param graph the graph for which <code>prepareGraph</code> has been called
337    * @param options the options for the module's layout algorithm
338    */
339   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
340     // remove the data providers set by this module by restoring the initial state
341     String style = options.getString(ITEM_LAYOUT_STYLE);
342     if (VALUE_HV.equals(style)) {
343       restoreDataProvider(graph, HVTreeLayouter.SUBTREE_ORIENTATION);
344     } else if (VALUE_AR.equals(style)) {
345       restoreDataProvider(graph, ARTreeLayouter.ROUTING_POLICY);
346     }
347   }
348 
349   /**
350    * Configures the module's layout algorithm according to the given options.
351    * @param tree the <code>TreeLayouter</code> to be configured
352    * @param options the layout options to set
353    */
354   protected void configure(TreeLayouter tree, final OptionHandler options) {
355     ((ComponentLayouter) tree.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
356 
357     tree.setMinimalNodeDistance(options.getInt(SECTION_DIRECTED, ITEM_MINIMAL_NODE_DISTANCE));
358     tree.setMinimalLayerDistance(options.getInt(SECTION_DIRECTED, ITEM_MINIMAL_LAYER_DISTANCE));
359 
360     final String orientation = options.getString(ITEM_ORIENTATION);
361     if (VALUE_TOP_TO_BOTTOM.equals(orientation)) {
362       tree.setLayoutOrientation(LayoutOrientation.TOP_TO_BOTTOM);
363     } else if (VALUE_BOTTOM_TO_TOP.equals(orientation)) {
364       tree.setLayoutOrientation(LayoutOrientation.BOTTOM_TO_TOP);
365     } else if (VALUE_RIGHT_TO_LEFT.equals(orientation)) {
366       tree.setLayoutOrientation(LayoutOrientation.RIGHT_TO_LEFT);
367     } else {
368       tree.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
369     }
370 
371     if (options.getBool(ITEM_ORTHOGONAL_EDGE_ROUTING)) {
372       tree.setLayoutStyle(TreeLayouter.ORTHOGONAL_STYLE);
373     } else {
374       tree.setLayoutStyle(TreeLayouter.PLAIN_STYLE);
375     }
376 
377     final String leafLayoutPolicyStr = options.getString(ITEM_CHILD_PLACEMENT_POLICY);
378     if (VALUE_SIBLINGS_ON_SAME_LAYER.equals(leafLayoutPolicyStr)) {
379       tree.setChildPlacementPolicy(TreeLayouter.CHILD_PLACEMENT_POLICY_SIBLINGS_ON_SAME_LAYER);
380     } else if (VALUE_LEAVES_STACKED_LEFT.equals(leafLayoutPolicyStr)) {
381       tree.setChildPlacementPolicy(TreeLayouter.CHILD_PLACEMENT_POLICY_LEAVES_STACKED_LEFT);
382     } else if (VALUE_LEAVES_STACKED_RIGHT.equals(leafLayoutPolicyStr)) {
383       tree.setChildPlacementPolicy(TreeLayouter.CHILD_PLACEMENT_POLICY_LEAVES_STACKED_RIGHT);
384     } else if (VALUE_LEAVES_STACKED.equals(leafLayoutPolicyStr)) {
385       tree.setChildPlacementPolicy(TreeLayouter.CHILD_PLACEMENT_POLICY_LEAVES_STACKED);
386     } else if (VALUE_ALL_LEAVES_ON_SAME_LAYER.equals(leafLayoutPolicyStr)) {
387       tree.setChildPlacementPolicy(TreeLayouter.CHILD_PLACEMENT_POLICY_ALL_LEAVES_ON_SAME_LAYER);
388     }
389 
390     if (options.getBool(ITEM_ENFORCE_GLOBAL_LAYERING)) {
391       tree.setEnforceGlobalLayering(true);
392     } else {
393       tree.setEnforceGlobalLayering(false);
394     }
395 
396     final String portStyle = options.getString(ITEM_PORT_STYLE);
397     if (VALUE_NODE_CENTER_PORTS.equals(portStyle)) {
398       tree.setPortStyle(TreeLayouter.NODE_CENTER_PORTS);
399     } else if (VALUE_BORDER_CENTER_PORTS.equals(portStyle)) {
400       tree.setPortStyle(TreeLayouter.BORDER_CENTER_PORTS);
401     } else if (VALUE_BORDER_DISTRIBUTED_PORTS.equals(portStyle)) {
402       tree.setPortStyle(TreeLayouter.BORDER_DISTRIBUTED_PORTS);
403     } else {
404       tree.setPortStyle(TreeLayouter.PORT_CONSTRAINTS_AWARE);
405     }
406 
407     tree.setIntegratedNodeLabelingEnabled(options.getBool(SECTION_DIRECTED, ITEM_INTEGRATED_NODE_LABELING));
408     tree.setIntegratedEdgeLabelingEnabled(options.getBool(SECTION_DIRECTED, ITEM_INTEGRATED_EDGE_LABELING));
409 
410     final String verticalAlignment = options.getString(ITEM_VERTICAL_ALIGNMENT);
411     if (VALUE_ALIGNMENT_TOP.equals(verticalAlignment)) {
412       tree.setVerticalAlignment(0);
413     } else if (VALUE_ALIGNMENT_BOTTOM.equals(verticalAlignment)) {
414       tree.setVerticalAlignment(1);
415     } else {
416       tree.setVerticalAlignment(0.5); //center aligned
417     }
418 
419     final String busAlignment = options.getString(ITEM_BUS_ALIGNMENT);
420     if (VALUE_ALIGNMENT_TOP.equals(busAlignment)) {
421       tree.setBusAlignment(0.1);
422     } else if (VALUE_ALIGNMENT_BOTTOM.equals(busAlignment)) {
423       tree.setBusAlignment(0.9);
424     } else {
425       tree.setBusAlignment(0.5); //center aligned
426     }
427 
428     tree.setSubgraphLayouterEnabled(options.getBool(ITEM_ACT_ON_SELECTION_ONLY));
429   }
430 
431   /**
432    * Configures the module's layout algorithm according to the given options.
433    * @param ar the <code>ARTreeLayouter</code> to be configured
434    * @param options the options for the module's layout algorithm
435    */
436   protected void configure(final ARTreeLayouter ar, final OptionHandler options) {
437     if (options.getBool(ITEM_USE_VIEW_ASPECT_RATIO)) {
438       Graph2DView view = getGraph2DView();
439       if (view != null) {
440         Dimension dim = view.getSize();
441         ar.setAspectRatio(dim.getWidth() / (double) dim.getHeight());
442       } else {
443         ar.setAspectRatio(1);
444       }
445     } else {
446       ar.setAspectRatio(options.getDouble(ITEM_ASPECT_RATIO));
447     }
448     ar.setHorizontalSpace(options.getInt(SECTION_AR, ITEM_HORIZONTAL_SPACE_AR));
449     ar.setVerticalSpace(options.getInt(SECTION_AR, ITEM_VERTICAL_SPACE_AR));
450     ar.setBendDistance(options.getInt(SECTION_AR, ITEM_BEND_DISTANCE));
451     ar.setSubgraphLayouterEnabled(options.getBool(ITEM_ACT_ON_SELECTION_ONLY));
452   }
453 
454   /**
455    * This method configures the modules underlying algorithm
456    * with options found in the given <code>OptionHandler</code>.
457    * @param hv the <code>HVTreeLayouter</code> to be configured
458    * @param options the options for the module's layout algorithm
459    */
460   protected void configure(HVTreeLayouter hv, OptionHandler options) {
461     hv.setHorizontalSpace(options.getInt(SECTION_HV, ITEM_HORIZONTAL_SPACE_HV));
462     hv.setVerticalSpace(options.getInt(SECTION_HV, ITEM_VERTICAL_SPACE_HV));
463     hv.setSubgraphLayouterEnabled(options.getBool(ITEM_ACT_ON_SELECTION_ONLY));
464   }
465 
466 
467   /**
468    * Configures the module's layout algorithm according to the given options.
469    * @param reduction the <code>TreeReductionStage</code> to be configured
470    * @param options the options for the module's layout algorithm
471    */
472   protected void configure(final TreeReductionStage reduction, final OptionHandler options) {
473     //configure tree reduction state and non-tree edge routing
474     reduction.setMultiParentAllowed(
475             VALUE_DIRECTED.equals(options.getString(ITEM_LAYOUT_STYLE)) &&
476             !options.getBool(ITEM_ENFORCE_GLOBAL_LAYERING) &&
477             !VALUE_ALL_LEAVES_ON_SAME_LAYER.equals(options.getString(ITEM_CHILD_PLACEMENT_POLICY)));
478 
479     final Object routingStyle = options.get(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES);
480     if (VALUE_ROUTE_ORGANIC.equals(routingStyle)) {
481       OrganicEdgeRouter organic = new OrganicEdgeRouter();
482       reduction.setNonTreeEdgeRouter(organic);
483       reduction.setNonTreeEdgeSelectionKey(OrganicEdgeRouter.ROUTE_EDGE_DPKEY);
484     } else if (VALUE_ROUTE_ORTHOGONAL.equals(routingStyle)) {
485       EdgeRouter orthogonal = new EdgeRouter();
486       orthogonal.setReroutingEnabled(true);
487       orthogonal.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
488 
489       reduction.setNonTreeEdgeSelectionKey(orthogonal.getSelectedEdgesDpKey());
490       reduction.setNonTreeEdgeRouter(orthogonal);
491     } else if (VALUE_ROUTE_STRAIGHTLINE.equals(routingStyle)) {
492       reduction.setNonTreeEdgeRouter(reduction.createStraightlineRouter());
493     } else if (VALUE_ROUTE_BUNDLED.equals(routingStyle)){
494       
495       // Edge Bundling
496       final EdgeBundling ebc = reduction.getEdgeBundling();
497       final EdgeBundleDescriptor descriptor = new EdgeBundleDescriptor();
498       descriptor.setBundled(true);
499       ebc.setDefaultBundleDescriptor(descriptor);
500       ebc.setBundlingStrength(options.getDouble(EDGE_BUNDLING_STRENGTH));
501 
502       // Sets a new straight-line router in case some edges are not bundled, e.g. self-loops
503       OrganicEdgeRouter organic = new OrganicEdgeRouter();
504       reduction.setNonTreeEdgeRouter(organic);
505       reduction.setNonTreeEdgeSelectionKey(OrganicEdgeRouter.ROUTE_EDGE_DPKEY);
506     }
507 
508     if (options.getBool(ITEM_INTEGRATED_EDGE_LABELING)) {
509       //if integrated edge labeling is enabled, we set generic labeling as the non-tree edge labeling algorithm
510       final GreedyMISLabeling labeling = new GreedyMISLabeling();
511       labeling.setOptimizationStrategy(MISLabelingAlgorithm.OPTIMIZATION_BALANCED);
512       labeling.setPlaceNodeLabels(false);
513       labeling.setPlaceEdgeLabels(true);
514       labeling.setSelection("NonTreeEdgeLabelsSelectionDp");
515       reduction.setNonTreeEdgeLabelingAlgorithm(labeling);
516       reduction.setNonTreeEdgeLabelSelectionKey(labeling.getSelectionKey());
517     }
518   }
519 
520   /**
521    * This stage temporarily removes edges that are incident to group nodes.
522    * <p>
523    *   The stage must be prepended to the layout algorithm and applies the following three steps:
524    *   <ul>
525    *     <li>Removes from the graph edges that are incident to group nodes.</li>
526    *     <li>Invokes the core layout algorithm on the reduced graph.</li>
527    *     <li>Re-inserts all previously removed edges and optionally places their labels.</li>
528    *   </ul>
529    * </p>
530    * <p>
531    *   Typical usage:
532    *   <pre>
533    *     GenericTreeLayouter tl = new GenericTreeLayouter();
534    *     HandleEdgesBetweenGroupsStage trs = new HandleEdgesBetweenGroupsStage();
535    *
536    *     tl.prependStage(trs);
537    *     new BufferedLayouter(tl).doLayout(graph);
538    *     tl.removeStage(trs);
539    *   </pre>
540    * </p>
541    * <p>
542    *   This stage can be useful for layout algorithms or stages that cannot handle edges between group nodes e.g.,
543    *   {@link TreeReductionStage}. Optionally, {@link HandleEdgesBetweenGroupsStage} can also place the labels of
544    *   the edges that were temporarily removed right after they are restored back to the graph.
545    * </p>
546    * <p>
547    *   The routing of the temporarily hidden edges can be customized by specifying an
548    *   {@link #getMarkedEdgeRouter() edge routing algorithm} for those edges.
549    * </p>
550    */
551   private static class HandleEdgesBetweenGroupsStage extends AbstractLayoutStage {
552 
553     private boolean considerEdgeLabels = true;
554 
555     private Layouter markedEdgeRouter;
556     private Object edgeSelectionKey;
557 
558     /**
559      * Creates a new instance of {@link HandleEdgesBetweenGroupsStage} with default settings.
560      */
561     public HandleEdgesBetweenGroupsStage() {
562       markedEdgeRouter = new StraightLineEdgeRouter();
563       edgeSelectionKey = StraightLineEdgeRouter.SELECTED_EDGES;
564     }
565 
566     /**
567      * Returns whether or not the {@link y.layout.LayoutStage} should place the labels of the edges that have been temporarily
568      * hidden, when these edges will be restored back.
569      *
570      * @return <code>true</code> if the stage should also place the labels of previously hidden edges,
571      * <code>false</code> otherwise
572      *
573      * @see #setConsiderEdgeLabelsEnabled(boolean)
574      *
575      */
576     public boolean isConsiderEdgeLabelsEnabled() {
577       return considerEdgeLabels;
578     }
579 
580     /**
581      * Specifies whether or not the {@link y.layout.LayoutStage} should place the labels of the edges that have been temporarily
582      * hidden, when these edges will be restored back.
583      * <p>
584      *   By default, labels of previously hidden edges will be placed by the stage at the time they get restored.
585      * </p>
586      *
587      * @param considerEdgeLabels <code>true</code> if the stage should also place the labels of the previously removed edges,
588      * <code>false</code> otherwise
589      *
590      */
591     public void setConsiderEdgeLabelsEnabled( final boolean considerEdgeLabels ) {
592       this.considerEdgeLabels = considerEdgeLabels;
593     }
594 
595     /**
596      * Returns the key to register a {@link DataProvider} that will be used by the
597      * {@link #getMarkedEdgeRouter() edge routing algorithm} to determine the edges that need to be routed.
598      *
599      * @return the {@link DataProvider} key
600      *
601      * @see #setMarkedEdgeRouter(Layouter)
602      * @see #setEdgeSelectionKey(Object)
603      */
604     public Object getEdgeSelectionKey() {
605       return edgeSelectionKey;
606     }
607 
608 
609     /**
610      * Specifies the key to register a {@link DataProvider} that will be used by the
611      * {@link #getMarkedEdgeRouter() edge routing algorithm} to determine the edges that need to be routed.
612      *
613      * @param edgeSelectionKey the {@link DataProvider} key
614      *
615      * @see #setMarkedEdgeRouter(Layouter)
616      */
617     public void setEdgeSelectionKey( final Object edgeSelectionKey ) {
618       this.edgeSelectionKey = edgeSelectionKey;
619     }
620 
621     /**
622      * Returns the edge routing algorithm that is applied to the set of marked edges.
623      *
624      * Note that, it is required that a suitable {@link #setEdgeSelectionKey(Object) edge selection key} is specified
625      *         and the router's scope is reduced to the selected edges.
626      *
627      * @return the edge routing algorithm used for marked edges
628      *
629      * @see #setMarkedEdgeRouter(Layouter)
630      * @see #setEdgeSelectionKey(Object)
631      */
632     public Layouter getMarkedEdgeRouter() {
633       return markedEdgeRouter;
634     }
635 
636     /**
637      * Specifies the edge routing algorithm that is applied to the set of marked edges.
638      *
639      * Note that, it is required that a suitable {@link #setEdgeSelectionKey(Object) edge selection key} is specified
640      *         and the router's scope is reduced to the selected edges.
641      *
642      * @param markedEdgeRouter the edge routing algorithm used for marked edges
643      *
644      * @see #setMarkedEdgeRouter(Layouter)
645      */
646     public void setMarkedEdgeRouter( final Layouter markedEdgeRouter ) {
647       this.markedEdgeRouter = markedEdgeRouter;
648     }
649 
650     /**
651      * Accepts all general graphs without exception.
652      *
653      * @param graph the input graph
654      *
655      * @return <code>true</code> for all input graphs
656      */
657     public boolean canLayout(LayoutGraph graph) {
658       return true;
659     }
660 
661     /**
662      * Removes all edges that are incident to group nodes and passes it to the {@link #getCoreLayouter() core layout
663      * algorithm}.
664      * <p>
665      *   This {@link y.layout.LayoutStage} removes some edges from the graph such that no edges incident to group nodes
666      *   exist. Then, it applies the {@link #getCoreLayouter() core layout algorithm} to the reduced graph.
667      *   After it produces the result, it re-inserts the previously removed edges and routes them.
668      * </p>
669      *
670      * @param graph the input graph
671      */
672     public void doLayout( final LayoutGraph graph ) {
673 
674       final boolean existGroups = !Grouping.isFlat(graph);
675 
676       if (!existGroups){
677         // if no group exist, invoke the core layout algorithm
678         doLayoutCore(graph);
679       } else {
680         final Grouping grouping = new Grouping(graph);
681         final GraphHider edgeHider = new GraphHider(graph);
682 
683         // marks hidden edges
684         final EdgeMap hiddenEdgesMap = graph.createEdgeMap();
685 
686         boolean existHiddenEdges = false;
687         // hides edges between group nodes
688         for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
689           final Edge edge = ec.edge();
690 
691           if (grouping.isGroupNode(edge.source()) || grouping.isGroupNode(edge.target())){
692             hiddenEdgesMap.setBool(edge, true);
693             edgeHider.hide(edge);
694             if (!existHiddenEdges){
695               existHiddenEdges = true;
696             }
697           }
698         }
699 
700         // invokes the core layout algorithm
701         doLayoutCore(graph);
702 
703         if (existHiddenEdges) {
704           // re-inserts all previously hidden edges
705           edgeHider.unhideAll();
706 
707           // routes the marked edges
708           routeSelectedEdges(graph, hiddenEdgesMap);
709 
710           if (considerEdgeLabels) {
711             //all labels of hidden edges should be marked
712             final Object selectionKey = "SELECTED_LABELS";
713             graph.addDataProvider(selectionKey, new DataProviderAdapter() {
714               public boolean getBool( Object dataHolder ) {
715                 if (dataHolder instanceof EdgeLabelLayout) {
716                   final EdgeLabelLayout labelLayout = (EdgeLabelLayout) dataHolder;
717                   final Edge relatedEdge = graph.getFeature(labelLayout);
718                   return hiddenEdgesMap.getBool(relatedEdge);
719                 } else {
720                   return false;
721                 }
722               }
723             });
724 
725             //place marked labels
726             final GreedyMISLabeling labeling = new GreedyMISLabeling();
727             labeling.setOptimizationStrategy(MISLabelingAlgorithm.OPTIMIZATION_BALANCED);
728             labeling.setPlaceNodeLabels(false);
729             labeling.setPlaceEdgeLabels(true);
730             labeling.setSelection(selectionKey);
731             labeling.doLayout(graph);
732 
733             //dispose selection key
734             graph.removeDataProvider(selectionKey);
735           }
736         }
737 
738         graph.disposeEdgeMap(hiddenEdgesMap);
739         grouping.dispose();
740       }
741     }
742 
743     /**
744      * Routes all edges that are temporarily hidden by this {@link y.layout.LayoutStage}.
745      *
746      * <p>
747      *   This method is called by {@link #doLayout(LayoutGraph)} after the reduced graph was arranged by the
748      *   {@link #getCoreLayouter() core layout algorithm}. It may be overridden to apply custom edge routes.
749      * </p>
750      *
751      * Note that, this method will do nothing if no {@link #getMarkedEdgeRouter() edge routing algorithm}
752      *            was specified (i.e., if it is <code>null</code>).
753      *
754      * @param graph the graph that contains the hidden edges
755      * @param markedEdgeMap an {@link EdgeMap} that returns boolean value <code>true</code>
756      *                      for all hidden by the stage edges of the graph
757      */
758     protected void routeSelectedEdges(LayoutGraph graph, EdgeMap markedEdgeMap) {
759       if (markedEdgeRouter == null) return;
760 
761       DataProvider backupDP = null;
762       if (edgeSelectionKey != null) {
763         backupDP = graph.getDataProvider(edgeSelectionKey);
764         graph.addDataProvider(edgeSelectionKey, markedEdgeMap);
765       }
766       if (markedEdgeRouter instanceof StraightLineEdgeRouter) {
767         final StraightLineEdgeRouter router = (StraightLineEdgeRouter) markedEdgeRouter;
768         router.setSphereOfAction(StraightLineEdgeRouter.ROUTE_SELECTED_EDGES);
769         router.setSelectedEdgesDpKey(edgeSelectionKey);
770       }
771 
772       markedEdgeRouter.doLayout(graph);
773 
774       graph.removeDataProvider(edgeSelectionKey);
775       if (backupDP != null) {
776         graph.addDataProvider(edgeSelectionKey, backupDP);
777       }
778     }
779   }
780 }
781