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.Node;
35  import y.layout.Layouter;
36  import y.layout.router.polyline.EdgeLayoutDescriptor;
37  import y.layout.router.polyline.EdgeRouter;
38  import y.layout.router.polyline.Grid;
39  import y.layout.router.polyline.PenaltySettings;
40  import y.option.ConstraintManager;
41  import y.option.OptionGroup;
42  import y.option.OptionHandler;
43  import y.option.OptionItem;
44  import y.util.DataProviderAdapter;
45  import y.view.Graph2D;
46  
47  /**
48   * This module represents an interactive configurator and launcher for {@link y.layout.router.polyline.EdgeRouter}.
49   *
50   */
51  public class PolylineEdgeRouterModule extends LayoutModule {
52  
53    //// Module 'Orthogonal-' / 'Polyline Edge Router'
54    private static final String POLYLINE_EDGE_ROUTER = "POLYLINE_EDGE_ROUTER";
55    
56    //// Section 'Layout'
57    protected static final String SECTION_LAYOUT = "LAYOUT";
58    // Section 'Layout' items
59    protected static final String ITEM_SCOPE = "SCOPE";
60    protected static final String VALUE_SCOPE_ALL_EDGES = "SCOPE_ALL_EDGES";
61    protected static final String VALUE_SCOPE_SELECTED_EDGES = "SCOPE_SELECTED_EDGES";
62    protected static final String VALUE_SCOPE_EDGES_AT_SELECTED_NODES = "SCOPE_EDGES_AT_SELECTED_NODES";
63    protected static final String ITEM_OPTIMIZATION_STRATEGY = "OPTIMIZATION_STRATEGY";
64    protected static final String VALUE_STRATEGY_BALANCED = "STRATEGY_BALANCED";
65    protected static final String VALUE_STRATEGY_MINIMIZE_BENDS = "STRATEGY_MINIMIZE_BENDS";
66    protected static final String VALUE_STRATEGY_MINIMIZE_CROSSINGS = "STRATEGY_MINIMIZE_CROSSINGS";
67    protected static final String ITEM_MONOTONIC_RESTRICTION = "MONOTONIC_RESTRICTION";
68    protected static final String VALUE_MONOTONIC_NONE = "MONOTONIC_NONE";
69    protected static final String VALUE_MONOTONIC_HORIZONTAL = "MONOTONIC_HORIZONTAL";
70    protected static final String VALUE_MONOTONIC_VERTICAL = "MONOTONIC_VERTICAL";
71    protected static final String VALUE_MONOTONIC_BOTH = "MONOTONIC_BOTH";
72    protected static final String TITLE_MINIMAL_DISTANCES = "MINIMAL_DISTANCES";
73    protected static final String ITEM_MINIMAL_EDGE_TO_EDGE_DISTANCE = "MINIMAL_EDGE_TO_EDGE_DISTANCE";
74    protected static final String ITEM_MINIMAL_NODE_TO_EDGE_DISTANCE = "MINIMAL_NODE_TO_EDGE_DISTANCE";
75    protected static final String ITEM_MINIMAL_NODE_CORNER_DISTANCE = "MINIMAL_NODE_CORNER_DISTANCE";
76    protected static final String ITEM_MINIMAL_FIRST_SEGMENT_LENGTH = "MINIMAL_FIRST_SEGMENT_LENGTH";
77    protected static final String ITEM_MINIMAL_LAST_SEGMENT_LENGTH = "MINIMAL_LAST_SEGMENT_LENGTH";
78    protected static final String TITLE_GRID_SETTINGS = "GRID_SETTINGS";
79    protected static final String ITEM_GRID_ENABLED = "GRID_ENABLED";
80    protected static final String ITEM_GRID_SPACING = "GRID_SPACING";
81    protected static final String ITEM_CONSIDER_NODE_LABELS = "CONSIDER_NODE_LABELS";
82    protected static final String ITEM_CONSIDER_EDGE_LABELS = "CONSIDER_EDGE_LABELS";
83    protected static final String ITEM_ENABLE_REROUTING = "ENABLE_REROUTING";
84    protected static final String ITEM_MAXIMAL_DURATION = "MAXIMAL_DURATION";
85    
86    //// Section 'Octilinear Routing'
87    protected static final String SECTION_POLYLINE_ROUTING = "POLYLINE_ROUTING";
88    // Section 'Octilinear Routing' items
89    protected static final String ITEM_ENABLE_POLYLINE_ROUTING = "ENABLE_POLYLINE_ROUTING";
90    protected static final String ITEM_PREFERRED_POLYLINE_SEGMENT_LENGTH = "PREFERRED_POLYLINE_SEGMENT_LENGTH";
91    
92    // data provider delete flags
93    private boolean isEdgesDPAddedByModule;
94    private boolean isNodesDPAddedByModule;
95  
96    /**
97     * Creates an instance of this module.
98     */
99    public PolylineEdgeRouterModule() {
100     super(POLYLINE_EDGE_ROUTER);
101     setPortIntersectionCalculatorEnabled(true);
102   }
103 
104   /**
105    * Creates an OptionHandler and adds the option items used by this module.
106    * @return the created <code>OptionHandler</code> providing module related options
107    */
108   protected OptionHandler createOptionHandler() {
109     final OptionHandler options = new OptionHandler(getModuleName());
110     final ConstraintManager optionConstraints = new ConstraintManager(options);
111     // Default providers
112     final EdgeRouter polyline = new EdgeRouter();
113     final EdgeLayoutDescriptor descriptor = polyline.getDefaultEdgeLayoutDescriptor();
114     final Grid grid = polyline.getGrid();
115 
116     //// Section 'Layout'
117     options.useSection(SECTION_LAYOUT);
118     // Populate section
119     byte scope = polyline.getSphereOfAction();
120     options.addEnum(ITEM_SCOPE, new String[]{
121         VALUE_SCOPE_ALL_EDGES,
122         VALUE_SCOPE_SELECTED_EDGES,
123         VALUE_SCOPE_EDGES_AT_SELECTED_NODES
124     }, scope == EdgeRouter.ROUTE_ALL_EDGES ? 0 : 1);
125     options.addEnum(ITEM_OPTIMIZATION_STRATEGY, new String[]{
126         VALUE_STRATEGY_BALANCED,
127         VALUE_STRATEGY_MINIMIZE_BENDS,
128         VALUE_STRATEGY_MINIMIZE_CROSSINGS
129     }, 0);
130     options.addEnum(ITEM_MONOTONIC_RESTRICTION, new String[]{
131         VALUE_MONOTONIC_NONE,
132         VALUE_MONOTONIC_HORIZONTAL,
133         VALUE_MONOTONIC_VERTICAL,
134         VALUE_MONOTONIC_BOTH
135     }, descriptor.getMonotonicPathRestriction());
136 
137     // Group 'Minimum Distances'
138     final OptionGroup distancesGroup = new OptionGroup();
139     distancesGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_MINIMAL_DISTANCES);
140     // Populate group
141     distancesGroup.addItem(
142         options.addDouble(ITEM_MINIMAL_EDGE_TO_EDGE_DISTANCE, descriptor.getMinimalEdgeToEdgeDistance()));
143     distancesGroup.addItem(
144         options.addDouble(ITEM_MINIMAL_NODE_TO_EDGE_DISTANCE, polyline.getMinimalNodeToEdgeDistance()));
145     distancesGroup.addItem(
146         options.addDouble(ITEM_MINIMAL_NODE_CORNER_DISTANCE, descriptor.getMinimalNodeCornerDistance()));
147     distancesGroup.addItem(
148         options.addDouble(ITEM_MINIMAL_FIRST_SEGMENT_LENGTH, descriptor.getMinimalFirstSegmentLength()));
149     distancesGroup.addItem(
150         options.addDouble(ITEM_MINIMAL_LAST_SEGMENT_LENGTH, descriptor.getMinimalLastSegmentLength()));
151 
152     // Group 'Grid Settings'
153     final OptionGroup gridGroup = new OptionGroup();
154     gridGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_GRID_SETTINGS);
155     // Populate group
156     final OptionItem itemGridEnabled =
157         gridGroup.addItem(options.addBool(ITEM_GRID_ENABLED, grid != null));
158     final OptionItem itemGridSpacing =
159         gridGroup.addItem(options.addDouble(ITEM_GRID_SPACING, grid != null ? grid.getSpacing() : 10));
160     // Enable/disable items depending on specific values
161     optionConstraints.setEnabledOnValueEquals(itemGridEnabled, Boolean.TRUE, itemGridSpacing);
162 
163     // Ungrouped settings
164     options.addBool(ITEM_CONSIDER_NODE_LABELS, polyline.isConsiderNodeLabelsEnabled());
165     options.addBool(ITEM_CONSIDER_EDGE_LABELS, polyline.isConsiderEdgeLabelsEnabled());
166     options.addBool(ITEM_ENABLE_REROUTING, polyline.isReroutingEnabled());
167     options.addInt(ITEM_MAXIMAL_DURATION, 0);
168 
169     //// Section 'Octilinear Routing'
170     options.useSection(SECTION_POLYLINE_ROUTING);
171     // Populate section
172     final OptionItem itemEnablePolylineRouting = options.addBool(ITEM_ENABLE_POLYLINE_ROUTING, true);
173     final OptionItem itemPreferredPolylineSegmentLength =
174         options.addDouble(ITEM_PREFERRED_POLYLINE_SEGMENT_LENGTH, polyline.getPreferredPolylineSegmentLength());
175     // Enable/disable items depending on specific values\
176     optionConstraints.setEnabledOnValueEquals(itemEnablePolylineRouting,
177         Boolean.TRUE, itemPreferredPolylineSegmentLength);
178 
179     return options;
180   }
181 
182   /**
183    * Main module execution routine.
184    * Launches the module's underlying algorithm on the module's graph based on user options.
185    */
186   protected void mainrun() {
187     final EdgeRouter edgeRouter = new EdgeRouter();
188 
189     final OptionHandler options = getOptionHandler();
190     configure(edgeRouter, options);
191     
192     final Graph2D graph = getGraph2D();
193     prepareGraph(graph, options);
194     try {
195       launchLayouter(edgeRouter);
196     } finally {
197       restoreGraph(graph, options);
198     }
199   }
200 
201   /**
202    * Prepares a <code>graph</code> depending on the given options for the
203    * module's layout algorithm.
204    * <br>
205    * Additional resources created by this method have to be freed up by calling
206    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
207    * layout calculation.  
208    * @param graph the graph to be prepared
209    * @param options the options for the module's layout algorithm
210    */
211   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
212     if (VALUE_SCOPE_SELECTED_EDGES.equals(options.getString(ITEM_SCOPE))) {
213       // rather use an external registered data provider than registering an own
214       // e.g. EdgeRouterDemo does provide an own data provider
215       isEdgesDPAddedByModule = graph.getDataProvider(Layouter.SELECTED_EDGES) == null;
216       if (isEdgesDPAddedByModule) {
217         graph.addDataProvider(Layouter.SELECTED_EDGES, new DataProviderAdapter() {
218           public boolean getBool(Object dataHolder) {
219             return graph.isSelected((Edge) dataHolder);
220           }
221         });
222       }
223     } else if (VALUE_SCOPE_EDGES_AT_SELECTED_NODES.equals(options.getString(ITEM_SCOPE))) {
224       // rather use an external registered data provider than registering an own
225       isNodesDPAddedByModule = graph.getDataProvider(Layouter.SELECTED_NODES) == null;
226       if (isNodesDPAddedByModule) {
227         graph.addDataProvider(Layouter.SELECTED_NODES, new DataProviderAdapter() {
228           public boolean getBool(Object dataHolder) {
229             return graph.isSelected((Node) dataHolder);
230           }
231         });
232       }
233     }
234   }
235 
236   /**
237    * Restores the given <code>graph</code> by freeing up resources created by
238    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
239    * @param graph the graph for which <code>prepareGraph</code> has been called
240    * @param options the options for the module's layout algorithm
241    */
242   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
243     // remove the data providers if they were added by this module
244     if(VALUE_SCOPE_SELECTED_EDGES.equals(options.getString(ITEM_SCOPE))) {
245       if (isEdgesDPAddedByModule) {
246         isEdgesDPAddedByModule = false;
247         graph.removeDataProvider(Layouter.SELECTED_EDGES);
248       }
249     } else if (VALUE_SCOPE_EDGES_AT_SELECTED_NODES.equals(options.getString(ITEM_SCOPE))) {
250       if (isNodesDPAddedByModule) {
251         isNodesDPAddedByModule = false;
252         graph.removeDataProvider(Layouter.SELECTED_NODES);
253       }
254     }
255   }
256 
257   /**
258    * Configures the module's layout algorithm according to the given options.
259    * @param router the <code>EdgeRouter</code> to be configured
260    * @param options the layout options to set
261    */
262   protected void configure(final EdgeRouter router, final OptionHandler options) {
263     final String scope = options.getString(ITEM_SCOPE);
264     if (VALUE_SCOPE_EDGES_AT_SELECTED_NODES.equals(scope)) {
265       router.setSphereOfAction(EdgeRouter.ROUTE_EDGES_AT_SELECTED_NODES);
266     } else if (VALUE_SCOPE_ALL_EDGES.equals(scope)) {
267       router.setSphereOfAction(EdgeRouter.ROUTE_ALL_EDGES);
268     } else {
269       router.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
270     }
271 
272     final EdgeLayoutDescriptor descriptor = router.getDefaultEdgeLayoutDescriptor();
273     
274     final String strategy = options.getString(ITEM_OPTIMIZATION_STRATEGY);
275     final PenaltySettings penaltySettings = descriptor.getPenaltySettings();
276     if (strategy.equals(VALUE_STRATEGY_BALANCED)) {
277       penaltySettings.setBendPenalty(3);
278       penaltySettings.setEdgeCrossingPenalty(1);
279     } else if (strategy.equals(VALUE_STRATEGY_MINIMIZE_BENDS)) {
280       penaltySettings.setBendPenalty(3);
281       penaltySettings.setEdgeCrossingPenalty(0);
282     } else {
283       penaltySettings.setBendPenalty(3);
284       penaltySettings.setEdgeCrossingPenalty(5);
285     }
286 
287     final String monotonyFlag = options.getString(ITEM_MONOTONIC_RESTRICTION);
288     if (monotonyFlag.equals(VALUE_MONOTONIC_BOTH)) {
289       descriptor.setMonotonicPathRestriction(EdgeLayoutDescriptor.MONOTONIC_BOTH);
290     } else if (monotonyFlag.equals(VALUE_MONOTONIC_HORIZONTAL)) {
291       descriptor.setMonotonicPathRestriction(EdgeLayoutDescriptor.MONOTONIC_HORIZONTAL);
292     } else if (monotonyFlag.equals(VALUE_MONOTONIC_VERTICAL)) {
293       descriptor.setMonotonicPathRestriction(EdgeLayoutDescriptor.MONOTONIC_VERTICAL);
294     } else {
295       descriptor.setMonotonicPathRestriction(EdgeLayoutDescriptor.MONOTONIC_NONE);
296     }
297 
298     descriptor.setMinimalEdgeToEdgeDistance(options.getDouble(ITEM_MINIMAL_EDGE_TO_EDGE_DISTANCE));
299     router.setMinimalNodeToEdgeDistance(options.getDouble(ITEM_MINIMAL_NODE_TO_EDGE_DISTANCE));
300     descriptor.setMinimalNodeCornerDistance(options.getDouble(ITEM_MINIMAL_NODE_CORNER_DISTANCE));
301     descriptor.setMinimalFirstSegmentLength(options.getDouble(ITEM_MINIMAL_FIRST_SEGMENT_LENGTH));
302     descriptor.setMinimalLastSegmentLength(options.getDouble(ITEM_MINIMAL_LAST_SEGMENT_LENGTH));
303 
304     if (options.getBool(ITEM_GRID_ENABLED)) {
305       final double gridSpacing = options.getDouble(ITEM_GRID_SPACING);
306       router.setGrid(new Grid(0, 0, gridSpacing));
307     } else {
308       router.setGrid(null);
309     }
310 
311     router.setConsiderNodeLabelsEnabled(options.getBool(ITEM_CONSIDER_NODE_LABELS));
312     router.setConsiderEdgeLabelsEnabled(options.getBool(ITEM_CONSIDER_EDGE_LABELS));
313     router.setReroutingEnabled(options.getBool(ITEM_ENABLE_REROUTING));
314     router.setPolylineRoutingEnabled(options.getBool(ITEM_ENABLE_POLYLINE_ROUTING));
315     router.setPreferredPolylineSegmentLength(options.getDouble(ITEM_PREFERRED_POLYLINE_SEGMENT_LENGTH));
316     
317     int maximalDuration = options.getInt(ITEM_MAXIMAL_DURATION);
318     if (maximalDuration == 0) {
319       router.setMaximumDuration(Long.MAX_VALUE);
320     } else {
321       router.setMaximumDuration(maximalDuration * 1000);
322     }
323   }
324 }
325