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.layout.Layouter;
35  import y.layout.router.ChannelEdgeRouter;
36  import y.layout.router.ChannelEdgeRouter.OrthogonalShortestPathPathFinder;
37  import y.layout.router.OrthogonalPatternEdgeRouter;
38  import y.layout.router.OrthogonalSegmentDistributionStage;
39  import y.option.ConstraintManager;
40  import y.option.DoubleOptionItem;
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
49   * {@link y.layout.router.ChannelEdgeRouter}.
50   *
51   *
52   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/channel_edge_router#channel_edge_router" target="_blank">Section Channel Edge Routing</a> in the yFiles for Java Developer's Guide
53   */
54  public class ChannelEdgeRouterModule extends LayoutModule {
55    //// Module 'Channel Edge Router'
56    protected static final String MODULE_CHANNEL_EDGE_ROUTER = "CHANNEL_EDGE_ROUTER";
57  
58    //// Section 'default' items
59    protected static final String TITLE_LAYOUT_OPTIONS = "LAYOUT_OPTIONS";
60    protected static final String ITEM_PATHFINDER = "PATHFINDER";
61    protected static final String VALUE_ORTHOGONAL_PATTERN_PATH_FINDER = "ORTHOGONAL_PATTERN_PATH_FINDER";
62    protected static final String VALUE_ORTHOGONAL_SHORTESTPATH_PATH_FINDER = "ORTHOGONAL_SHORTESTPATH_PATH_FINDER";
63    protected static final String ITEM_SCOPE = "SCOPE";
64    protected static final String VALUE_SCOPE_ALL_EDGES = "SCOPE_ALL_EDGES";
65    protected static final String VALUE_SCOPE_SELECTED_EDGES = "SCOPE_SELECTED_EDGES";
66    protected static final String VALUE_SCOPE_AT_SELECTED_NODES = "SCOPE_AT_SELECTED_NODES";
67    protected static final String ITEM_MINIMUM_DISTANCE = "MINIMUM_DISTANCE";
68    protected static final String ITEM_ACTIVATE_GRID_ROUTING = "ACTIVATE_GRID_ROUTING";
69    protected static final String ITEM_GRID_SPACING = "GRID_SPACING";
70    protected static final String TITLE_COST = "COST";
71    protected static final String ITEM_BEND_COST = "BEND_COST_FACTOR";
72    protected static final String ITEM_EDGE_CROSSING_COST = "EDGE_CROSSING_COST";
73    protected static final String ITEM_NODE_CROSSING_COST = "NODE_CROSSING_COST";
74    private boolean isEdgeDPAddedByModule;
75  
76    /**
77     * Creates an instance of this module.
78     */
79    public ChannelEdgeRouterModule() {
80      super(MODULE_CHANNEL_EDGE_ROUTER);
81      setPortIntersectionCalculatorEnabled(true);
82    }
83  
84    /**
85     * Creates an OptionHandler and adds the option items used by this module.
86     * @return the created <code>OptionHandler</code> providing module related options
87     */
88    protected OptionHandler createOptionHandler() {
89      final OptionHandler options = new OptionHandler(getModuleName());
90      final ConstraintManager optionConstraints = new ConstraintManager(options);
91      // Defaults provider
92      final ChannelEdgeRouter defaults = new ChannelEdgeRouter();
93      // Default values
94      final double minimumDistance, gridSpacing, bendCost, edgeCrossingCost, nodeCrossingCost;
95      final boolean isGridRoutingEnabled;
96      if (defaults.getPathFinderStrategy() instanceof OrthogonalPatternEdgeRouter) {
97        // Defaults provider
98        final OrthogonalPatternEdgeRouter defaultsOper = (OrthogonalPatternEdgeRouter) defaults.getPathFinderStrategy();
99        // Set defaults
100       minimumDistance = defaultsOper.getMinimumDistance();
101       isGridRoutingEnabled = defaultsOper.isGridRoutingEnabled();
102       gridSpacing = defaultsOper.getGridWidth();
103       bendCost = defaultsOper.getBendCost();
104       edgeCrossingCost = defaultsOper.getEdgeCrossingCost();
105       nodeCrossingCost = defaultsOper.getNodeCrossingCost();
106     } else if (defaults.getPathFinderStrategy() instanceof ChannelEdgeRouter.OrthogonalShortestPathPathFinder) {
107       // Defaults provider
108       final ChannelEdgeRouter.OrthogonalShortestPathPathFinder defaultsOsppf =
109           (ChannelEdgeRouter.OrthogonalShortestPathPathFinder) defaults.getPathFinderStrategy();
110       // Set defaults
111       minimumDistance = defaultsOsppf.getMinimumDistance();
112       isGridRoutingEnabled = defaultsOsppf.isGridRoutingEnabled();
113       gridSpacing = defaultsOsppf.getGridSpacing();
114       bendCost = 1;
115       edgeCrossingCost = 5;
116       nodeCrossingCost = 50;
117     } else {
118       // Set defaults
119       minimumDistance = 10;
120       isGridRoutingEnabled = true;
121       gridSpacing = 20;
122       bendCost = 1;
123       edgeCrossingCost = 5;
124       nodeCrossingCost = 20;
125     }
126 
127     final boolean isOrthogonalShortestPath =
128         defaults.getPathFinderStrategy() instanceof ChannelEdgeRouter.OrthogonalShortestPathPathFinder;
129     
130     // Group 'Layout'
131     final OptionGroup layoutGroup = new OptionGroup();
132     layoutGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_LAYOUT_OPTIONS);
133     // Populate group
134     final OptionItem itemPathfinder = layoutGroup.addItem(
135         options.addEnum(ITEM_PATHFINDER, new String[]{
136             VALUE_ORTHOGONAL_PATTERN_PATH_FINDER,
137             VALUE_ORTHOGONAL_SHORTESTPATH_PATH_FINDER
138         }, isOrthogonalShortestPath ? 1 : 0));
139     layoutGroup.addItem(
140         options.addEnum(ITEM_SCOPE, new String[]{
141             VALUE_SCOPE_ALL_EDGES,
142             VALUE_SCOPE_SELECTED_EDGES,
143             VALUE_SCOPE_AT_SELECTED_NODES
144         }, 0));
145     final OptionItem itemMinimumDistance = layoutGroup.addItem(
146         options.addDouble(ITEM_MINIMUM_DISTANCE, minimumDistance));
147     if (isOrthogonalShortestPath) {
148       itemMinimumDistance.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(4.0));
149     }
150     final OptionItem itemActivateGridRouting = layoutGroup.addItem(
151         options.addBool(ITEM_ACTIVATE_GRID_ROUTING, isGridRoutingEnabled));
152     final OptionItem itemGridSpacing =layoutGroup.addItem(
153         options.addDouble(ITEM_GRID_SPACING, gridSpacing));
154     itemGridSpacing.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(2.0));
155     // Enable/disable items depending on specific values
156     optionConstraints.setEnabledOnValueEquals(itemActivateGridRouting, Boolean.TRUE, itemGridSpacing);
157     
158     // Group 'Cost'
159     final OptionGroup costGroup = new OptionGroup();
160     costGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_COST);
161     // Populate group
162     final OptionItem itemBendCost = costGroup.addItem(options.addDouble(ITEM_BEND_COST, bendCost));
163     final OptionItem itemEdgeCrossingCost = costGroup.addItem(
164         options.addDouble(ITEM_EDGE_CROSSING_COST, edgeCrossingCost));
165     final OptionItem itemNodeCrossingCost = costGroup.addItem(
166         options.addDouble(ITEM_NODE_CROSSING_COST, nodeCrossingCost));
167     // Enable/disable items depending on specific values
168     optionConstraints.setEnabledOnValueEquals(itemPathfinder, VALUE_ORTHOGONAL_PATTERN_PATH_FINDER, itemBendCost);
169     optionConstraints.setEnabledOnValueEquals(itemPathfinder, VALUE_ORTHOGONAL_PATTERN_PATH_FINDER, itemEdgeCrossingCost);
170     optionConstraints.setEnabledOnValueEquals(itemPathfinder, VALUE_ORTHOGONAL_PATTERN_PATH_FINDER, itemNodeCrossingCost);
171     return options;
172   }
173 
174   /**
175    * Main module execution routine.
176    * Launches the module's underlying algorithm on the module's graph based on user options.
177    */
178   protected void mainrun() {
179     final ChannelEdgeRouter channel = new ChannelEdgeRouter();
180 
181     final OptionHandler options = getOptionHandler();
182     configure(channel, options);
183 
184     final Graph2D graph = getGraph2D();
185     prepareGraph(graph, options);
186     try {
187       launchLayouter(channel);
188     } finally {
189       restoreGraph(graph, options);
190     }
191   }
192 
193   /**
194    * Prepares a <code>graph</code> depending on the given options for the
195    * module's layout algorithm.
196    * <br>
197    * Additional resources created by this method have to be freed up by calling
198    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
199    * layout calculation.  
200    * @param graph the graph to be prepared
201    * @param options the options for the module's layout algorithm
202    */
203   protected void prepareGraph(final Graph2D graph, OptionHandler options) {
204     isEdgeDPAddedByModule = graph.getDataProvider(ChannelEdgeRouter.AFFECTED_EDGES) == null;
205     if (isEdgeDPAddedByModule) {
206       //set affected edges
207       if (options.get(ITEM_SCOPE).equals(VALUE_SCOPE_ALL_EDGES)) {
208         graph.addDataProvider(ChannelEdgeRouter.AFFECTED_EDGES, new DataProviderAdapter() {
209           public boolean getBool(Object dataHolder) {
210             return true;
211           }
212         });
213       } else if (options.get(ITEM_SCOPE).equals(VALUE_SCOPE_SELECTED_EDGES)) {
214         graph.addDataProvider(ChannelEdgeRouter.AFFECTED_EDGES, new DataProviderAdapter() {
215           public boolean getBool(Object dataHolder) {
216             return graph.isSelected((Edge) dataHolder);
217           }
218         });
219       } else {
220         graph.addDataProvider(ChannelEdgeRouter.AFFECTED_EDGES, new DataProviderAdapter() {
221           public boolean getBool(Object dataHolder) {
222             return graph.isSelected(((Edge) dataHolder).source()) || graph.isSelected(((Edge) dataHolder).target());
223           }
224         });
225       }
226     }
227   }
228 
229 
230   /**
231    * Restores the given <code>graph</code> by freeing up resources created by
232    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
233    * @param graph the graph for which <code>prepareGraph</code> has been called
234    * @param options the options for the module's layout algorithm
235    */
236   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
237     if (isEdgeDPAddedByModule) {
238       isEdgeDPAddedByModule = false;
239       graph.removeDataProvider(ChannelEdgeRouter.AFFECTED_EDGES);
240     }
241   }
242 
243   /**
244    * Configures the module's layout algorithm according to the given options.
245    * @param channel the <code>ChannelEdgeRouter</code> to be configured
246    * @param options the layout options to set
247    */
248   protected void configure(final ChannelEdgeRouter channel, final OptionHandler options) {
249     final Layouter pathFinder;
250     if (options.get(ITEM_PATHFINDER).equals(VALUE_ORTHOGONAL_PATTERN_PATH_FINDER)) {
251       final OrthogonalPatternEdgeRouter oper = new OrthogonalPatternEdgeRouter();
252       configure(oper, options);
253       pathFinder = oper;
254     } else {
255       final OrthogonalShortestPathPathFinder ospf = new OrthogonalShortestPathPathFinder();
256       configure(ospf, options);
257       pathFinder = ospf;
258     }
259     channel.setPathFinderStrategy(pathFinder);
260 
261     OrthogonalSegmentDistributionStage segmentDistributionStage = new OrthogonalSegmentDistributionStage();
262     segmentDistributionStage.setAffectedEdgesDPKey(ChannelEdgeRouter.AFFECTED_EDGES);
263     segmentDistributionStage.setPreferredDistance(options.getDouble(ITEM_MINIMUM_DISTANCE));
264     segmentDistributionStage.setGridEnabled(options.getBool(ITEM_ACTIVATE_GRID_ROUTING));
265     segmentDistributionStage.setGridWidth(options.getDouble(ITEM_GRID_SPACING));
266 
267     channel.setEdgeDistributionStrategy(segmentDistributionStage);
268   }
269 
270   private void configure(final OrthogonalShortestPathPathFinder ospf, final OptionHandler options) {
271     ospf.setSelectedEdgesDpKey(ChannelEdgeRouter.AFFECTED_EDGES);
272     ospf.setMinimumDistance((int) options.getDouble(ITEM_MINIMUM_DISTANCE));
273 
274     ospf.setGridRoutingEnabled(options.getBool(ITEM_ACTIVATE_GRID_ROUTING));
275     ospf.setGridSpacing((int) options.getDouble(ITEM_GRID_SPACING));
276 
277     ospf.setCrossingCost(options.getDouble(ITEM_EDGE_CROSSING_COST));
278   }
279 
280   private void configure(final OrthogonalPatternEdgeRouter oper, OptionHandler options) {
281     oper.setAffectedEdgesDPKey(ChannelEdgeRouter.AFFECTED_EDGES);
282     oper.setMinimumDistance(options.getDouble(ITEM_MINIMUM_DISTANCE));
283 
284     oper.setGridRoutingEnabled(options.getBool(ITEM_ACTIVATE_GRID_ROUTING));
285     oper.setGridWidth(options.getDouble(ITEM_GRID_SPACING));
286 
287     oper.setBendCost(options.getDouble(ITEM_BEND_COST));
288     oper.setEdgeCrossingCost(options.getDouble(ITEM_EDGE_CROSSING_COST));
289     oper.setNodeCrossingCost(options.getDouble(ITEM_NODE_CROSSING_COST));
290 
291     //disable edge overlap costs when Edge distribution will run afterwards anyway
292     oper.setEdgeOverlapCost(0.0);
293   }
294 }
295