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.Edge;
34  import y.base.EdgeCursor;
35  import y.layout.CanonicMultiStageLayouter;
36  import y.layout.ComponentLayouter;
37  import y.layout.LabelLayoutConstants;
38  import y.layout.LabelRanking;
39  import y.layout.PreferredPlacementDescriptor;
40  import y.layout.grouping.FixedGroupLayoutStage;
41  import y.layout.labeling.GreedyMISLabeling;
42  import y.layout.orthogonal.EdgeLayoutDescriptor;
43  import y.layout.orthogonal.OrthogonalGroupLayouter;
44  import y.layout.orthogonal.OrthogonalLayouter;
45  import y.option.ConstraintManager;
46  import y.option.EnumOptionItem;
47  import y.option.IntOptionItem;
48  import y.option.OptionHandler;
49  import y.option.ConstraintManager.Condition;
50  import y.option.OptionItem;
51  import y.view.EdgeLabel;
52  import y.view.EdgeRealizer;
53  import y.view.Graph2D;
54  import y.view.hierarchy.HierarchyManager;
55  
56  
57  /**
58   * This module represents an interactive configurator and launcher for
59   * {@link y.layout.orthogonal.OrthogonalLayouter}
60   * and {@link y.layout.orthogonal.OrthogonalGroupLayouter} respectively.
61   *
62   *
63   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/layout_advanced_features#layout_advanced_features" target="_blank">Section Advanced Layout Concepts</a> in the yFiles for Java Developer's Guide
64   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/layout_stages#layout_stages" target="_blank">Section Layout Stages</a> in the yFiles for Java Developer's Guide
65   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/orthogonal_group_layouter#orthogonal_group_layouter" target="_blank">Section Orthogonal Layout of Grouped Graphs</a> in the yFiles for Java Developer's Guide
66   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/orthogonal_layouter#orthogonal_layouter" target="_blank">Section Orthogonal Layout</a> in the yFiles for Java Developer's Guide
67   */
68  public class OrthogonalLayoutModule extends LayoutModule {
69    //// Module 'Orthogonal Layout'
70    protected static final String MODULE_ORTHOGONAL = "ORTHOGONAL_LAYOUTER";
71    
72    //// Section 'Layout'
73    protected static final String SECTION_LAYOUT = "LAYOUT";
74    // Section 'Layout' items
75    protected static final String ITEM_STYLE = "STYLE";
76    protected static final String VALUE_NORMAL = "NORMAL";
77    protected static final String VALUE_NORMAL_TREE = "NORMAL_TREE";
78    protected static final String VALUE_UNIFORM_NODES = "UNIFORM_NODES";
79    protected static final String VALUE_BOX_NODES = "BOX_NODES";
80    protected static final String VALUE_MIXED = "MIXED";
81    protected static final String VALUE_FIXED_MIXED = "FIXED_MIXED";
82    protected static final String VALUE_FIXED_BOX_NODES = "FIXED_BOX_NODES";
83    protected static final String ITEM_GRID = "GRID";
84    protected static final String ITEM_LENGTH_REDUCTION = "LENGTH_REDUCTION";
85    protected static final String ITEM_USE_EXISTING_DRAWING_AS_SKETCH = "USE_EXISTING_DRAWING_AS_SKETCH";
86    protected static final String ITEM_CROSSING_POSTPROCESSING = "CROSSING_POSTPROCESSING";
87    protected static final String ITEM_PERCEIVED_BENDS_POSTPROCESSING = "PERCEIVED_BENDS_POSTPROCESSING";
88    protected static final String ITEM_ALIGN_DEGREE_ONE_NODES = "ALIGN_DEGREE_ONE_NODES";
89    protected static final String ITEM_USE_RANDOMIZATION = "USE_RANDOMIZATION";
90    protected static final String ITEM_USE_FACE_MAXIMIZATION = "USE_FACE_MAXIMIZATION";
91    protected static final String ITEM_ROUTE_MULTI_EDGES_IN_PARALLEL = "ROUTE_MULTI_EDGES_IN_PARALLEL";
92    protected static final String ITEM_MINIMUM_FIRST_SEGMENT_LENGTH = "MINIMUM_FIRST_SEGMENT_LENGTH";
93    protected static final String ITEM_MINIMUM_LAST_SEGMENT_LENGTH = "MINIMUM_LAST_SEGMENT_LENGTH";
94    protected static final String ITEM_MINIMUM_SEGMENT_LENGTH = "MINIMUM_SEGMENT_LENGTH";
95    
96    //// Section 'Labeling'
97    protected static final String SECTION_LABELING = "LABELING";
98    // Section 'Labeling' items
99    protected static final String ITEM_EDGE_LABELING = "EDGE_LABELING";
100   protected static final String VALUE_NONE = "NONE";
101   protected static final String VALUE_INTEGRATED = "INTEGRATED";
102   protected static final String VALUE_GENERIC = "GENERIC";
103   protected static final String ITEM_EDGE_LABEL_MODEL = "EDGE_LABEL_MODEL";
104   protected static final String VALUE_BEST = "BEST";
105   protected static final String VALUE_AS_IS = "AS_IS";
106   protected static final String VALUE_CENTER_SLIDER = "CENTER_SLIDER";
107   protected static final String VALUE_SIDE_SLIDER = "SIDE_SLIDER";
108   protected static final String VALUE_FREE = "FREE";
109   protected static final String ITEM_CONSIDER_NODE_LABELS = "CONSIDER_NODE_LABELS";
110   
111   //// Section 'Grouping'
112   protected static final String SECTION_GROUPING = "GROUPING";
113   // Section 'Grouping' items
114   protected static final String ITEM_GROUP_POLICY = "GROUP_LAYOUT_POLICY";
115   protected static final String VALUE_LAYOUT_GROUPS = "LAYOUT_GROUPS";
116   protected static final String VALUE_FIX_GROUPS = "FIX_GROUPS";
117   protected static final String VALUE_IGNORE_GROUPS = "IGNORE_GROUPS";
118   protected static final String ITEM_GROUP_LAYOUT_QUALITY = "GROUP_LAYOUT_QUALITY";
119 
120   /**
121    * Creates an instance of this module.
122    */
123   public OrthogonalLayoutModule() {
124     super(MODULE_ORTHOGONAL);
125     setPortIntersectionCalculatorEnabled(true);
126   }
127 
128   /**
129    * Creates an OptionHandler and adds the option items used by this module.
130    * @return the created <code>OptionHandler</code> providing module related options
131    */
132   protected OptionHandler createOptionHandler() {
133     final OptionHandler options = new OptionHandler(getModuleName());
134     final ConstraintManager optionConstraints = new ConstraintManager(options);
135  
136     //// Section 'Layout'
137     options.useSection(SECTION_LAYOUT);
138     // Populate section
139     options.addEnum(ITEM_STYLE, new String[]{
140         VALUE_NORMAL,
141         VALUE_NORMAL_TREE,
142         VALUE_UNIFORM_NODES,
143         VALUE_BOX_NODES,
144         VALUE_MIXED,
145         VALUE_FIXED_MIXED,
146         VALUE_FIXED_BOX_NODES
147     }, 0);
148     options.addInt(ITEM_GRID,25)
149       .setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
150     options.addBool(ITEM_LENGTH_REDUCTION, true);
151     options.addBool(ITEM_USE_EXISTING_DRAWING_AS_SKETCH, false);
152     options.addBool(ITEM_CROSSING_POSTPROCESSING, true);
153     options.addBool(ITEM_PERCEIVED_BENDS_POSTPROCESSING, true);
154     options.addBool(ITEM_ALIGN_DEGREE_ONE_NODES, true);
155     options.addBool(ITEM_USE_RANDOMIZATION, true);
156     options.addBool(ITEM_USE_FACE_MAXIMIZATION,false);
157     options.addBool(ITEM_ROUTE_MULTI_EDGES_IN_PARALLEL, false);
158     options.addDouble(ITEM_MINIMUM_FIRST_SEGMENT_LENGTH, 10);
159     options.addDouble(ITEM_MINIMUM_LAST_SEGMENT_LENGTH, 10);
160     options.addDouble(ITEM_MINIMUM_SEGMENT_LENGTH, 10);
161     
162     //// Section 'Labeling'
163     options.useSection(SECTION_LABELING);
164     // Populate section
165     options.addEnum(ITEM_EDGE_LABELING, new String[]{
166         VALUE_NONE,
167         VALUE_INTEGRATED,
168         VALUE_GENERIC
169     }, 0);
170     options.addEnum(ITEM_EDGE_LABEL_MODEL, new String[]{
171         VALUE_BEST,
172         VALUE_AS_IS,
173         VALUE_CENTER_SLIDER,
174         VALUE_SIDE_SLIDER,
175         VALUE_FREE
176     }, 0);
177     options.addBool(ITEM_CONSIDER_NODE_LABELS, false);
178     // Enable/disable items depending on specific values
179     optionConstraints.setEnabledOnValueEquals(ITEM_EDGE_LABELING, VALUE_NONE, ITEM_EDGE_LABEL_MODEL, true);    
180     final Condition c =
181         optionConstraints.createConditionValueEquals(ITEM_USE_EXISTING_DRAWING_AS_SKETCH, Boolean.FALSE);
182     optionConstraints.setEnabledOnCondition(c, options.getItem(ITEM_CROSSING_POSTPROCESSING));
183     optionConstraints.setEnabledOnCondition(c, options.getItem(ITEM_PERCEIVED_BENDS_POSTPROCESSING));
184     optionConstraints.setEnabledOnCondition(c, options.getItem(ITEM_ALIGN_DEGREE_ONE_NODES));
185     optionConstraints.setEnabledOnCondition(c, options.getItem(ITEM_STYLE));
186     optionConstraints.setEnabledOnCondition(c, options.getItem(ITEM_USE_RANDOMIZATION));
187 
188     //// Section 'Grouping'
189     options.useSection(SECTION_GROUPING);
190     // Populate section
191     final EnumOptionItem itemGroupPolicy = options.addEnum(ITEM_GROUP_POLICY, new String[]{
192         VALUE_LAYOUT_GROUPS,
193         VALUE_FIX_GROUPS,
194         VALUE_IGNORE_GROUPS
195     }, 0);
196     final OptionItem itemGroupLayoutQuality = options.addDouble(ITEM_GROUP_LAYOUT_QUALITY, 1.0, 0.0, 1.0);
197     // Enable/disable items depending on specific values
198     optionConstraints.setEnabledOnValueEquals(itemGroupPolicy, VALUE_LAYOUT_GROUPS, itemGroupLayoutQuality);
199     
200     return options;
201   }
202 
203   /**
204    * Main module execution routine.
205    * Launches the module's underlying algorithm on the module's graph based on user options.
206    */
207   protected void mainrun() {
208     final CanonicMultiStageLayouter canonic;
209     
210     final OptionHandler options = getOptionHandler();
211     
212     if (HierarchyManager.containsGroupNodes(getGraph2D())
213         && !options.get(ITEM_GROUP_POLICY).equals(VALUE_IGNORE_GROUPS)
214         && !options.get(ITEM_GROUP_POLICY).equals(VALUE_FIX_GROUPS)) {
215       final OrthogonalGroupLayouter orthogonalGroup = new OrthogonalGroupLayouter();
216       configure(orthogonalGroup, options);
217       canonic = orthogonalGroup;
218     } else {
219       final OrthogonalLayouter orthogonal = new OrthogonalLayouter();
220       configure(orthogonal, options);
221       canonic = orthogonal;
222     }
223 
224     final Graph2D graph = getGraph2D();
225     prepareGraph(graph, options);
226     
227     launchLayouter(canonic);
228   }
229 
230   /**
231    * Sets edge label models depending on the given options.
232    * @param graph the graph whose label models may be changed
233    * @param options the layout options that determine whether or not to
234    * change label models
235    */
236   private void prepareGraph(final Graph2D graph, final OptionHandler options) {
237     final boolean normalStyle = VALUE_NORMAL.equals(options.getString(ITEM_STYLE));
238     final String el = options.getString(ITEM_EDGE_LABELING);
239     if (el.equals(VALUE_GENERIC) || (el.equals(VALUE_INTEGRATED) && normalStyle)) {
240       setupEdgeLabelModel(graph, el, options.getString(ITEM_EDGE_LABEL_MODEL));
241     }
242   }
243 
244   /**
245    * Configures the module's layout algorithm according to the given options.
246    * <p>
247    * Important: This method does also depend on the <code>Graph2D</code>
248    * of this module in addition to the method's parameters.
249    * </p>
250    * @param orthogonal the <code>OrthogonalLayouter</code> to be configured
251    * @param options the layout options to set
252    */
253   protected void configure(final OrthogonalLayouter orthogonal, final OptionHandler options) {
254     ((ComponentLayouter) orthogonal.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
255 
256     ////////////////////////////////////////////////////////////////////////////
257     // Layout
258     ////////////////////////////////////////////////////////////////////////////
259 
260     final String styleValue = options.getString(ITEM_STYLE);
261     if (VALUE_NORMAL_TREE.equals(styleValue)) {
262       orthogonal.setLayoutStyle(OrthogonalLayouter.NORMAL_TREE_STYLE);
263     } else if (VALUE_UNIFORM_NODES.equals(styleValue)) {
264       orthogonal.setLayoutStyle(OrthogonalLayouter.UNIFORM_STYLE);
265     } else if (VALUE_BOX_NODES.equals(styleValue)) {
266       orthogonal.setLayoutStyle(OrthogonalLayouter.BOX_STYLE);
267     } else if (VALUE_MIXED.equals(styleValue)) {
268       orthogonal.setLayoutStyle(OrthogonalLayouter.MIXED_STYLE);
269     } else if (VALUE_FIXED_MIXED.equals(styleValue)) {
270       orthogonal.setLayoutStyle(OrthogonalLayouter.FIXED_MIXED_STYLE);
271     } else if (VALUE_FIXED_BOX_NODES.equals(styleValue)) {
272       orthogonal.setLayoutStyle(OrthogonalLayouter.FIXED_BOX_STYLE);
273     } else {
274       // if VALUE_NORMAL.equals(styleValue)
275       orthogonal.setLayoutStyle(OrthogonalLayouter.NORMAL_STYLE);
276     }
277 
278     final EdgeLayoutDescriptor layoutDescriptor = orthogonal.getEdgeLayoutDescriptor();
279     layoutDescriptor.setMinimumFirstSegmentLength(options.getDouble(ITEM_MINIMUM_FIRST_SEGMENT_LENGTH));
280     layoutDescriptor.setMinimumLastSegmentLength(options.getDouble(ITEM_MINIMUM_LAST_SEGMENT_LENGTH));
281     layoutDescriptor.setMinimumSegmentLength(options.getDouble(ITEM_MINIMUM_SEGMENT_LENGTH));
282 
283     orthogonal.setGrid(options.getInt(ITEM_GRID));
284     orthogonal.setUseLengthReduction(
285         options.getBool(ITEM_LENGTH_REDUCTION));
286     orthogonal.setUseCrossingPostprocessing(
287         options.getBool(ITEM_CROSSING_POSTPROCESSING));
288     orthogonal.setPerceivedBendsOptimizationEnabled(
289         options.getBool(ITEM_PERCEIVED_BENDS_POSTPROCESSING));
290     orthogonal.setAlignDegreeOneNodesEnabled(options.getBool(ITEM_ALIGN_DEGREE_ONE_NODES));
291     orthogonal.setUseRandomization(
292         options.getBool(ITEM_USE_RANDOMIZATION));
293     orthogonal.setUseFaceMaximization(
294         options.getBool(ITEM_USE_FACE_MAXIMIZATION));
295     orthogonal.setUseSketchDrawing(options.getBool(ITEM_USE_EXISTING_DRAWING_AS_SKETCH));
296     orthogonal.setParallelEdgeLayouterEnabled(options.getBool(ITEM_ROUTE_MULTI_EDGES_IN_PARALLEL));
297 
298 
299     ////////////////////////////////////////////////////////////////////////////
300     // Labels
301     ////////////////////////////////////////////////////////////////////////////
302 
303     final boolean normalStyle = (orthogonal.getLayoutStyle() == OrthogonalLayouter.NORMAL_STYLE);
304     final String el = options.getString(ITEM_EDGE_LABELING);
305     orthogonal.setIntegratedEdgeLabelingEnabled(el.equals(VALUE_INTEGRATED) && normalStyle);
306     orthogonal.setConsiderNodeLabelsEnabled(options.getBool(ITEM_CONSIDER_NODE_LABELS) && normalStyle);
307 
308     if (HierarchyManager.containsGroupNodes(getGraph2D()) && !options.get(ITEM_GROUP_POLICY).equals(VALUE_IGNORE_GROUPS)) {
309       if (options.get(ITEM_GROUP_POLICY).equals(VALUE_FIX_GROUPS)) {
310         final FixedGroupLayoutStage fgl = new FixedGroupLayoutStage();
311         fgl.setInterEdgeRoutingStyle(FixedGroupLayoutStage.ROUTING_STYLE_ORTHOGONAL);
312         orthogonal.prependStage(fgl);
313       }
314     }
315     
316     if (options.getString(ITEM_EDGE_LABELING).equals(VALUE_GENERIC)) {
317       final GreedyMISLabeling la = new GreedyMISLabeling();
318       la.setPlaceNodeLabels(false);
319       la.setPlaceEdgeLabels(true);
320       la.setAutoFlippingEnabled(true);
321       la.setProfitModel(new LabelRanking());
322 
323       // the greedy labeling algorithm is appended to the main algorithm instead
324       // of being set as the main algorithm's label layouter because node label
325       // handling (see considerNodeLabelsEnabled above) requires and sets a 
326       // y.layout.LabelLayoutTranslator instance as label layouter internally
327       // and replacing said LabelLayoutTranslator instance would *silently*
328       // turn off node label handling
329       orthogonal.appendStage(la);
330     }
331   }
332   
333   /**
334    * This method configures the modules underlying algorithm
335    * with options found in the given <code>OptionHandler</code>.
336    * @param orthogonalGroup the <code>OrthogonalGroupLayouter</code> to be configured
337    * @param options an <code>OptionHandler</code> providing the option-values referred to
338    */
339   protected void configure(final OrthogonalGroupLayouter orthogonalGroup, final OptionHandler options) {
340     final boolean normalStyle = (VALUE_NORMAL.equals(options.getString(ITEM_STYLE)));
341     final String el = options.getString(ITEM_EDGE_LABELING);
342 
343     ((ComponentLayouter) orthogonalGroup.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
344     final EdgeLayoutDescriptor descriptor = orthogonalGroup.getEdgeLayoutDescriptor();
345     descriptor.setMinimumFirstSegmentLength(options.getDouble(ITEM_MINIMUM_FIRST_SEGMENT_LENGTH));
346     descriptor.setMinimumLastSegmentLength(options.getDouble(ITEM_MINIMUM_LAST_SEGMENT_LENGTH));
347     descriptor.setMinimumSegmentLength(options.getDouble(ITEM_MINIMUM_SEGMENT_LENGTH));
348     orthogonalGroup.setIntegratedEdgeLabelingEnabled(el.equals(VALUE_INTEGRATED) && normalStyle);
349     orthogonalGroup.setConsiderNodeLabelsEnabled(options.getBool(ITEM_CONSIDER_NODE_LABELS) && normalStyle);
350     orthogonalGroup.setAlignDegreeOneNodesEnabled(options.getBool(ITEM_ALIGN_DEGREE_ONE_NODES));
351     orthogonalGroup.setPerceivedBendsOptimizationEnabled(options.getBool(ITEM_PERCEIVED_BENDS_POSTPROCESSING));
352 
353     orthogonalGroup.setGrid(options.getInt(ITEM_GRID));
354     orthogonalGroup.setLayoutQuality(options.getDouble(ITEM_GROUP_LAYOUT_QUALITY));
355     orthogonalGroup.setParallelEdgeLayouterEnabled(options.getBool(ITEM_ROUTE_MULTI_EDGES_IN_PARALLEL));
356   }
357 
358   private static void setupEdgeLabelModel(final Graph2D graph,  final String edgeLabeling, final String edgeLabelModel) {
359     if (edgeLabeling.equals(VALUE_NONE) || edgeLabelModel.equals(VALUE_AS_IS)) {
360       return; //nothing to do
361     }
362     
363     byte model = EdgeLabel.SIDE_SLIDER;
364     if (edgeLabelModel.equals(VALUE_CENTER_SLIDER)) {
365       model = EdgeLabel.CENTER_SLIDER;
366     } else if (edgeLabelModel.equals(VALUE_FREE) || edgeLabelModel.equals(VALUE_BEST)) {
367       model = EdgeLabel.FREE;
368     }
369 
370     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
371       final Edge e = ec.edge();
372       EdgeRealizer er = graph.getRealizer(e);
373       for (int i = 0; i < er.labelCount(); i++) {
374         final EdgeLabel el = er.getLabel(i);
375         el.setModel(model);
376         final byte prefOnSide = el.getPreferredPlacementDescriptor().getSideOfEdge();
377         if (model == EdgeLabel.CENTER_SLIDER && prefOnSide != LabelLayoutConstants.PLACE_ON_EDGE) {
378           setPreferredSide(el, LabelLayoutConstants.PLACE_ON_EDGE);
379         } else if (model == EdgeLabel.SIDE_SLIDER && prefOnSide == LabelLayoutConstants.PLACE_ON_EDGE) {
380           setPreferredSide(el, LabelLayoutConstants.PLACE_RIGHT_OF_EDGE);
381         }
382       }
383     }
384   }
385 
386   private static void setPreferredSide(final EdgeLabel el, final byte preferredSide) {
387     final PreferredPlacementDescriptor oldDescriptor =
388             el.getPreferredPlacementDescriptor();
389     if (oldDescriptor.getSideOfEdge() != preferredSide) {
390       final PreferredPlacementDescriptor newDescriptor =
391               new PreferredPlacementDescriptor(oldDescriptor);
392       newDescriptor.setSideOfEdge(preferredSide);
393       el.setPreferredPlacementDescriptor(newDescriptor);
394     }
395   }
396 }
397