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.EdgeCursor;
34  import y.base.NodeCursor;
35  import y.layout.ComponentLayouter;
36  import y.layout.EdgeBundleDescriptor;
37  import y.layout.EdgeBundling;
38  import y.layout.EdgeLabelModel;
39  import y.layout.FreeEdgeLabelModel;
40  import y.layout.FreeNodeLabelModel;
41  import y.layout.NodeLabelModel;
42  import y.layout.router.OrganicEdgeRouter;
43  import y.layout.router.polyline.EdgeRouter;
44  import y.layout.tree.BalloonLayouter;
45  import y.layout.tree.TreeReductionStage;
46  import y.option.ConstraintManager;
47  import y.option.EnumOptionItem;
48  import y.option.OptionHandler;
49  import y.option.OptionItem;
50  import y.view.EdgeLabel;
51  import y.view.EdgeRealizer;
52  import y.view.Graph2D;
53  import y.view.NodeLabel;
54  import y.view.NodeRealizer;
55  import y.view.Selections;
56  import y.view.SmartEdgeLabelModel;
57  import y.view.SmartNodeLabelModel;
58  
59  /**
60   * This module represents an interactive configurator and launcher for {@link y.layout.tree.BalloonLayouter}.
61   *
62   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/tree_layouter#tree_layouter" target="_blank">Section Tree
63   *      Layout</a> in the yFiles for Java Developer's Guide
64   */
65  public class BalloonLayoutModule extends LayoutModule {
66    //// Module 'Balloon Layout'
67    protected static final String MODULE_BALLOON = "BALLOON";
68  
69    //// Section 'General'
70    protected static final String SECTION_GENERAL = "GENERAL";
71    // Section 'General' items
72    protected static final String ITEM_ROOT_NODE_POLICY = "ROOT_NODE_POLICY";
73    protected static final String VALUE_DIRECTED_ROOT = "DIRECTED_ROOT";
74    protected static final String VALUE_CENTER_ROOT = "CENTER_ROOT";
75    protected static final String VALUE_SELECTED_ROOT = "SELECTED_ROOT";
76    protected static final String VALUE_WEIGHTED_CENTER_ROOT = "WEIGHTED_CENTER_ROOT";
77    protected static final String ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES = "ROUTING_STYLE_FOR_NON_TREE_EDGES";
78    protected static final String VALUE_ROUTE_ORGANIC = "ROUTE_ORGANIC";
79    protected static final String VALUE_ROUTE_ORTHOGONAL = "ROUTE_ORTHOGONAL";
80    protected static final String VALUE_ROUTE_STRAIGHTLINE = "ROUTE_STRAIGHTLINE";
81    protected static final String VALUE_ROUTE_BUNDLED = "ROUTE_BUNDLED";
82    protected static final String ITEM_ACT_ON_SELECTION_ONLY = "ACT_ON_SELECTION_ONLY";
83    protected static final String ITEM_PREFERRED_CHILD_WEDGE = "PREFERRED_CHILD_WEDGE";
84    protected static final String ITEM_PREFERRED_ROOT_WEDGE = "PREFERRED_ROOT_WEDGE";
85    protected static final String ITEM_MINIMAL_EDGE_LENGTH = "MINIMAL_EDGE_LENGTH";
86    protected static final String ITEM_COMPACTNESS_FACTOR = "COMPACTNESS_FACTOR";
87    protected static final String ITEM_ALLOW_OVERLAPS = "ALLOW_OVERLAPS";
88    protected static final String ITEM_BALLOON_FROM_SKETCH = "FROM_SKETCH";
89    protected static final String ITEM_PLACE_CHILDREN_INTERLEAVED = "PLACE_CHILDREN_INTERLEAVED";
90    protected static final String ITEM_STRAIGHTEN_CHAINS = "STRAIGHTEN_CHAINS";
91    protected static final String EDGE_BUNDLING_STRENGTH = "EDGE_BUNDLING_STRENGTH";
92    //// Section 'Labeling'
93    protected static final String SECTION_LABELING = "LABELING";
94    // Section 'Labeling' items
95    protected static final String ITEM_NODE_LABELING_STYLE = "NODE_LABELING_STYLE";
96    protected static final String VALUE_NODE_LABELING_STYLE_NONE = "NODE_LABELING_STYLE_NONE";
97    protected static final String VALUE_NODE_LABELING_STYLE_HORIZONTAL = "NODE_LABELING_STYLE_HORIZONTAL";
98    protected static final String VALUE_NODE_LABELING_STYLE_RAYLIKE_LEAVES = "NODE_LABELING_STYLE_RAYLIKE_LEAVES";
99    protected static final String VALUE_NODE_LABELING_STYLE_CONSIDER_CURRENT_POSITION = "NODE_LABELING_STYLE_CONSIDER_CURRENT_POSITION";
100   protected static final String ITEM_INTEGRATED_EDGE_LABELING = "INTEGRATED_EDGE_LABELING";
101   /**
102    * Creates an instance of this module.
103    */
104   public BalloonLayoutModule() {
105     super(MODULE_BALLOON);
106     setPortIntersectionCalculatorEnabled(true);
107   }
108 
109   /**
110    * Creates an OptionHandler and adds the option items used by this module.
111    * @return the created <code>OptionHandler</code> providing module related options
112    */
113   protected OptionHandler createOptionHandler() {
114     final OptionHandler options = new OptionHandler(getModuleName());
115     // Defaults provider
116     final BalloonLayouter defaults = new BalloonLayouter();
117 
118     //// Section 'General'
119     options.useSection(SECTION_GENERAL);
120     // Populate section
121     options.addEnum(ITEM_ROOT_NODE_POLICY, new String[]{
122         VALUE_DIRECTED_ROOT,
123         VALUE_CENTER_ROOT,
124         VALUE_WEIGHTED_CENTER_ROOT,
125         VALUE_SELECTED_ROOT
126     }, 0);
127     final EnumOptionItem itemNonTreeEdgeRouting = options.addEnum(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES, new String[]{
128         VALUE_ROUTE_ORGANIC,
129         VALUE_ROUTE_ORTHOGONAL,
130         VALUE_ROUTE_STRAIGHTLINE,
131         VALUE_ROUTE_BUNDLED
132     }, 0);
133 
134     final OptionItem itemBundlingStrength = options.addDouble(EDGE_BUNDLING_STRENGTH, 0.99, 0.0, 1.0);
135 
136     final ConstraintManager optionConstraints = new ConstraintManager(options);
137     final ConstraintManager.Condition bundlingCondition =
138             optionConstraints.createConditionValueEquals(itemNonTreeEdgeRouting, VALUE_ROUTE_BUNDLED);
139     optionConstraints.setEnabledOnCondition(bundlingCondition, itemBundlingStrength);
140 
141     options.addBool(ITEM_ACT_ON_SELECTION_ONLY, false);
142     options.addInt(ITEM_PREFERRED_CHILD_WEDGE, defaults.getPreferredChildWedge(), 1, 359);
143     options.addInt(ITEM_PREFERRED_ROOT_WEDGE, defaults.getPreferredRootWedge(), 1, 360);
144     options.addInt(ITEM_MINIMAL_EDGE_LENGTH, defaults.getMinimalEdgeLength(), 10, 400);
145     options.addDouble(ITEM_COMPACTNESS_FACTOR, defaults.getCompactnessFactor(), 0.1, 0.9);
146     options.addBool(ITEM_ALLOW_OVERLAPS, defaults.getAllowOverlaps());
147     options.addBool(ITEM_BALLOON_FROM_SKETCH, defaults.isFromSketchModeEnabled());
148     options.addBool(ITEM_PLACE_CHILDREN_INTERLEAVED,
149         defaults.getInterleavedMode() == BalloonLayouter.INTERLEAVED_MODE_ALL_NODES);
150     options.addBool(ITEM_STRAIGHTEN_CHAINS, defaults.isChainStraighteningModeEnabled());
151 
152     //// Section 'Labeling'
153     options.useSection(SECTION_LABELING);
154     // Populate section
155     options.addBool(ITEM_INTEGRATED_EDGE_LABELING, true);
156     options.addEnum(ITEM_NODE_LABELING_STYLE, new String[]{
157         VALUE_NODE_LABELING_STYLE_NONE,
158         VALUE_NODE_LABELING_STYLE_HORIZONTAL,
159         VALUE_NODE_LABELING_STYLE_RAYLIKE_LEAVES,
160         VALUE_NODE_LABELING_STYLE_CONSIDER_CURRENT_POSITION
161     }, 3);
162     
163     return options;
164   }
165 
166   /**
167    * Main module execution routine.
168    * Launches the module's underlying algorithm on the module's graph based on user options.
169    */
170   protected void mainrun() {
171     final BalloonLayouter balloon = new BalloonLayouter();
172     
173     final OptionHandler options = getOptionHandler();
174     configure(balloon, options);
175 
176     final Graph2D graph = getGraph2D();
177     prepareGraph(graph, options);
178     launchLayouter(balloon);
179     restoreGraph(graph, options);
180   }
181 
182   /**
183    * Sets edge and node label models depending on the given options.
184    * <p>
185    * Also adds the {@link y.base.DataProvider} for marking the selected node as root of the tree 
186    * if the root policy is set to {@link y.layout.tree.BalloonLayouter#SELECTED_ROOT}. 
187    * </p>
188    * @param graph the graph to be prepared
189    * @param options the options for the module's layout algorithm
190    */
191   private void prepareGraph(final Graph2D graph, final OptionHandler options) {
192     if (options.getBool(ITEM_INTEGRATED_EDGE_LABELING)) {
193       setupEdgeLabelModel(graph);
194     }
195     if (VALUE_NODE_LABELING_STYLE_RAYLIKE_LEAVES.equals(options.getString(ITEM_NODE_LABELING_STYLE))) {
196       setupNodeLabelModel(graph);
197     }
198     if (options.get(ITEM_ROOT_NODE_POLICY).equals(VALUE_SELECTED_ROOT)){
199       // backup existing data providers to prevent loss of user settings
200       backupDataProvider(graph, BalloonLayouter.SELECTED_ROOT_DPKEY);
201       graph.addDataProvider(BalloonLayouter.SELECTED_ROOT_DPKEY, Selections.createSelectionNodeMap(graph));
202     }
203   }
204 
205   /**
206    * Restores the given <code>graph</code> by freeing up resources created by
207    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
208    * @param graph the graph for which <code>prepareGraph</code> has been called
209    * @param options the options for the module's layout algorithm
210    */
211   protected void restoreGraph(Graph2D graph, OptionHandler options) {
212     if (options.get(ITEM_ROOT_NODE_POLICY).equals(VALUE_SELECTED_ROOT)){
213       restoreDataProvider(graph, BalloonLayouter.SELECTED_ROOT_DPKEY);
214     }
215   }
216 
217   /**
218    * Configures the module's layout algorithm according to the given options.
219    * @param balloon the <code>BalloonLayouter</code> to be configured
220    * @param options the layout options to set
221    */
222   protected void configure(final BalloonLayouter balloon, final OptionHandler options) {
223     ((ComponentLayouter) balloon.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
224 
225     if (options.get(ITEM_ROOT_NODE_POLICY).equals(VALUE_DIRECTED_ROOT)) {
226       balloon.setRootNodePolicy(BalloonLayouter.DIRECTED_ROOT);
227     } else if (options.get(ITEM_ROOT_NODE_POLICY).equals(VALUE_CENTER_ROOT)) {
228       balloon.setRootNodePolicy(BalloonLayouter.CENTER_ROOT);
229     } else if (options.get(ITEM_ROOT_NODE_POLICY).equals(VALUE_SELECTED_ROOT)){
230       balloon.setRootNodePolicy(BalloonLayouter.SELECTED_ROOT);
231     } else {
232       balloon.setRootNodePolicy(BalloonLayouter.WEIGHTED_CENTER_ROOT);
233     }
234 
235     balloon.setPreferredChildWedge(options.getInt(ITEM_PREFERRED_CHILD_WEDGE));
236     balloon.setPreferredRootWedge(options.getInt(ITEM_PREFERRED_ROOT_WEDGE));
237     balloon.setMinimalEdgeLength(options.getInt(ITEM_MINIMAL_EDGE_LENGTH));
238     balloon.setCompactnessFactor(options.getDouble(ITEM_COMPACTNESS_FACTOR));
239     balloon.setAllowOverlaps(options.getBool(ITEM_ALLOW_OVERLAPS));
240     balloon.setFromSketchModeEnabled(options.getBool(ITEM_BALLOON_FROM_SKETCH));
241     
242     if (options.getBool(ITEM_INTEGRATED_EDGE_LABELING)) {
243       balloon.setIntegratedEdgeLabelingEnabled(true);
244     } else {
245       balloon.setIntegratedEdgeLabelingEnabled(false);
246     }
247     
248     balloon.setChainStraighteningModeEnabled(options.getBool(ITEM_STRAIGHTEN_CHAINS));
249     balloon.setInterleavedMode(options.getBool(ITEM_PLACE_CHILDREN_INTERLEAVED)
250         ? BalloonLayouter.INTERLEAVED_MODE_ALL_NODES : BalloonLayouter.INTERLEAVED_MODE_OFF);
251     balloon.setIntegratedNodeLabelingEnabled(false);
252     balloon.setConsiderNodeLabelsEnabled(false);
253     
254     final String nodeLabelingStyle = options.getString(ITEM_NODE_LABELING_STYLE);
255     if (VALUE_NODE_LABELING_STYLE_RAYLIKE_LEAVES.equals(nodeLabelingStyle)) {
256       balloon.setIntegratedNodeLabelingEnabled(true);
257       balloon.setNodeLabelingPolicy(BalloonLayouter.NODE_LABELING_MIXED);
258     } else if (VALUE_NODE_LABELING_STYLE_CONSIDER_CURRENT_POSITION.equals(nodeLabelingStyle)) {
259       balloon.setConsiderNodeLabelsEnabled(true);
260     } else if (VALUE_NODE_LABELING_STYLE_HORIZONTAL.equals(nodeLabelingStyle)) {
261       balloon.setIntegratedNodeLabelingEnabled(true);
262       balloon.setNodeLabelingPolicy(BalloonLayouter.NODE_LABELING_HORIZONTAL);
263     }
264 
265     balloon.setSubgraphLayouterEnabled(options.getBool(ITEM_ACT_ON_SELECTION_ONLY));
266 
267     //configure tree reduction stage
268     final TreeReductionStage trs = new TreeReductionStage();
269     balloon.appendStage(trs);
270     if (VALUE_ROUTE_ORGANIC.equals(options.get(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES))) {
271       trs.setNonTreeEdgeRouter(new OrganicEdgeRouter());
272       trs.setNonTreeEdgeSelectionKey(OrganicEdgeRouter.ROUTE_EDGE_DPKEY);
273     } else if (VALUE_ROUTE_ORTHOGONAL.equals(options.get(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES))) {
274       final EdgeRouter orthogonal = new EdgeRouter();
275       orthogonal.setReroutingEnabled(true);
276       orthogonal.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
277       trs.setNonTreeEdgeSelectionKey(orthogonal.getSelectedEdgesDpKey());
278       trs.setNonTreeEdgeRouter(orthogonal);
279     } else if (VALUE_ROUTE_STRAIGHTLINE.equals(options.get(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES))) {
280       trs.setNonTreeEdgeRouter(trs.createStraightlineRouter());
281     } else if (VALUE_ROUTE_BUNDLED.equals(options.get(ITEM_ROUTING_STYLE_FOR_NON_TREE_EDGES))){
282       // Edge Bundling
283       final EdgeBundling ebc = trs.getEdgeBundling();
284       final EdgeBundleDescriptor descriptor = new EdgeBundleDescriptor();
285       descriptor.setBundled(true);
286       ebc.setDefaultBundleDescriptor(descriptor);
287       ebc.setBundlingStrength(options.getDouble(EDGE_BUNDLING_STRENGTH));
288 
289       // Sets a new straight-line router in case some edges are not bundled, e.g. self-loops
290       trs.setNonTreeEdgeRouter(trs.createStraightlineRouter());
291     }
292   }
293 
294   /**
295    * Guarantees that all edge labels have a free edge label model.
296    */
297   private void setupEdgeLabelModel(final Graph2D graph) {
298     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
299       final EdgeRealizer er = graph.getRealizer(ec.edge());
300       for (int i = 0; i < er.labelCount(); i++) {
301         final EdgeLabel el = er.getLabel(i);
302         final EdgeLabelModel labelModel = el.getLabelModel();
303         if (!isFreeModel(labelModel)) {
304           //the free model that is set if an edge label has a non-free model
305           final SmartEdgeLabelModel defaultLabelModel = new SmartEdgeLabelModel(); 
306           el.setLabelModel(defaultLabelModel, defaultLabelModel.getDefaultParameter());
307         }
308       }
309     }
310   }
311 
312   private static boolean isFreeModel(final EdgeLabelModel model) {
313     return (model instanceof FreeEdgeLabelModel) || (model instanceof SmartEdgeLabelModel);
314   }
315 
316   /** Guarantees that all node labels have a free node label model. */
317   private void setupNodeLabelModel(final Graph2D graph) {
318     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
319       final NodeRealizer nr = graph.getRealizer(nc.node());
320       for (int i = 0; i < nr.labelCount(); i++) {
321         final NodeLabel nl = nr.getLabel(i);
322         final NodeLabelModel labelModel = nl.getLabelModel();
323         if (!isFreeModel(labelModel)) {
324           //the free model that is set if a node label has a non-free model
325           final SmartNodeLabelModel defaultLabelModel = new SmartNodeLabelModel();
326           nl.setLabelModel(defaultLabelModel, defaultLabelModel.getDefaultParameter());
327         }
328       }
329     }
330   }
331 
332   private static boolean isFreeModel(final NodeLabelModel model) {
333     return (model instanceof FreeNodeLabelModel) || (model instanceof SmartNodeLabelModel);
334   }
335 }
336