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.layout.ComponentLayouter;
34  import y.layout.circular.CircularLayouter;
35  import y.layout.EdgeBundling;
36  import y.layout.EdgeBundleDescriptor;
37  import y.layout.circular.SingleCycleLayouter;
38  import y.layout.tree.BalloonLayouter;
39  import y.option.OptionHandler;
40  import y.option.ConstraintManager;
41  import y.option.EnumOptionItem;
42  import y.option.OptionItem;
43  import y.view.Graph2D;
44  
45  /**
46   * This module represents an interactive configurator and launcher for
47   * {@link y.layout.circular.CircularLayouter}.
48   * 
49   *
50   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/circular_layouter#circular_layouter" target="_blank">Section Circular Layout</a> in the yFiles for Java Developer's Guide
51   */
52  public class CircularLayoutModule extends LayoutModule {
53    //// Module 'Circular Layout'
54    protected static final String MODULE_CIRCULAR = "CIRCULAR";
55    
56    //// Section 'General'
57    protected static final String SECTION_GENERAL = "GENERAL";
58    // Section 'General' items
59    protected static final String ITEM_LAYOUT_STYLE = "LAYOUT_STYLE";
60    protected static final String VALUE_BCC_COMPACT = "BCC_COMPACT";
61    protected static final String VALUE_BCC_ISOLATED = "BCC_ISOLATED";
62    protected static final String VALUE_CIRCULAR_CUSTOM_GROUPS = "CIRCULAR_CUSTOM_GROUPS";
63    protected static final String VALUE_SINGLE_CYCLE = "SINGLE_CYCLE";
64    protected static final String ITEM_ACT_ON_SELECTION_ONLY = "ACT_ON_SELECTION_ONLY";
65    protected static final String ITEM_FROM_SKETCH = "FROM_SKETCH";
66    protected static final String ITEM_HANDLE_NODE_LABELS = "HANDLE_NODE_LABELS";
67    
68    //// Section 'Partition'
69    protected static final String SECTION_CYCLE = "CYCLE";
70    // Section 'Partition' items
71    private static final String ITEM_PARTITION_LAYOUT_STYLE = "PARTITION_LAYOUT_STYLE";
72    private static final String VALUE_PARTITION_LAYOUTSTYLE_CYCLIC = "PARTITION_LAYOUTSTYLE_CYCLIC";
73    private static final String VALUE_PARTITION_LAYOUTSTYLE_DISK = "PARTITION_LAYOUTSTYLE_DISK";
74    private static final String VALUE_PARTITION_LAYOUTSTYLE_ORGANIC = "PARTITION_LAYOUTSTYLE_ORGANIC";
75    protected static final String ITEM_MINIMAL_NODE_DISTANCE = "MINIMAL_NODE_DISTANCE";
76    protected static final String ITEM_CHOOSE_RADIUS_AUTOMATICALLY = "CHOOSE_RADIUS_AUTOMATICALLY";
77    protected static final String ITEM_FIXED_RADIUS = "FIXED_RADIUS";
78    
79    //// Section 'Edge Bundling'
80    protected static final String SECTION_BUNDLING = "EDGE_BUNDLING";
81    protected static final String EDGE_BUNDLING_ENABLED = "EDGE_BUNDLING_ENABLED";
82    protected static final String EDGE_BUNDLING_STRENGTH = "EDGE_BUNDLING_STRENGTH";
83    
84    //// Section 'Tree'
85    protected static final String SECTION_TREE = "TREE";
86    // Section 'Tree' items
87    protected static final String ITEM_PREFERRED_CHILD_WEDGE = "PREFERRED_CHILD_WEDGE";
88    protected static final String ITEM_MINIMAL_EDGE_LENGTH = "MINIMAL_EDGE_LENGTH";
89    protected static final String ITEM_MAXIMAL_DEVIATION_ANGLE = "MAXIMAL_DEVIATION_ANGLE";
90    protected static final String ITEM_COMPACTNESS_FACTOR = "COMPACTNESS_FACTOR";
91    protected static final String ITEM_MINIMAL_TREE_NODE_DISTANCE = "MINIMAL_TREE_NODE_DISTANCE";
92    protected static final String ITEM_ALLOW_OVERLAPS = "ALLOW_OVERLAPS";
93    protected static final String ITEM_PLACE_CHILDREN_ON_COMMON_RADIUS = "PLACE_CHILDREN_ON_COMMON_RADIUS";
94  
95    /**
96     * Creates an instance of this module.
97     */
98    public CircularLayoutModule() {
99      super(MODULE_CIRCULAR);
100   }
101 
102   /**
103    * Creates an OptionHandler and adds the option items used by this module.
104    * @return the created <code>OptionHandler</code> providing module related options
105    */
106   protected OptionHandler createOptionHandler() {
107     final OptionHandler options = new OptionHandler(getModuleName());
108     final ConstraintManager optionConstraints = new ConstraintManager(options);
109     // Defaults provider
110     final CircularLayouter defaults = new CircularLayouter();
111     final SingleCycleLayouter defaultsSC = defaults.getSingleCycleLayouter();
112     final BalloonLayouter defaultsB = defaults.getBalloonLayouter();
113 
114     //// Section 'General'
115     options.useSection(SECTION_GENERAL);
116     // Populate section
117     final EnumOptionItem itemGlobalLayoutStyle = options.addEnum(ITEM_LAYOUT_STYLE, new String[]{
118         VALUE_BCC_COMPACT,
119         VALUE_BCC_ISOLATED,
120         VALUE_CIRCULAR_CUSTOM_GROUPS,
121         VALUE_SINGLE_CYCLE
122     }, defaults.getLayoutStyle());
123     options.addBool(ITEM_ACT_ON_SELECTION_ONLY, false);
124     options.addBool(ITEM_FROM_SKETCH, false);
125     options.addBool(ITEM_HANDLE_NODE_LABELS, false);
126 
127     //// Section 'Partition'
128     options.useSection(SECTION_CYCLE);
129     // Populate section
130     final EnumOptionItem itemLayoutStyle = options.addEnum(ITEM_PARTITION_LAYOUT_STYLE, new String[]{
131         VALUE_PARTITION_LAYOUTSTYLE_CYCLIC,
132         VALUE_PARTITION_LAYOUTSTYLE_DISK,
133         VALUE_PARTITION_LAYOUTSTYLE_ORGANIC
134     }, defaults.getPartitionLayoutStyle());
135     final OptionItem itemChooseRadiusAutomatically =
136         options.addBool(ITEM_CHOOSE_RADIUS_AUTOMATICALLY, defaultsSC.getAutomaticRadius());
137     final OptionItem itemFixedRadius = options.addInt(ITEM_FIXED_RADIUS, (int) defaultsSC.getFixedRadius(), 50, 800);
138     final OptionItem itemMinimalNodeDistance =
139         options.addInt(ITEM_MINIMAL_NODE_DISTANCE, (int) defaultsSC.getMinimalNodeDistance(), 0, 999);
140     // Enable/disable items depending on specific values
141     optionConstraints.setEnabledOnValueEquals(itemChooseRadiusAutomatically, Boolean.FALSE, itemFixedRadius);
142     optionConstraints.setEnabledOnValueEquals(itemChooseRadiusAutomatically, Boolean.TRUE, itemMinimalNodeDistance);
143 
144     //// Section Bundling
145     options.useSection(SECTION_BUNDLING);
146     final OptionItem bundlingEnabled = options.addBool(EDGE_BUNDLING_ENABLED, false);
147     final OptionItem itemBundlingStrength = options.addDouble(EDGE_BUNDLING_STRENGTH, 0.95, 0.0, 1.0);
148    
149     // Bundling can only be applied if Partition Style is Cyclic and Layout Style is NOT BCC_ISOLATED
150     final ConstraintManager.Condition bundlingCondition =
151         optionConstraints.createConditionValueEquals(itemLayoutStyle, VALUE_PARTITION_LAYOUTSTYLE_CYCLIC).and((
152             optionConstraints.createConditionValueEquals(itemGlobalLayoutStyle, VALUE_BCC_ISOLATED)).inverse());
153     optionConstraints.setEnabledOnCondition(bundlingCondition, bundlingEnabled);
154     final ConstraintManager.Condition bundlingSettingsCondition =
155         optionConstraints.createConditionValueEquals(bundlingEnabled, Boolean.TRUE).and(bundlingCondition);
156     optionConstraints.setEnabledOnCondition(bundlingSettingsCondition, itemBundlingStrength);
157     
158     //// Section 'Tree'
159     options.useSection(SECTION_TREE);
160     // Populate section
161     final OptionItem itemPreferredChildWedge =
162         options.addInt(ITEM_PREFERRED_CHILD_WEDGE, defaultsB.getPreferredChildWedge(), 1, 359);
163     final OptionItem itemMinimalEdgeLength =
164         options.addInt(ITEM_MINIMAL_EDGE_LENGTH, defaultsB.getMinimalEdgeLength(), 5, 400);
165     final OptionItem itemMaximalDeviationAngle =
166         options.addInt(ITEM_MAXIMAL_DEVIATION_ANGLE, defaults.getMaximalDeviationAngle(), 10, 360);
167     final OptionItem itemCompactnessFactor =
168         options.addDouble(ITEM_COMPACTNESS_FACTOR, defaultsB.getCompactnessFactor(), 0.1, 0.9);
169     final OptionItem itemMinimalTreeNodeDistance =
170         options.addInt(ITEM_MINIMAL_TREE_NODE_DISTANCE, defaultsB.getMinimalNodeDistance(), 0, 100);
171     final OptionItem itemAllowOverlaps = options.addBool(ITEM_ALLOW_OVERLAPS, defaultsB.getAllowOverlaps());
172     final OptionItem itemPlaceChildrenCommonRadius = options.addBool(ITEM_PLACE_CHILDREN_ON_COMMON_RADIUS, true);
173     // Enable/disable items depending on specific values
174     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemMinimalTreeNodeDistance, true);
175     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemPlaceChildrenCommonRadius, true);
176     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemPreferredChildWedge, true);
177     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemMinimalEdgeLength, true);
178     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemMaximalDeviationAngle, true);
179     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemCompactnessFactor, true);
180     optionConstraints.setEnabledOnValueEquals(itemLayoutStyle, VALUE_SINGLE_CYCLE, itemAllowOverlaps, true);
181     
182     return options;
183   }
184 
185 
186   /**
187    * Main module execution routine.
188    * Launches the module's underlying algorithm on the module's graph based on user options.
189    */
190   protected void mainrun() {
191     final CircularLayouter circular = new CircularLayouter();
192 
193     final OptionHandler options = getOptionHandler();
194     configure(circular, options);
195 
196     final Graph2D graph = getGraph2D();
197     prepareGraph(graph, options);
198     try {
199       launchLayouter(circular);
200     } finally {
201       restoreGraph(graph, options);
202     }
203   }
204 
205   /**
206    * Prepares a <code>graph</code> depending on the given options for the
207    * module's layout algorithm.
208    * <br>
209    * Additional resources created by this method have to be freed up by calling
210    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
211    * layout calculation.  
212    * @param graph the graph to be prepared
213    * @param options the options for the module's layout algorithm
214    */
215   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
216     if (options.getString(ITEM_LAYOUT_STYLE).equals(VALUE_CIRCULAR_CUSTOM_GROUPS)) {
217       //Set up grouping key for custom layout style
218       //This acts as an adapter for grouping structure to circular grouping keys
219       if (graph.getHierarchyManager() != null) {
220         // backup existing data providers to prevent loss of user settings
221         backupDataProvider(graph, CircularLayouter.CIRCULAR_CUSTOM_GROUPS_DPKEY);
222         graph.addDataProvider(CircularLayouter.CIRCULAR_CUSTOM_GROUPS_DPKEY,
223             graph.getHierarchyManager().getParentNodeIdDataProvider());
224       }
225     }
226   }
227 
228   /**
229    * Restores the given <code>graph</code> by freeing up resources created by
230    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
231    * @param graph the graph for which <code>prepareGraph</code> has been called
232    * @param options the options for the module's layout algorithm
233    */
234   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
235     // remove the data providers set by this module by restoring the initial state
236     if (options.getString(ITEM_LAYOUT_STYLE).equals(VALUE_CIRCULAR_CUSTOM_GROUPS) &&
237         graph.getHierarchyManager() != null) {
238       restoreDataProvider(graph, CircularLayouter.CIRCULAR_CUSTOM_GROUPS_DPKEY);
239     }
240   }
241 
242   /**
243    * Configures the module's layout algorithm according to the given options.
244    * @param circular the <code>CircularLayouter</code> to be configured
245    * @param options the layout options to set
246    */
247   protected void configure(final CircularLayouter circular, final OptionHandler options) {
248     ((ComponentLayouter) circular.getComponentLayouter()).setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
249 
250     if (options.getString(ITEM_LAYOUT_STYLE).equals(VALUE_BCC_COMPACT)) {
251       circular.setLayoutStyle(CircularLayouter.BCC_COMPACT);
252     } else if (options.getString(ITEM_LAYOUT_STYLE).equals(VALUE_BCC_ISOLATED)) {
253       circular.setLayoutStyle(CircularLayouter.BCC_ISOLATED);
254     } else if (options.getString(ITEM_LAYOUT_STYLE).equals(VALUE_CIRCULAR_CUSTOM_GROUPS)) {
255       circular.setLayoutStyle(CircularLayouter.CIRCULAR_CUSTOM_GROUPS);
256     } else {
257       circular.setLayoutStyle(CircularLayouter.SINGLE_CYCLE);
258     }
259 
260     circular.setSubgraphLayouterEnabled(options.getBool(ITEM_ACT_ON_SELECTION_ONLY));
261     circular.setMaximalDeviationAngle(options.getInt(ITEM_MAXIMAL_DEVIATION_ANGLE));
262     circular.setFromSketchModeEnabled(options.getBool(ITEM_FROM_SKETCH));
263     circular.setPlaceChildrenOnCommonRadiusEnabled(options.getBool(ITEM_PLACE_CHILDREN_ON_COMMON_RADIUS));
264     circular.setConsiderNodeLabelsEnabled(options.getBool(ITEM_HANDLE_NODE_LABELS));
265 
266     if (options.getString(ITEM_PARTITION_LAYOUT_STYLE).equals(VALUE_PARTITION_LAYOUTSTYLE_CYCLIC)) {
267       circular.setPartitionLayoutStyle(CircularLayouter.PARTITION_LAYOUTSTYLE_CYCLIC);
268     } else if (options.getString(ITEM_PARTITION_LAYOUT_STYLE).equals(VALUE_PARTITION_LAYOUTSTYLE_DISK)) {
269       circular.setPartitionLayoutStyle(CircularLayouter.PARTITION_LAYOUTSTYLE_DISK);
270     } else if (options.getString(ITEM_PARTITION_LAYOUT_STYLE).equals(VALUE_PARTITION_LAYOUTSTYLE_ORGANIC)) {
271       circular.setPartitionLayoutStyle(CircularLayouter.PARTITION_LAYOUTSTYLE_ORGANIC);
272     }
273 
274     final SingleCycleLayouter cl = circular.getSingleCycleLayouter();
275     cl.setMinimalNodeDistance(options.getInt(ITEM_MINIMAL_NODE_DISTANCE));
276     cl.setAutomaticRadius(options.getBool(ITEM_CHOOSE_RADIUS_AUTOMATICALLY));
277     cl.setFixedRadius(options.getInt(ITEM_FIXED_RADIUS));
278 
279     final BalloonLayouter bl = circular.getBalloonLayouter();
280     bl.setPreferredChildWedge(options.getInt(ITEM_PREFERRED_CHILD_WEDGE));
281     bl.setMinimalEdgeLength(options.getInt(ITEM_MINIMAL_EDGE_LENGTH));
282     bl.setCompactnessFactor(options.getDouble(ITEM_COMPACTNESS_FACTOR));
283     bl.setAllowOverlaps(options.getBool(ITEM_ALLOW_OVERLAPS));
284     bl.setMinimalNodeDistance(options.getInt(ITEM_MINIMAL_TREE_NODE_DISTANCE));
285     
286     // Edge Bundling
287     final EdgeBundling ebc = circular.getEdgeBundling();
288     final EdgeBundleDescriptor descriptor = new EdgeBundleDescriptor();
289     descriptor.setBundled(options.getBool(EDGE_BUNDLING_ENABLED));
290     ebc.setDefaultBundleDescriptor(descriptor);
291     ebc.setBundlingStrength(options.getDouble(EDGE_BUNDLING_STRENGTH));
292   }
293 }
294