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.algo.GraphConnectivity;
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.base.NodeCursor;
40  import y.base.NodeMap;
41  import y.geom.YPoint;
42  import y.geom.YPointCursor;
43  import y.geom.YPointPath;
44  import y.layout.AbstractLayoutStage;
45  import y.layout.LayoutGraph;
46  import y.layout.Layouter;
47  import y.layout.router.BusDescriptor;
48  import y.layout.router.BusRouter;
49  import y.option.ConstraintManager;
50  import y.option.DoubleOptionItem;
51  import y.option.IntOptionItem;
52  import y.option.OptionGroup;
53  import y.option.OptionHandler;
54  import y.option.OptionItem;
55  import y.util.DataProviderAdapter;
56  import y.util.GraphHider;
57  import y.util.Maps;
58  import y.view.Graph2D;
59  
60  import java.util.HashSet;
61  import java.util.Set;
62  
63  /**
64   * Module for the {@link y.layout.router.BusRouter}.
65   * <p>
66   * There are more scopes in this module than in {@link BusRouter}. Each additional scope is mapped to an appropriate
67   * combinations of scope and fixed edges in BusRouter.
68   * </p>
69   * <dl>
70   * <dt>ALL</dt>
71   * <dd>Maps to <code>BusRouter.SCOPE_ALL</code>. All edges are in scope, and all of them are movable.</dd>
72   * <dt>SUBSET</dt>
73   * <dd>Maps to <code>BusRouter.SCOPE_SUBSET</code>. Selected edges are in scope, and all of them are movable.</dd>
74   * <dt>SUBSET_BUS</dt>
75   * <dd>Each bus with at least one selected edge is in scope, and all of their edges are movable.</dd>
76   * <dt>PARTIAL</dt>
77   * <dd>Each bus with at least one selected node is in scope, and only the adjacent edges of the selected nodes are
78   * movable.</dd>
79   * <dt>EDGES_AT_SELECTED_NODES</dt>
80   * <dd> Maps to <code>BusRouter.SCOPE_SUBSET</code>. Edges connected to selected nodes are in scope, and all of them are movable.
81   * </dd>
82   * </dl>
83   *
84   *
85   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/orthogonal_bus_router#orthogonal_bus_router" target="_blank">Section Orthogonal Bus-style Edge Routing</a> in the yFiles for Java Developer's Guide
86   */
87  public class BusRouterModule extends LayoutModule {
88    //// Module 'Orthogonal Bus-style Edge Router'
89    protected static final String MODULE_BUS_ROUTER = "BUS_ROUTER";
90    
91    //// Section 'default' items
92    protected static final String TITLE_LAYOUT = "GROUP_LAYOUT";
93    protected static final String ITEM_SCOPE = "SCOPE";
94    protected static final String VALUE_SCOPE_ALL = "ALL";
95    protected static final String VALUE_SCOPE_SUBSET = "SUBSET";
96    protected static final String VALUE_SCOPE_EDGES_AT_SELECTED_NODES = "EDGES_AT_SELECTED_NODES";
97    protected static final String VALUE_SCOPE_SUBSET_BUS = "SUBSET_BUS";
98    protected static final String VALUE_SCOPE_PARTIAL = "PARTIAL";
99    protected static final String ITEM_BUSES = "BUSES";
100   protected static final String VALUE_SINGLE = "SINGLE";
101   protected static final String VALUE_COLOR = "COLOR";
102   protected static final String VALUE_CONNECTED_COMPONENT = "CONNECTED_COMPONENT";
103   protected static final String ITEM_GRID_ENABLED = "GRID_ENABLED";
104   protected static final String ITEM_GRID_SPACING = "GRID_SPACING";
105   protected static final String ITEM_MIN_DISTANCE_TO_NODES = "MIN_DISTANCE_TO_NODES";
106   protected static final String ITEM_MIN_DISTANCE_TO_EDGES = "MIN_DISTANCE_TO_EDGES";
107   protected static final String TITLE_SELECTION = "GROUP_SELECTION";
108   protected static final String ITEM_PREFERRED_BACKBONE_COUNT = "PREFERRED_BACKBONE_COUNT";
109   protected static final String ITEM_MINIMUM_BACKBONE_LENGTH = "MINIMUM_BACKBONE_LENGTH";
110   protected static final String TITLE_ROUTING = "GROUP_ROUTING";
111   protected static final String ITEM_CROSSING_COST = "CROSSING_COST";
112   protected static final String ITEM_CROSSING_REROUTING = "CROSSING_REROUTING";
113   protected static final String ITEM_MINIMUM_CONNECTIONS_COUNT = "MINIMUM_CONNECTIONS_COUNT";
114 
115   /**
116    * Specifies whether the options of group layout are used.
117    */
118   protected boolean optionsLayout;
119   /**
120    * Specifies whether the options for initial backbone selection are used.
121    */
122   protected boolean optionsSelection;
123   /**
124    * Specifies whether the options for routing and recombination are used.
125    */
126   protected boolean optionsRouting;
127 
128   /**
129    * Creates an instance of this module.
130    */
131   public BusRouterModule() {
132     super(MODULE_BUS_ROUTER);
133     optionsLayout = true;
134     optionsSelection = true;
135     optionsRouting = true;
136   }
137 
138   /**
139    * Creates an OptionHandler and adds the option items used by this module.
140    * @return the created <code>OptionHandler</code> providing module related options
141    */
142   protected OptionHandler createOptionHandler() {
143     final OptionHandler options = new OptionHandler(getModuleName());
144     addOptionItems(new BusRouter(), options);
145     return options;
146   }
147 
148   /**
149    * Adds the option items used by this module to the given <code>OptionHandler</code>.
150    * @param defaults a <code>BusRouter</code> instance that provides default option values.
151    * @param options the <code>OptionHandler</code> to add the items to
152    */
153   protected void addOptionItems(final BusRouter defaults, final OptionHandler options) {
154     final ConstraintManager optionConstraints = new ConstraintManager(options);
155 
156     if (optionsLayout) {
157       // Group 'Layout'
158       final OptionGroup layoutGroup = new OptionGroup();
159       layoutGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_LAYOUT);
160       // Populate group
161       layoutGroup.addItem(options.addEnum(ITEM_SCOPE, new String[]{
162           VALUE_SCOPE_ALL,
163           VALUE_SCOPE_SUBSET,
164           VALUE_SCOPE_EDGES_AT_SELECTED_NODES,
165           VALUE_SCOPE_SUBSET_BUS,
166           VALUE_SCOPE_PARTIAL
167       }, (int) defaults.getScope()));
168       layoutGroup.addItem(options.addEnum(ITEM_BUSES, new String[]{
169           VALUE_SINGLE,
170           VALUE_COLOR,
171           VALUE_CONNECTED_COMPONENT
172       }, 0));
173       layoutGroup.addItem(options.addBool(ITEM_GRID_ENABLED, defaults.isGridRoutingEnabled()));
174       final OptionItem itemGridSpacing = layoutGroup.addItem(
175           options.addInt(ITEM_GRID_SPACING, defaults.getGridSpacing()));
176       itemGridSpacing.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
177       final OptionItem itemMinDistanceNodes = layoutGroup.addItem(
178           options.addInt(ITEM_MIN_DISTANCE_TO_NODES, defaults.getMinimumDistanceToNode()));
179       itemMinDistanceNodes.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
180       final OptionItem itemMinDistanceEdges = layoutGroup.addItem(
181           options.addInt(ITEM_MIN_DISTANCE_TO_EDGES, defaults.getMinimumDistanceToEdge()));
182       itemMinDistanceEdges.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
183       // Enable/disable items depending on specific values
184       optionConstraints.setEnabledOnValueEquals(ITEM_GRID_ENABLED, Boolean.TRUE, ITEM_GRID_SPACING);
185     }
186 
187     if (optionsSelection) {
188       // Group 'Backbone Selection'
189       final OptionGroup selectionGroup = new OptionGroup();
190       selectionGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_SELECTION);
191       // Populate group
192       final OptionItem itemPreferredBackboneCount = selectionGroup.addItem(
193           options.addInt(ITEM_PREFERRED_BACKBONE_COUNT, defaults.getPreferredBackboneSegmentCount()));
194       itemPreferredBackboneCount.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
195       final OptionItem itemMinBackboneLength = selectionGroup.addItem(
196           options.addDouble(ITEM_MINIMUM_BACKBONE_LENGTH, defaults.getMinimumBackboneSegmentLength()));
197       itemMinBackboneLength.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(1.0));      
198     }
199 
200     if (optionsRouting) {
201       // Group 'Routing and Recombination'
202       final OptionGroup routingGroup = new OptionGroup();
203       routingGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_ROUTING);
204       // Populate group
205       final OptionItem itemCrossingCost = routingGroup.addItem(
206           options.addDouble(ITEM_CROSSING_COST, defaults.getCrossingCost()));
207       itemCrossingCost.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(0.0));
208       final OptionItem itemCrossingRerouting = routingGroup.addItem(
209           options.addBool(ITEM_CROSSING_REROUTING, defaults.isReroutingEnabled()));
210       itemCrossingRerouting.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(0.0));
211       final OptionItem itemMinimumConnectionCount = routingGroup.addItem(
212           options.addInt(ITEM_MINIMUM_CONNECTIONS_COUNT, defaults.getMinimumBusConnectionsCount()));
213       itemMinimumConnectionCount.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
214     }
215   }
216 
217   /**
218    * Main module execution routine.
219    * Launches the module's underlying algorithm on the module's graph based on user options.
220    */
221   protected void mainrun() {
222     final BusRouter bus = new BusRouter();
223     
224     final OptionHandler options = getOptionHandler();
225     configure(bus, options);
226 
227     final Graph2D graph = getGraph2D();
228     prepareGraph(graph, options);
229     try {
230       launchLayouter(new EdgeHidingStage(bus));
231     } finally {
232       restoreGraph(graph, options);
233     }
234   }
235 
236   /**
237    * Prepares a <code>graph</code> depending on the given options for the
238    * module's layout algorithm.
239    * <br>
240    * Additional resources created by this method have to be freed up by calling
241    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
242    * layout calculation.  
243    * @param graph the graph to be prepared
244    * @param options the options for the module's layout algorithm
245    */
246   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
247     if (!optionsLayout) {
248       // nothing to do
249       return;
250     }
251     
252     
253     ////////////////////////////////////////////////////////////////////////////
254     // Bus assignment
255     ////////////////////////////////////////////////////////////////////////////
256     
257     // backup existing data providers to prevent loss of user settings
258     backupDataProvider(graph, BusRouter.EDGE_DESCRIPTOR_DPKEY);
259 
260     // Explicit variables for ITEM_BUSES and ITEM_SCOPE since these are often queried
261     final String busType = options.getString(ITEM_BUSES);
262     final Object scope = options.get(ITEM_SCOPE);
263 
264     // The following creates bus descriptors according to the set options for
265     // ITEM_SCOPE and ITEM_BUSES taking the current selection into account.
266     final EdgeMap descriptorMap = Maps.createHashedEdgeMap();
267     graph.addDataProvider(BusRouter.EDGE_DESCRIPTOR_DPKEY, descriptorMap);
268     final NodeMap node2CompId = Maps.createHashedNodeMap();
269     GraphConnectivity.connectedComponents(graph, node2CompId);
270     // Create the bus descriptors. For scopes SUBSET_BUS and PARTIAL, this is done for all edges since the bus IDs
271     // are required to determine which edges belong to the final scope.
272     if (VALUE_SCOPE_SUBSET.equals(scope)) {
273       for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
274         final Edge edge = ec.edge();
275         descriptorMap.set(edge, new BusDescriptor(getBusId(graph, edge, node2CompId, busType), false));
276       }
277     } else if (VALUE_SCOPE_EDGES_AT_SELECTED_NODES.equals(scope)) {
278       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
279         final Edge edge = ec.edge();
280         if (graph.isSelected(edge.source()) || graph.isSelected(edge.target())) {
281           descriptorMap.set(edge, new BusDescriptor(getBusId(graph, edge, node2CompId, busType), false));
282         }
283       }
284     } else if (VALUE_SCOPE_PARTIAL.equals(scope)) {
285       EdgeMap nonOrthogonalFixedEdges = null;
286       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
287         final Edge edge = ec.edge();
288         final boolean fixed = !graph.isSelected(edge.source()) && !graph.isSelected(edge.target());
289         if (fixed && !isOrthogonal(edge, graph)) {
290           if (nonOrthogonalFixedEdges == null) {
291             nonOrthogonalFixedEdges = Maps.createHashedEdgeMap();
292           }
293           // mark for hiding the fixed edges that are not orthogonal because BusRouter can not handle them
294           nonOrthogonalFixedEdges.setBool(edge, true);
295         }
296         if (nonOrthogonalFixedEdges != null) {
297           graph.addDataProvider(EdgeHidingStage.EDGE_HIDE_DPKEY, nonOrthogonalFixedEdges);
298         }
299         descriptorMap.set(edge, new BusDescriptor(getBusId(graph, edge, node2CompId, busType), fixed));
300       }
301     } else {
302       // else if VALUE_SCOPE_SUBSET_BUS.equals(scope) or VALUE_SCOPE_ALL.equals(scope)
303       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
304         final Edge edge = ec.edge();
305         descriptorMap.set(edge, new BusDescriptor(getBusId(graph, edge, node2CompId, busType), false));
306       }
307     }
308     
309     
310     ////////////////////////////////////////////////////////////////////////////
311     // Edge selection
312     ////////////////////////////////////////////////////////////////////////////
313 
314     // backup existing data providers to prevent loss of user settings
315     backupDataProvider(graph, BusRouter.EDGE_SUBSET_DPKEY);
316     
317     if (VALUE_SCOPE_SUBSET.equals(scope)) {
318       // The selected edges are in scope, and all of them are movable
319       graph.addDataProvider(BusRouter.EDGE_SUBSET_DPKEY, new DataProviderAdapter() {
320         public boolean getBool(Object dataHolder) {
321           return dataHolder instanceof Edge && graph.isSelected((Edge) dataHolder);
322         }
323       });
324     } else if (VALUE_SCOPE_EDGES_AT_SELECTED_NODES.equals(scope)) {
325       graph.addDataProvider(BusRouter.EDGE_SUBSET_DPKEY, new DataProviderAdapter() {
326         public boolean getBool(Object dataHolder) {
327           if (dataHolder instanceof Edge) {
328             final Edge edge = (Edge) dataHolder;
329             return graph.isSelected(edge.source()) || graph.isSelected(edge.target());
330           }
331           return false;
332         }
333       });
334     } else if (VALUE_SCOPE_SUBSET_BUS.equals(scope)) {
335       // Each bus with at least one selected edge is in scope, and all of their edges are movable.
336       final Set selectedIDs = new HashSet();
337       for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
338         selectedIDs.add(((BusDescriptor) descriptorMap.get(ec.edge())).getID());
339       }
340       graph.addDataProvider(BusRouter.EDGE_SUBSET_DPKEY, new DataProviderAdapter() {
341         public boolean getBool(Object dataHolder) {
342           return selectedIDs.contains(((BusDescriptor) descriptorMap.get(dataHolder)).getID());
343         }
344       });
345     } else if (VALUE_SCOPE_PARTIAL.equals(scope)) {
346       // Each bus with at least one selected node is in scope, and the adjacent edges of the selected
347       // nodes are movable.
348       final Set selectedIDs = new HashSet();
349       for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
350         final Node node = nc.node();
351         for (EdgeCursor ec = node.edges(); ec.ok(); ec.next()) {
352           selectedIDs.add(((BusDescriptor) descriptorMap.get(ec.edge())).getID());
353         }
354       }
355       graph.addDataProvider(BusRouter.EDGE_SUBSET_DPKEY, new DataProviderAdapter() {
356         public boolean getBool(Object dataHolder) {
357           return selectedIDs.contains(((BusDescriptor) descriptorMap.get(dataHolder)).getID());
358         }
359       });
360     }
361   }
362 
363   /**
364    * Restores the given <code>graph</code> by freeing up resources created by
365    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
366    * @param graph the graph for which <code>prepareGraph</code> has been called
367    * @param options the options for the module's layout algorithm
368    */
369   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
370     if (optionsLayout) {
371       // remove the data providers set by this module by restoring the initial state
372       restoreDataProvider(graph, BusRouter.EDGE_DESCRIPTOR_DPKEY);
373       restoreDataProvider(graph, BusRouter.EDGE_SUBSET_DPKEY);
374       graph.removeDataProvider(EdgeHidingStage.EDGE_HIDE_DPKEY);
375     }
376   }
377 
378   /**
379    * Configures the module's layout algorithm according to the given options.
380    * @param bus the <code>BusRouter</code> to be configured
381    * @param options the layout options to set
382    */
383   protected void configure(final BusRouter bus, final OptionHandler options) {
384 
385     if (optionsLayout) {
386       bus.setScope(toBusRouterScope(options.get(ITEM_SCOPE)));
387       bus.setGridRoutingEnabled(options.getBool(ITEM_GRID_ENABLED));
388       bus.setGridSpacing(options.getInt(ITEM_GRID_SPACING));
389       bus.setMinimumDistanceToNode(options.getInt(ITEM_MIN_DISTANCE_TO_NODES));
390       bus.setMinimumDistanceToEdge(options.getInt(ITEM_MIN_DISTANCE_TO_EDGES));
391     }
392 
393     if (optionsSelection) {
394       bus.setPreferredBackboneSegmentCount(options.getInt(ITEM_PREFERRED_BACKBONE_COUNT));
395       bus.setMinimumBackboneSegmentLength(options.getDouble(ITEM_MINIMUM_BACKBONE_LENGTH));
396     }
397 
398     if (optionsRouting) {
399       bus.setCrossingCost(options.getDouble(ITEM_CROSSING_COST));
400       bus.setReroutingEnabled(options.getBool(ITEM_CROSSING_REROUTING));
401       bus.setMinimumBusConnectionsCount(options.getInt(ITEM_MINIMUM_CONNECTIONS_COUNT));
402     }
403   }
404 
405   private static Object getBusId(final Graph2D graph, final Edge edge, final DataProvider component, final String busType) {
406     if (VALUE_COLOR.equals(busType)) {
407       return graph.getRealizer(edge).getLineColor();
408     } else if (VALUE_CONNECTED_COMPONENT.equals(busType)) {
409       return component.get(edge.source());
410     }
411     // else if VALUE_SINGLE.equals(busType)
412     return VALUE_SINGLE;
413   }
414 
415   private static byte toBusRouterScope(final Object scopeName) {
416     if (VALUE_SCOPE_ALL.equals(scopeName)) {
417       return BusRouter.SCOPE_ALL;
418     } else if (VALUE_SCOPE_SUBSET.equals(scopeName) ||
419                VALUE_SCOPE_SUBSET_BUS.equals(scopeName) ||
420                VALUE_SCOPE_PARTIAL.equals(scopeName) ||
421                VALUE_SCOPE_EDGES_AT_SELECTED_NODES.equals(scopeName)) {
422       return BusRouter.SCOPE_SUBSET;
423     } else {
424       return BusRouter.SCOPE_ALL;
425     }
426   }
427 
428   /**
429    * Utility method to check if an edge path only contains orthogonal segments.
430    *
431    * @param edge  edge to check
432    * @param graph owner of the given edge
433    *
434    * @return <code>true</code> if the given edge only contains orthogonal segments, <code>false</code> otherwise.
435    */
436   private static boolean isOrthogonal(final Edge edge, final LayoutGraph graph) {
437     final YPointPath path = graph.getPath(edge);
438     YPoint last = null;
439     for (YPointCursor cur = path.points(); cur.ok(); cur.next()) {
440       final YPoint current = cur.point();
441       if (last != null && Math.abs(last.x - current.x) > 0.00001 && Math.abs(last.y - current.y) > 0.00001) {
442         return false;
443       }
444       last = current;
445     }
446     return true;
447   }
448 
449   /**
450    * Hides some edges defined in a DP registered with {@link #EDGE_HIDE_DPKEY}, executes the core layout and
451    * un-hides the edges afterwards.
452    */
453   private static class EdgeHidingStage extends AbstractLayoutStage {
454 
455     private static final Object EDGE_HIDE_DPKEY = "y.module.BusRouterModule.EdgeHiderStage.EDGE_HIDE_DPKEY";
456 
457     EdgeHidingStage(Layouter core) {
458       super(core);
459     }
460 
461     public boolean canLayout(LayoutGraph graph) {
462       return canLayoutCore(graph);
463     }
464 
465     public void doLayout(LayoutGraph graph) {
466       GraphHider hider = null;
467       final DataProvider edge2ShouldHide = graph.getDataProvider(EDGE_HIDE_DPKEY);
468       if (edge2ShouldHide != null) {
469         hider = new GraphHider(graph);
470         for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
471           final Edge edge = ec.edge();
472           if (edge2ShouldHide.getBool(edge)) {
473             hider.hide(edge);
474           }
475         }
476       }
477 
478       doLayoutCore(graph);
479 
480       if (hider != null) {
481         hider.unhideEdges();
482       }
483     }
484   }
485 }
486