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.DataProvider;
34  import y.base.Edge;
35  import y.base.Node;
36  import y.base.NodeCursor;
37  import y.base.NodeMap;
38  import y.layout.ComponentLayouter;
39  import y.layout.PortConstraintKeys;
40  import y.layout.organic.OutputRestriction;
41  import y.layout.organic.SmartOrganicLayouter;
42  import y.option.ConstraintManager;
43  import y.option.ConstraintManager.Condition;
44  import y.option.DefaultEditorFactory;
45  import y.option.DoubleOptionItem;
46  import y.option.EnumOptionItem;
47  import y.option.IntOptionItem;
48  import y.option.OptionHandler;
49  import y.option.OptionGroup;
50  import y.option.OptionItem;
51  import y.util.DataProviderAdapter;
52  import y.util.DataProviders;
53  import y.util.Maps;
54  import y.view.Arrow;
55  import y.view.EdgeRealizer;
56  import y.view.Graph2D;
57  import y.view.Graph2DLayoutExecutor;
58  import y.view.Selections;
59  import y.view.hierarchy.HierarchyManager;
60  
61  import java.awt.Rectangle;
62  
63  /**
64   * This module represents an interactive configurator and launcher for
65   * {@link y.layout.organic.SmartOrganicLayouter}.
66   *
67   *
68   * @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
69   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/smart_organic_layouter#smart_organic_layouter" target="_blank">Section Organic Layout Style</a> in the yFiles for Java Developer's Guide
70   */
71  public class SmartOrganicLayoutModule extends LayoutModule {
72    //// Module 'Smart Organic Layout'
73    protected static final String MODULE_SMARTORGANIC = "SMARTORGANIC";
74    
75    //// Section 'Visual'
76    protected static final String SECTION_VISUAL = "VISUAL";
77    // Section 'Visual' items
78    protected static final String ITEM_SCOPE = "SCOPE";
79    protected static final String VALUE_SCOPE_ALL = "ALL";
80    protected static final String VALUE_SCOPE_MAINLY_SUBSET = "MAINLY_SUBSET";
81    protected static final String VALUE_SCOPE_SUBSET = "SUBSET";
82    protected static final String ITEM_PREFERRED_EDGE_LENGTH = "PREFERRED_EDGE_LENGTH";
83    protected static final String ITEM_CONSIDER_NODE_LABELS = "CONSIDER_NODE_LABELS";
84    protected static final String ITEM_ALLOW_NODE_OVERLAPS = "ALLOW_NODE_OVERLAPS";
85    protected static final String ITEM_MINIMAL_NODE_DISTANCE = "MINIMAL_NODE_DISTANCE";
86    protected static final String ITEM_AVOID_NODE_EDGE_OVERLAPS = "AVOID_NODE_EDGE_OVERLAPS";
87    protected static final String ITEM_COMPACTNESS = "COMPACTNESS";
88    protected static final String ITEM_USE_AUTO_CLUSTERING = "USE_AUTO_CLUSTERING";
89    protected static final String ITEM_AUTO_CLUSTERING_QUALITY = "AUTO_CLUSTERING_QUALITY";
90    
91    //// Section 'Restrictions'
92    protected static final String SECTION_RESTRICTIONS = "RESTRICTIONS";
93    // Section 'Restrictions' items
94    protected static final String ITEM_RESTRICT_OUTPUT = "RESTRICT_OUTPUT";
95    protected static final String VALUE_NONE = "NONE";
96    protected static final String VALUE_OUTPUT_CAGE = "OUTPUT_CAGE";
97    protected static final String VALUE_OUTPUT_CIRCULAR_CAGE = "OUTPUT_CIRCULAR_CAGE";
98    protected static final String VALUE_OUTPUT_AR = "OUTPUT_AR";
99    protected static final String VALUE_OUTPUT_ELLIPTICAL_CAGE = "OUTPUT_ELLIPTICAL_CAGE";
100   protected static final String TITLE_OUTPUT_CAGE = "OUTPUT_CAGE";
101   protected static final String ITEM_RECT_CAGE_USE_VIEW = "RECT_CAGE_USE_VIEW";
102   protected static final String ITEM_CAGE_X = "CAGE_X";
103   protected static final String ITEM_CAGE_Y = "CAGE_Y";
104   protected static final String ITEM_CAGE_WIDTH = "CAGE_WIDTH";
105   protected static final String ITEM_CAGE_HEIGHT = "CAGE_HEIGHT";
106   protected static final String TITLE_OUTPUT_CIRCULAR_CAGE = "OUTPUT_CIRCULAR_CAGE";
107   protected static final String ITEM_CIRC_CAGE_USE_VIEW = "CIRC_CAGE_USE_VIEW";
108   protected static final String ITEM_CAGE_CENTER_X = "CAGE_CENTER_X";
109   protected static final String ITEM_CAGE_CENTER_Y = "CAGE_CENTER_Y";
110   protected static final String ITEM_CAGE_RADIUS = "CAGE_RADIUS";
111   protected static final String TITLE_OUTPUT_AR = "OUTPUT_AR";
112   protected static final String ITEM_AR_CAGE_USE_VIEW = "AR_CAGE_USE_VIEW";
113   protected static final String ITEM_CAGE_RATIO = "CAGE_RATIO";
114   protected static final String TITLE_OUTPUT_ELLIPTICAL_CAGE = "OUTPUT_ELLIPTICAL_CAGE";
115   protected static final String ITEM_ELL_CAGE_USE_VIEW = "ELL_CAGE_USE_VIEW";
116   protected static final String ITEM_ELLIPTICAL_CAGE_X = "ELLIPTICAL_CAGE_X";
117   protected static final String ITEM_ELLIPTICAL_CAGE_Y = "ELLIPTICAL_CAGE_Y";
118   protected static final String ITEM_ELLIPTICAL_CAGE_WIDTH = "ELLIPTICAL_CAGE_WIDTH";
119   protected static final String ITEM_ELLIPTICAL_CAGE_HEIGHT = "ELLIPTICAL_CAGE_HEIGHT";
120   
121   //// Section 'Grouping'
122   protected static final String SECTION_GROUPING = "GROUPING";
123   // Section 'Grouping' items
124   protected static final String ITEM_GROUP_LAYOUT_POLICY = "GROUP_LAYOUT_POLICY";
125   protected static final String VALUE_LAYOUT_GROUPS = "LAYOUT_GROUPS";
126   protected static final String VALUE_FIX_GROUP_CONTENTS = "FIX_GROUP_CONTENTS";
127   protected static final String VALUE_FIX_GROUP_BOUNDS = "FIX_GROUP_BOUNDS";
128   protected static final String VALUE_IGNORE_GROUPS = "IGNORE_GROUPS";
129   protected static final String ITEM_USE_AUTOMATIC_GROUP_NODE_COMPACTION = "USE_AUTOMATIC_GROUP_NODE_COMPACTION";
130   protected static final String ITEM_GROUP_COMPACTNESS = "GROUP_COMPACTNESS";
131   
132   //// Section 'Algorithm'
133   protected static final String SECTION_ALGORITHM = "ALGORITHM";
134   // Section 'Algorithm' items
135   protected static final String ITEM_QUALITY_TIME_RATIO = "QUALITY_TIME_RATIO";
136   protected static final String ITEM_MAXIMAL_DURATION = "MAXIMAL_DURATION";
137   protected static final String ITEM_ACTIVATE_DETERMINISTIC_MODE = "ACTIVATE_DETERMINISTIC_MODE";
138   protected static final String ITEM_ALLOW_MULTI_THREADING = "ALLOW_MULTI_THREADING";
139 
140   //// Section 'Substructure Layout'
141   protected static final String SECTION_SUBSTRUCTURE_LAYOUT = "SUBSTRUCTURE_LAYOUT";
142   protected static final String ITEM_CONSIDER_EDGE_DIRECTION = "CONSIDER_EDGE_DIRECTION";
143   protected static final String ITEM_USE_EDGE_GROUPING = "USE_EDGE_GROUPING";
144   protected static final String ITEM_CYCLE = "CYCLE";
145   protected static final String VALUE_CYCLE_NONE = "NONE";
146   protected static final String VALUE_CYCLE_CIRCULAR = "CIRCULAR";
147   protected static final String ITEM_CHAIN = "CHAIN";
148   protected static final String VALUE_CHAIN_NONE = "NONE";
149   protected static final String VALUE_CHAIN_RECTANGULAR = "RECTANGULAR";
150   protected static final String VALUE_CHAIN_STRAIGHT_LINE = "STRAIGHT_LINE";
151   protected static final String ITEM_STAR = "STAR";
152   protected static final String VALUE_STAR_NONE = "NONE";
153   protected static final String VALUE_STAR_SEPARATED_RADIAL = "SEPARATED_RADIAL";
154   protected static final String VALUE_STAR_RADIAL = "RADIAL";
155   protected static final String VALUE_STAR_CIRCULAR = "CIRCULAR";
156   protected static final String ITEM_PARALLEL = "PARALLEL";
157   protected static final String VALUE_PARALLEL_NONE = "NONE";
158   protected static final String VALUE_PARALLEL_STRAIGHT_LINE = "STRAIGHT_LINE";
159   protected static final String VALUE_PARALLEL_RECTANGULAR = "RECTANGULAR";
160   protected static final String VALUE_PARALLEL_RADIAL = "RADIAL";
161 
162   private boolean isNodeDPAddedByModule;
163 
164   /**
165    * Creates an instance of this module.
166    */
167   public SmartOrganicLayoutModule() {
168     super(MODULE_SMARTORGANIC);
169     setPortIntersectionCalculatorEnabled(true);
170   }
171 
172   /**
173    * Creates an OptionHandler and adds the option items used by this module.
174    * @return the created <code>OptionHandler</code> providing module related options
175    */
176   protected OptionHandler createOptionHandler() {
177     final OptionHandler options = new OptionHandler(getModuleName());
178     final ConstraintManager optionConstraints = new ConstraintManager(options);
179     // Defaults provider
180     final SmartOrganicLayouter defaults = new SmartOrganicLayouter();
181 
182     //// Section 'Visual'
183     options.useSection(SECTION_VISUAL);
184 
185     // Populate section
186     options.addEnum(ITEM_SCOPE, new String[]{
187         VALUE_SCOPE_ALL,
188         VALUE_SCOPE_MAINLY_SUBSET,
189         VALUE_SCOPE_SUBSET
190     }, defaults.getScope());
191     options.addInt(ITEM_PREFERRED_EDGE_LENGTH, (int)defaults.getPreferredEdgeLength(), 5, 500);
192     final OptionItem itemConsiderNodeLabels =
193         options.addBool(ITEM_CONSIDER_NODE_LABELS,defaults.isConsiderNodeLabelsEnabled());
194     final OptionItem itemAllowNodeOverlaps = options.addBool(ITEM_ALLOW_NODE_OVERLAPS, defaults.isNodeOverlapsAllowed());
195     final OptionItem itemMinimalNodeDistance =
196             options.addDouble(ITEM_MINIMAL_NODE_DISTANCE, defaults.getMinimalNodeDistance(), 0, 100, 0);
197     options.addBool(ITEM_AVOID_NODE_EDGE_OVERLAPS, false);
198     options.addDouble(ITEM_COMPACTNESS, defaults.getCompactness(),0,1);
199     final OptionItem itemUseAutoClustering =
200         options.addBool(ITEM_USE_AUTO_CLUSTERING, defaults.isAutoClusteringEnabled());
201     final OptionItem itemAutoClusteringQuality =
202         options.addDouble(ITEM_AUTO_CLUSTERING_QUALITY, defaults.getAutoClusteringQuality(), 0, 1);
203     // Enable/disable items depending on specific values
204     Condition condition =
205         optionConstraints.createConditionValueEquals(itemAllowNodeOverlaps, Boolean.FALSE).or(
206             optionConstraints.createConditionValueEquals(itemConsiderNodeLabels, Boolean.TRUE));
207     optionConstraints.setEnabledOnCondition(condition, itemMinimalNodeDistance);
208     optionConstraints.setEnabledOnValueEquals(itemConsiderNodeLabels, Boolean.FALSE, itemAllowNodeOverlaps);
209     optionConstraints.setEnabledOnValueEquals(itemUseAutoClustering, Boolean.TRUE, itemAutoClusteringQuality);
210     
211     //// Section 'Restrictions'
212     options.useSection(SECTION_RESTRICTIONS);
213     // Populate section
214     final Object ctrId = new Object();
215     final EnumOptionItem itemRestrictOutput = options.addEnum(ITEM_RESTRICT_OUTPUT, new String[]{
216         VALUE_NONE,
217         VALUE_OUTPUT_CAGE,
218         VALUE_OUTPUT_CIRCULAR_CAGE,
219         VALUE_OUTPUT_AR,
220         VALUE_OUTPUT_ELLIPTICAL_CAGE
221     }, 0);
222     itemRestrictOutput.setAttribute(DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId);
223     
224     // Group 'Rectangular'
225     final OptionGroup cageGroup = new OptionGroup();
226     cageGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_OUTPUT_CAGE);
227     // Populate group
228     cageGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId);
229     cageGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CARD_ID, VALUE_OUTPUT_CAGE);
230     final OptionItem itemRectCageUseView = cageGroup.addItem(options.addBool(ITEM_RECT_CAGE_USE_VIEW, true));
231     final OptionItem itemCageX = cageGroup.addItem(options.addDouble(ITEM_CAGE_X, 0.0d));
232     final OptionItem itemCageY = cageGroup.addItem(options.addDouble(ITEM_CAGE_Y, 0.0d));
233     final OptionItem itemCageWidth = cageGroup.addItem(options.addDouble(ITEM_CAGE_WIDTH, 1000.0d));
234     itemCageWidth.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(Double.MIN_VALUE));
235     final OptionItem itemCageHeight = cageGroup.addItem(options.addDouble(ITEM_CAGE_HEIGHT, 1000.0d));
236     itemCageHeight.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(Double.MIN_VALUE));
237     // Enable/disable items depending on specific values
238     optionConstraints.setEnabledOnValueEquals(itemRestrictOutput, VALUE_OUTPUT_CAGE, cageGroup);
239     condition = optionConstraints.createConditionValueEquals(itemRestrictOutput, VALUE_OUTPUT_CAGE).and(
240         optionConstraints.createConditionValueEquals(itemRectCageUseView, Boolean.FALSE));
241     optionConstraints.setEnabledOnCondition(condition, itemCageX);
242     optionConstraints.setEnabledOnCondition(condition, itemCageY);
243     optionConstraints.setEnabledOnCondition(condition, itemCageWidth);
244     optionConstraints.setEnabledOnCondition(condition, itemCageHeight);
245 
246     // Group 'Circular'
247     final OptionGroup circularCageGroup = new OptionGroup();
248     circularCageGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_OUTPUT_CIRCULAR_CAGE);
249     // Populate group
250     circularCageGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId);
251     circularCageGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CARD_ID, VALUE_OUTPUT_CIRCULAR_CAGE);
252     final OptionItem itemCircCageUseView = circularCageGroup.addItem(options.addBool(ITEM_CIRC_CAGE_USE_VIEW, true));
253     final OptionItem itemCageCenterX = circularCageGroup.addItem(options.addDouble(ITEM_CAGE_CENTER_X, 0.0d));
254     final OptionItem itemCageCenterY = circularCageGroup.addItem(options.addDouble(ITEM_CAGE_CENTER_Y, 0.0d));
255     final OptionItem itemCageRadius = circularCageGroup.addItem(options.addDouble(ITEM_CAGE_RADIUS, 1000.0d));
256     itemCageRadius.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(Double.MIN_VALUE));
257     // Enable/disable items depending on specific values
258     optionConstraints.setEnabledOnValueEquals(itemRestrictOutput, VALUE_OUTPUT_CIRCULAR_CAGE, circularCageGroup);
259     condition = optionConstraints.createConditionValueEquals(itemRestrictOutput, VALUE_OUTPUT_CIRCULAR_CAGE).and(
260         optionConstraints.createConditionValueEquals(itemCircCageUseView, Boolean.FALSE));
261     optionConstraints.setEnabledOnCondition(condition, itemCageCenterX);
262     optionConstraints.setEnabledOnCondition(condition, itemCageCenterY);
263     optionConstraints.setEnabledOnCondition(condition, itemCageRadius);
264 
265     // Group 'Aspect Ratio'
266     final OptionGroup arGroup = new OptionGroup();
267     arGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_OUTPUT_AR);
268     // Populate group
269     arGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId);
270     arGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CARD_ID, VALUE_OUTPUT_AR);
271     final OptionItem itemArCageUseView = arGroup.addItem(options.addBool(ITEM_AR_CAGE_USE_VIEW, true));
272     optionConstraints.setEnabledOnValueEquals(itemRestrictOutput, VALUE_OUTPUT_AR, arGroup);
273     condition = optionConstraints.createConditionValueEquals(itemRestrictOutput, VALUE_OUTPUT_AR).and(
274         optionConstraints.createConditionValueEquals(itemArCageUseView, Boolean.FALSE));
275     optionConstraints.setEnabledOnCondition(condition, arGroup.addItem(options.addDouble(ITEM_CAGE_RATIO, 1.0d)));
276 
277     // Group 'Elliptical'
278     final OptionGroup ellipticalCageGroup = new OptionGroup();
279     ellipticalCageGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_OUTPUT_ELLIPTICAL_CAGE);
280     // Populate group
281     ellipticalCageGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId);
282     ellipticalCageGroup.setAttribute(DefaultEditorFactory.ATTRIBUTE_CARD_ID, VALUE_OUTPUT_ELLIPTICAL_CAGE);
283     final OptionItem itemEllCageUseView = ellipticalCageGroup.addItem(options.addBool(ITEM_ELL_CAGE_USE_VIEW, true));
284     final OptionItem itemEllipticalCageX = ellipticalCageGroup.addItem(options.addDouble(ITEM_ELLIPTICAL_CAGE_X, 0.0d));
285     final OptionItem itemEllipticalCageY = ellipticalCageGroup.addItem(options.addDouble(ITEM_ELLIPTICAL_CAGE_Y, 0.0d));
286     final OptionItem itemEllipticalCageWidth = ellipticalCageGroup.addItem(
287         options.addDouble(ITEM_ELLIPTICAL_CAGE_WIDTH, 1000.0d));
288     itemEllipticalCageWidth.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(Double.MIN_VALUE));
289     final OptionItem itemEllipticalCageHeight = ellipticalCageGroup.addItem(
290             options.addDouble(ITEM_ELLIPTICAL_CAGE_HEIGHT, 1000.0d));
291     itemEllipticalCageHeight.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(Double.MIN_VALUE));
292     // Enable/disable items depending on specific values
293     optionConstraints.setEnabledOnValueEquals(itemRestrictOutput, VALUE_OUTPUT_ELLIPTICAL_CAGE, ellipticalCageGroup);
294     condition = optionConstraints.createConditionValueEquals(itemRestrictOutput, VALUE_OUTPUT_ELLIPTICAL_CAGE).and(
295         optionConstraints.createConditionValueEquals(itemEllCageUseView, Boolean.FALSE));
296     optionConstraints.setEnabledOnCondition(condition, itemEllipticalCageX);
297     optionConstraints.setEnabledOnCondition(condition, itemEllipticalCageY);
298     optionConstraints.setEnabledOnCondition(condition, itemEllipticalCageWidth);
299     optionConstraints.setEnabledOnCondition(condition, itemEllipticalCageHeight);
300     
301     //// Section 'Grouping'
302     options.useSection(SECTION_GROUPING);
303     // Populate section
304     options.addEnum(ITEM_GROUP_LAYOUT_POLICY, new String[]{
305         VALUE_LAYOUT_GROUPS,
306         VALUE_FIX_GROUP_CONTENTS,
307         VALUE_FIX_GROUP_BOUNDS,
308         VALUE_IGNORE_GROUPS
309     }, 0);
310     final OptionItem itemUseAutomaticGroupNodeCompaction =
311         options.addBool(ITEM_USE_AUTOMATIC_GROUP_NODE_COMPACTION, defaults.isAutomaticGroupNodeCompactionEnabled());
312     final OptionItem itemGroupCompactness =
313         options.addDouble(ITEM_GROUP_COMPACTNESS, defaults.getGroupNodeCompactness(), 0, 1);
314     // Enable/disable items depending on specific values
315     optionConstraints.setEnabledOnValueEquals(itemUseAutomaticGroupNodeCompaction, Boolean.FALSE, itemGroupCompactness);
316 
317     //// Section 'Algorithm'
318     options.useSection(SECTION_ALGORITHM);
319     // Populate section
320     final OptionItem itemQualityTimeRatio =
321         options.addDouble(ITEM_QUALITY_TIME_RATIO, defaults.getQualityTimeRatio(), 0, 1);
322     itemQualityTimeRatio.setAttribute(DefaultEditorFactory.ATTRIBUTE_MIN_VALUE_LABEL_TEXT, "SPEED");
323     itemQualityTimeRatio.setAttribute(DefaultEditorFactory.ATTRIBUTE_MAX_VALUE_LABEL_TEXT, "QUALITY");
324     options.addInt(ITEM_MAXIMAL_DURATION, (int)(defaults.getMaximumDuration()/1000))
325       .setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(0));
326     options.addBool(ITEM_ACTIVATE_DETERMINISTIC_MODE, defaults.isDeterministic());
327     options.addBool(ITEM_ALLOW_MULTI_THREADING, true);
328 
329     //// Section 'Substructure Layout'
330     options.useSection(SECTION_SUBSTRUCTURE_LAYOUT);
331     options.addEnum(ITEM_CYCLE, new String[]{
332         VALUE_CYCLE_NONE,
333         VALUE_CYCLE_CIRCULAR
334     }, 1);
335     options.addEnum(ITEM_CHAIN, new String[]{
336         VALUE_CHAIN_NONE,
337         VALUE_CHAIN_RECTANGULAR,
338         VALUE_CHAIN_STRAIGHT_LINE
339     }, 0);
340     options.addEnum(ITEM_STAR, new String[]{
341         VALUE_STAR_NONE,
342         VALUE_STAR_SEPARATED_RADIAL,
343         VALUE_STAR_RADIAL,
344         VALUE_CYCLE_CIRCULAR
345     }, 2);
346     options.addEnum(ITEM_PARALLEL, new String[]{
347         VALUE_PARALLEL_NONE,
348         VALUE_PARALLEL_STRAIGHT_LINE,
349         VALUE_PARALLEL_RECTANGULAR,
350         VALUE_PARALLEL_RADIAL
351     }, 3);
352     options.addBool(ITEM_CONSIDER_EDGE_DIRECTION, false);
353     options.addBool(ITEM_USE_EDGE_GROUPING, true);
354 
355     return options;
356   }
357 
358   /**
359    * Main module execution routine.
360    * Launches the module's underlying algorithm on the module's graph based on user options.
361    */
362   protected void mainrun() {
363     final SmartOrganicLayouter organic = new SmartOrganicLayouter();
364     
365     final OptionHandler options = getOptionHandler();
366     configure(organic, options);
367 
368     final Graph2D graph = getGraph2D();
369 
370     final Graph2DLayoutExecutor layoutExecutor = getLayoutExecutor();
371     final boolean wasConfiguringGrouping = layoutExecutor.isConfiguringGrouping();
372     layoutExecutor.setConfiguringGrouping(isGroupingNodes(graph, options.getString(ITEM_GROUP_LAYOUT_POLICY)));
373 
374     prepareGraph(graph, options);
375     try {
376       launchLayouter(organic);
377     } finally {
378       restoreGraph(graph, options);
379       layoutExecutor.setConfiguringGrouping(wasConfiguringGrouping);
380     }
381   }
382 
383   /**
384    * Prepares a <code>graph</code> depending on the given options for the
385    * module's layout algorithm.
386    * <br>
387    * Additional resources created by this method have to be freed up by calling
388    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
389    * layout calculation.  
390    * @param graph the graph to be prepared
391    * @param options the options for the module's layout algorithm
392    */
393   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
394     isNodeDPAddedByModule = false;
395     final String policy = options.getString(ITEM_GROUP_LAYOUT_POLICY);
396     final boolean grouping = isGroupingNodes(graph, policy);
397     if (VALUE_FIX_GROUP_BOUNDS.equals(policy) && grouping) {
398       final NodeMap nodeMap = Maps.createHashedNodeMap();
399       for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
400         final Node node = nodeCursor.node();
401         if (HierarchyManager.getInstance(graph).isGroupNode(node)) {
402           nodeMap.set(node, SmartOrganicLayouter.GROUP_NODE_MODE_FIX_BOUNDS);
403         }
404       }
405       // backup existing data providers to prevent loss of user settings
406       backupDataProvider(graph, SmartOrganicLayouter.GROUP_NODE_MODE_DATA);
407       graph.addDataProvider(SmartOrganicLayouter.GROUP_NODE_MODE_DATA, nodeMap);
408       isNodeDPAddedByModule = true;
409     } else if (VALUE_FIX_GROUP_CONTENTS.equals(policy) && grouping) {
410       final NodeMap nodeMap = Maps.createHashedNodeMap();
411       for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
412         final Node node = nodeCursor.node();
413         if (HierarchyManager.getInstance(graph).isGroupNode(node)) {
414           nodeMap.set(node, SmartOrganicLayouter.GROUP_NODE_MODE_FIX_CONTENTS);
415         }
416       }
417       // backup existing data providers to prevent loss of user settings
418       backupDataProvider(graph, SmartOrganicLayouter.GROUP_NODE_MODE_DATA);
419       graph.addDataProvider(SmartOrganicLayouter.GROUP_NODE_MODE_DATA, nodeMap);
420       isNodeDPAddedByModule = true;
421     }
422     // backup existing data providers to prevent loss of user settings
423     backupDataProvider(graph, SmartOrganicLayouter.NODE_SUBSET_DATA);
424     graph.addDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA, Selections.createSelectionNodeMap(graph));
425 
426     if (options.getBool(ITEM_CONSIDER_EDGE_DIRECTION)) {
427       // backup existing data providers to prevent loss of user settings
428       backupDataProvider(graph, SmartOrganicLayouter.EDGE_DIRECTEDNESS_DPKEY);
429       graph.addDataProvider(
430           SmartOrganicLayouter.EDGE_DIRECTEDNESS_DPKEY, new EdgeDirectednessProvider(graph));
431     }
432     if (options.getBool(ITEM_USE_EDGE_GROUPING)) {
433       // backup existing data providers to prevent loss of user settings
434       backupDataProvider(graph, PortConstraintKeys.SOURCE_GROUPID_KEY);
435       backupDataProvider(graph, PortConstraintKeys.TARGET_GROUPID_KEY);
436       final DataProvider edgeGroupIdDP = DataProviders.createConstantDataProvider("Group");
437       graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, edgeGroupIdDP);
438       graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, edgeGroupIdDP);
439     }
440   }
441 
442   /**
443    * Restores the given <code>graph</code> by freeing up resources created by
444    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
445    * @param graph the graph for which <code>prepareGraph</code> has been called
446    * @param options the options for the module's layout algorithm
447    */
448   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
449     // remove the data providers set by this module by restoring the initial state
450     restoreDataProvider(graph, SmartOrganicLayouter.NODE_SUBSET_DATA);
451 
452     if (isNodeDPAddedByModule) {
453       isNodeDPAddedByModule = false;
454       restoreDataProvider(graph, SmartOrganicLayouter.GROUP_NODE_MODE_DATA);
455     }
456     if (options.getBool(ITEM_CONSIDER_EDGE_DIRECTION)) {
457       // remove the data providers set by this module by restoring the initial state
458       restoreDataProvider(graph, SmartOrganicLayouter.EDGE_DIRECTEDNESS_DPKEY);
459     }
460     if (options.getBool(ITEM_USE_EDGE_GROUPING)) {
461       // remove the data providers set by this module by restoring the initial state
462       restoreDataProvider(graph, PortConstraintKeys.SOURCE_GROUPID_KEY);
463       restoreDataProvider(graph, PortConstraintKeys.TARGET_GROUPID_KEY);
464     }
465   }
466 
467   /**
468    * Configures the module's layout algorithm according to the given options.
469    * @param organic the <code>SmartOrganicLayouter</code> to be configured
470    * @param options the layout options to set
471    */
472   protected void configure(final SmartOrganicLayouter organic, final OptionHandler options) {
473     organic.setPreferredEdgeLength(options.getInt(SECTION_VISUAL, ITEM_PREFERRED_EDGE_LENGTH));
474     
475     boolean considerNodeLabels = options.getBool(SECTION_VISUAL, ITEM_CONSIDER_NODE_LABELS);
476     organic.setConsiderNodeLabelsEnabled(considerNodeLabels);
477     organic.setNodeOverlapsAllowed(options.getBool(SECTION_VISUAL, ITEM_ALLOW_NODE_OVERLAPS) && !considerNodeLabels);
478     
479     organic.setMinimalNodeDistance(options.getDouble(SECTION_VISUAL, ITEM_MINIMAL_NODE_DISTANCE));
480     
481     final String is = options.getString(SECTION_VISUAL, ITEM_SCOPE);
482     if (VALUE_SCOPE_SUBSET.equals(is)) {
483       organic.setScope(SmartOrganicLayouter.SCOPE_SUBSET);
484     } else if (VALUE_SCOPE_MAINLY_SUBSET.equals(is)) {
485       organic.setScope(SmartOrganicLayouter.SCOPE_MAINLY_SUBSET);
486     } else {
487       // else if VALUE_SCOPE_ALL.equals(is)
488       organic.setScope(SmartOrganicLayouter.SCOPE_ALL);
489     }
490     
491     organic.setCompactness(options.getDouble(SECTION_VISUAL, ITEM_COMPACTNESS));
492     organic.setAutomaticGroupNodeCompactionEnabled(options.getBool(ITEM_USE_AUTOMATIC_GROUP_NODE_COMPACTION));
493     organic.setGroupNodeCompactness(options.getDouble(ITEM_GROUP_COMPACTNESS));
494     //Doesn't really make sense to ignore node sizes (for certain configurations, this setting
495     //doesn't have an effect anyway)
496     organic.setNodeSizeAware(true);
497     organic.setAutoClusteringEnabled(options.getBool(ITEM_USE_AUTO_CLUSTERING));
498     organic.setAutoClusteringQuality(options.getDouble(ITEM_AUTO_CLUSTERING_QUALITY));
499     organic.setNodeEdgeOverlapAvoided(options.getBool(ITEM_AVOID_NODE_EDGE_OVERLAPS));
500     organic.setDeterministic(options.getBool(SECTION_ALGORITHM, ITEM_ACTIVATE_DETERMINISTIC_MODE));
501     organic.setMultiThreadingAllowed(options.getBool(SECTION_ALGORITHM, ITEM_ALLOW_MULTI_THREADING));
502     organic.setMaximumDuration(1000L * options.getInt(SECTION_ALGORITHM, ITEM_MAXIMAL_DURATION));
503     organic.setQualityTimeRatio(options.getDouble(SECTION_ALGORITHM, ITEM_QUALITY_TIME_RATIO));
504 
505     final String itemCycle = options.getString(SECTION_SUBSTRUCTURE_LAYOUT, ITEM_CYCLE);
506     if (VALUE_CYCLE_CIRCULAR.equals(itemCycle)) {
507       organic.setCycleSubstructureStyle(SmartOrganicLayouter.CYCLE_SUBSTRUCTURE_STYLE_CIRCULAR);
508     } else {
509       organic.setCycleSubstructureStyle(SmartOrganicLayouter.CYCLE_SUBSTRUCTURE_STYLE_NONE);
510     }
511     final String itemChain = options.getString(SECTION_SUBSTRUCTURE_LAYOUT, ITEM_CHAIN);
512     if (VALUE_CHAIN_STRAIGHT_LINE.equals(itemChain)) {
513       organic.setChainSubstructureStyle(SmartOrganicLayouter.CHAIN_SUBSTRUCTURE_STYLE_STRAIGHT_LINE);
514     } else if (VALUE_CHAIN_RECTANGULAR.equals(itemChain)) {
515       organic.setChainSubstructureStyle(SmartOrganicLayouter.CHAIN_SUBSTRUCTURE_STYLE_RECTANGULAR);
516     } else {
517       organic.setChainSubstructureStyle(SmartOrganicLayouter.CHAIN_SUBSTRUCTURE_STYLE_NONE);
518     }
519     final String itemParallel = options.getString(SECTION_SUBSTRUCTURE_LAYOUT, ITEM_PARALLEL);
520     if (VALUE_PARALLEL_STRAIGHT_LINE.equals(itemParallel)) {
521       organic.setParallelSubstructureStyle(SmartOrganicLayouter.PARALLEL_SUBSTRUCTURE_STYLE_STRAIGHT_LINE);
522     } else if (VALUE_PARALLEL_RECTANGULAR.equals(itemParallel)) {
523       organic.setParallelSubstructureStyle(SmartOrganicLayouter.PARALLEL_SUBSTRUCTURE_STYLE_RECTANGULAR);
524     } else if (VALUE_PARALLEL_RADIAL.equals(itemParallel)) {
525       organic.setParallelSubstructureStyle(SmartOrganicLayouter.PARALLEL_SUBSTRUCTURE_STYLE_RADIAL);
526     } else {
527       organic.setParallelSubstructureStyle(SmartOrganicLayouter.PARALLEL_SUBSTRUCTURE_STYLE_NONE);
528     }
529     final String itemStar = options.getString(SECTION_SUBSTRUCTURE_LAYOUT, ITEM_STAR);
530     if (VALUE_STAR_SEPARATED_RADIAL.equals(itemStar)) {
531       organic.setStarSubstructureStyle(SmartOrganicLayouter.STAR_SUBSTRUCTURE_STYLE_SEPARATED_RADIAL);
532     } else if (VALUE_STAR_RADIAL.equals(itemStar)) {
533       organic.setStarSubstructureStyle(SmartOrganicLayouter.STAR_SUBSTRUCTURE_STYLE_RADIAL);
534     } else if (VALUE_STAR_CIRCULAR.equals(itemStar)) {
535       organic.setStarSubstructureStyle(SmartOrganicLayouter.STAR_SUBSTRUCTURE_STYLE_CIRCULAR);
536     } else {
537       organic.setStarSubstructureStyle(SmartOrganicLayouter.STAR_SUBSTRUCTURE_STYLE_NONE);
538     }
539     
540     ((ComponentLayouter) organic.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
541     
542     final String restrictOutput = options.getString(ITEM_RESTRICT_OUTPUT);
543     if (VALUE_NONE.equals(restrictOutput)) {
544       organic.setComponentLayouterEnabled(true);
545       organic.setOutputRestriction(OutputRestriction.NONE);
546     } else if (VALUE_OUTPUT_CAGE.equals(restrictOutput)) {
547       final double x;
548       final double y;
549       final double w;
550       final double h;
551       if (options.getBool(ITEM_RECT_CAGE_USE_VIEW) && getGraph2DView() != null) {
552         Rectangle visibleRect = getGraph2DView().getVisibleRect();
553         x = visibleRect.x;
554         y = visibleRect.y;
555         w = visibleRect.width;
556         h = visibleRect.height;
557       } else {
558         x = options.getDouble(ITEM_CAGE_X);
559         y = options.getDouble(ITEM_CAGE_Y);
560         w = options.getDouble(ITEM_CAGE_WIDTH);
561         h = options.getDouble(ITEM_CAGE_HEIGHT);
562       }
563       
564       organic.setOutputRestriction(
565           OutputRestriction.createRectangularCageRestriction(x, y, w, h));
566       organic.setComponentLayouterEnabled(false);
567     } else if (VALUE_OUTPUT_CIRCULAR_CAGE.equals(restrictOutput)) {
568       final double x;
569       final double y;
570       final double radius;
571       if (options.getBool(ITEM_CIRC_CAGE_USE_VIEW) && getGraph2DView() != null) {
572         Rectangle visibleRect = getGraph2DView().getVisibleRect();
573         x = visibleRect.getCenterX();
574         y = visibleRect.getCenterY();
575         radius = Math.min(visibleRect.width, visibleRect.height) * 0.5d;
576       } else {
577         x = options.getDouble(ITEM_CAGE_CENTER_X);
578         y = options.getDouble(ITEM_CAGE_CENTER_Y);
579         radius = options.getDouble(ITEM_CAGE_RADIUS);
580       }
581       
582       organic.setOutputRestriction(OutputRestriction.createCircularCageRestriction(x, y, radius));
583       organic.setComponentLayouterEnabled(false);
584     } else if (VALUE_OUTPUT_AR.equals(restrictOutput)) {
585       final double ratio;
586       if (options.getBool(ITEM_AR_CAGE_USE_VIEW) && getGraph2DView() != null) {
587         Rectangle visibleRect = getGraph2DView().getVisibleRect();
588         ratio = visibleRect.getWidth()/visibleRect.getHeight();
589       } else {
590         ratio = options.getDouble(ITEM_CAGE_RATIO);
591       }
592       
593       organic.setOutputRestriction(OutputRestriction.createAspectRatioRestriction(ratio));
594       organic.setComponentLayouterEnabled(true);
595       ((ComponentLayouter) organic.getComponentLayouter()).setPreferredLayoutSize(ratio * 100, 100);
596     } else if (VALUE_OUTPUT_ELLIPTICAL_CAGE.equals(restrictOutput)) {
597       final double x;
598       final double y;
599       final double w;
600       final double h;
601       if (options.getBool(ITEM_ELL_CAGE_USE_VIEW) && getGraph2DView() != null) {
602         Rectangle visibleRect = getGraph2DView().getVisibleRect();
603         x = visibleRect.x;
604         y = visibleRect.y;
605         w = visibleRect.width;
606         h = visibleRect.height;
607       } else {
608         x = options.getDouble(ITEM_ELLIPTICAL_CAGE_X);
609         y = options.getDouble(ITEM_ELLIPTICAL_CAGE_Y);
610         w = options.getDouble(ITEM_ELLIPTICAL_CAGE_WIDTH);
611         h = options.getDouble(ITEM_ELLIPTICAL_CAGE_HEIGHT);
612       }
613       
614       organic.setOutputRestriction(OutputRestriction.createEllipticalCageRestriction(x, y, w, h));
615       organic.setComponentLayouterEnabled(false);
616     }
617   }
618 
619   private boolean isGroupingNodes(Graph2D graph, String policy) {
620     return !VALUE_IGNORE_GROUPS.equals(policy) && HierarchyManager.containsGroupNodes(graph);
621   }
622 
623   private static final class EdgeDirectednessProvider extends DataProviderAdapter {
624     final Graph2D graph;
625 
626     EdgeDirectednessProvider(final Graph2D graph) {
627       this.graph = graph;
628     }
629 
630     public double getDouble(final Object dataHolder) {
631       try {
632         final EdgeRealizer realizer = graph.getRealizer((Edge) dataHolder);
633         if (realizer.getSourceArrow() == Arrow.NONE && realizer.getTargetArrow() != Arrow.NONE) {
634           //edge has some arrow at the target and none at the source -> consider as directed
635           return 1;
636         } else if (realizer.getTargetArrow() == Arrow.NONE && realizer.getSourceArrow() != Arrow.NONE) {
637           //edge has some arrow at the source and none at the target -> consider as reversed directed
638           return -1;
639         } else {
640           //edge has no arrows at all or arrows on both ends -> consider as undirected
641           return 0;
642         }
643       } catch (Exception e) {
644         //by default all edges are considered to be directed from source to target
645         return 1;
646       }
647     }
648   }
649 }
650