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.view.flowchart;
29  
30  import demo.view.flowchart.layout.FlowchartLayouter;
31  import demo.view.flowchart.painters.FlowchartLayoutConfigurator;
32  import y.base.Edge;
33  import y.base.EdgeCursor;
34  import y.base.EdgeMap;
35  import y.base.Node;
36  import y.base.NodeCursor;
37  import y.layout.LayoutOrientation;
38  import y.layout.PortConstraintKeys;
39  import y.module.LayoutModule;
40  import y.option.OptionGroup;
41  import y.option.OptionHandler;
42  import y.util.Maps;
43  import y.view.Graph2D;
44  import y.view.Graph2DLayoutExecutor;
45  
46  /**
47   * Provides a graphical settings component for {@link FlowchartLayouter}.
48   */
49  public class FlowchartLayoutModule extends LayoutModule {
50    private static final String FLOWCHART_LAYOUT = "FLOWCHART_LAYOUT";
51    private static final String GROUP_NEGATIVE_BRANCH = "GROUP_NEGATIVE_BRANCH";
52    private static final String GROUP_POSITIVE_BRANCH = "GROUP_POSITIVE_BRANCH";
53  
54    private static final String ALLOW_FLATWISE_EDGES = "ALLOW_FLATWISE_EDGES";
55    private static final String LANE_INSETS = "LANE_INSETS";
56    private static final String MINIMUM_EDGE_DISTANCE = "MINIMUM_EDGE_DISTANCE";
57    private static final String MINIMUM_NODE_DISTANCE = "MINIMUM_NODE_DISTANCE";
58    private static final String MINIMUM_POOL_DISTANCE = "MINIMUM_POOL_DISTANCE";
59  
60    private static final String IN_EDGE_GROUPING = "IN_EDGE_GROUPING";
61    private static final String GROUPING_ALL = "GROUPING_ALL";
62    private static final String GROUPING_OPTIMIZED = "GROUPING_OPTIMIZED";
63    private static final String GROUPING_NONE = "GROUPING_NONE";
64  
65    private static final String ORIENTATION = "ORIENTATION";
66    private static final String LEFT_TO_RIGHT = "LEFT_TO_RIGHT";
67    private static final String TOP_TO_BOTTOM = "TOP_TO_BOTTOM";
68  
69    private static final String NEGATIVE_BRANCH_LABEL = "NEGATIVE_BRANCH_LABEL";
70    private static final String NEGATIVE_BRANCH_DEFAULT = "No";
71    private static final String NEGATIVE_BRANCH_DIRECTION = "NEGATIVE_BRANCH_DIRECTION";
72  
73    private static final String POSITIVE_BRANCH_LABEL = "POSITIVE_BRANCH_LABEL";
74    private static final String POSITIVE_BRANCH_DEFAULT = "Yes";
75    private static final String POSITIVE_BRANCH_DIRECTION = "POSITIVE_BRANCH_DIRECTION";
76  
77    private static final String DIRECTION_WITH_THE_FLOW = "DIRECTION_WITH_THE_FLOW";
78    private static final String DIRECTION_FLATWISE = "DIRECTION_FLATWISE";
79    private static final String DIRECTION_LEFT_IN_FLOW = "DIRECTION_LEFT_IN_FLOW";
80    private static final String DIRECTION_RIGHT_IN_FLOW = "DIRECTION_RIGHT_IN_FLOW";
81    private static final String DIRECTION_UNDEFINED = "DIRECTION_UNDEFINED";
82  
83    private static final String[] DIRECTION_ENUM = {
84        DIRECTION_WITH_THE_FLOW, DIRECTION_FLATWISE,
85        DIRECTION_LEFT_IN_FLOW, DIRECTION_RIGHT_IN_FLOW,
86        DIRECTION_UNDEFINED
87    };
88  
89    private static final String[] GROUPING_ENUM = {
90        GROUPING_ALL, GROUPING_OPTIMIZED, GROUPING_NONE
91    };
92  
93    private static final String[] ORIENTATION_ENUM = {
94        TOP_TO_BOTTOM, LEFT_TO_RIGHT
95    };
96  
97    /**
98     * Creates a new FlowchartLayoutModule.
99     */
100   public FlowchartLayoutModule() {
101     super(FLOWCHART_LAYOUT);
102 
103     // PortIntersectionCalculator is enabled since there are many non-rectangular symbols in the flowchart palette
104     setPortIntersectionCalculatorEnabled(true);
105   }
106 
107 
108   /**
109    * Creates the option handler for this module.
110    */
111   protected OptionHandler createOptionHandler() {
112     final OptionHandler op = new OptionHandler(FLOWCHART_LAYOUT);
113 
114     op.addEnum(ORIENTATION, ORIENTATION_ENUM, 0);
115 
116     op.addString(POSITIVE_BRANCH_LABEL, POSITIVE_BRANCH_DEFAULT);
117     op.addEnum(POSITIVE_BRANCH_DIRECTION, DIRECTION_ENUM, 0);
118     op.addString(NEGATIVE_BRANCH_LABEL, NEGATIVE_BRANCH_DEFAULT);
119     op.addEnum(NEGATIVE_BRANCH_DIRECTION, DIRECTION_ENUM, 1);
120 
121     op.addEnum(IN_EDGE_GROUPING, GROUPING_ENUM, 1);
122     op.addBool(ALLOW_FLATWISE_EDGES, true);
123 
124     op.addDouble(MINIMUM_NODE_DISTANCE, 30.0);
125     op.addDouble(MINIMUM_EDGE_DISTANCE, 15.0);
126     op.addDouble(MINIMUM_POOL_DISTANCE, 30.0);
127     op.addDouble(LANE_INSETS, 10.0);
128 
129 
130     OptionGroup og = new OptionGroup();
131     og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_POSITIVE_BRANCH);
132     og.addItem(op.getItem(POSITIVE_BRANCH_LABEL));
133     og.addItem(op.getItem(POSITIVE_BRANCH_DIRECTION));
134 
135     og = new OptionGroup();
136     og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_NEGATIVE_BRANCH);
137     og.addItem(op.getItem(NEGATIVE_BRANCH_LABEL));
138     og.addItem(op.getItem(NEGATIVE_BRANCH_DIRECTION));
139 
140     // Ensure that the initial settings of this option handler and class FlowchartLayoutConfigurator are the same
141     adoptSettings(op, new FlowchartLayoutConfigurator());
142     // Ensure that the initial settings of this option handler and class FlowchartLayouter are the same
143     adoptSettings(op, new FlowchartLayouter());
144 
145     return op;
146   }
147 
148   /**
149    * Configures and runs the flowchart layout algorithm.
150    */
151   protected void mainrun() {
152     final OptionHandler op = getOptionHandler();
153 
154     final FlowchartLayouter layouter = new FlowchartLayouter();
155     final FlowchartLayoutConfigurator layoutConfigurator = new FlowchartLayoutConfigurator();
156 
157     configureLayouter(op, layouter);
158     configureLayoutConfigurator(op, layoutConfigurator);
159     configureLayoutExecutor();
160 
161     final Graph2D graph = getGraph2D();
162 
163     try {
164       configureInEdgeGrouping();
165       layoutConfigurator.prepareAll(graph);
166 
167       launchLayouter(layouter, true);
168 
169     } finally {
170       layoutConfigurator.restoreAll(graph);
171       graph.removeDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY);
172     }
173   }
174 
175   /**
176    * Configures the given layouter according to the settings of the given option handler.
177    */
178   static void configureLayouter(OptionHandler op, FlowchartLayouter layouter) {
179     layouter.setAllowFlatwiseEdges(op.getBool(ALLOW_FLATWISE_EDGES));
180     layouter.setLayoutOrientation(convertEnumToLayoutOrientation(op.getEnum(ORIENTATION)));
181 
182     layouter.setLaneInsets(op.getDouble(LANE_INSETS));
183     layouter.setMinimumEdgeLength(op.getDouble(MINIMUM_EDGE_DISTANCE));
184     layouter.setMinimumEdgeDistance(op.getDouble(MINIMUM_EDGE_DISTANCE));
185     layouter.setMinimumNodeDistance(op.getDouble(MINIMUM_NODE_DISTANCE));
186     layouter.setMinimumPoolDistance(op.getDouble(MINIMUM_POOL_DISTANCE));
187   }
188 
189   /**
190    * Adopts the settings of the given layouter to the given option handler.
191    */
192   static void adoptSettings(OptionHandler oh, FlowchartLayouter layouter) {
193     oh.set(ORIENTATION, isHorizontalOrientation(layouter) ? ORIENTATION_ENUM[1] : ORIENTATION_ENUM[0]);
194     oh.set(ALLOW_FLATWISE_EDGES, Boolean.valueOf(layouter.isAllowFlatwiseEdges()));
195 
196     oh.set(LANE_INSETS, new Double(layouter.getLaneInsets()));
197     oh.set(MINIMUM_EDGE_DISTANCE, new Double(layouter.getMinimumEdgeDistance()));
198     oh.set(MINIMUM_NODE_DISTANCE, new Double(layouter.getMinimumNodeDistance()));
199     oh.set(MINIMUM_POOL_DISTANCE, new Double(layouter.getMinimumPoolDistance()));
200   }
201 
202   /**
203    * Configures the given {@link FlowchartLayoutConfigurator} according to the settings of the given option handler.
204    */
205   static void configureLayoutConfigurator(OptionHandler op, FlowchartLayoutConfigurator configurator) {
206     configurator.setPositiveBranchLabel(op.getString(POSITIVE_BRANCH_LABEL));
207     configurator.setNegativeBranchLabel(op.getString(NEGATIVE_BRANCH_LABEL));
208     configurator.setPreferredNegativeBranchDirection(
209         convertEnumToBranchDirection(op.getEnum(NEGATIVE_BRANCH_DIRECTION)));
210     configurator.setPreferredPositiveBranchDirection(
211         convertEnumToBranchDirection(op.getEnum(POSITIVE_BRANCH_DIRECTION)));
212   }
213 
214   /**
215    * Adopts the settings of the given {@link FlowchartLayoutConfigurator} to the given option handler.
216    */
217   static void adoptSettings(OptionHandler oh, FlowchartLayoutConfigurator configurator) {
218     oh.set(POSITIVE_BRANCH_LABEL, configurator.getPositiveBranchLabel());
219     oh.set(NEGATIVE_BRANCH_LABEL, configurator.getNegativeBranchLabel());
220     oh.set(NEGATIVE_BRANCH_DIRECTION,
221         DIRECTION_ENUM[convertBranchDirectionToEnum(configurator.getPreferredNegativeBranchDirection())]);
222     oh.set(POSITIVE_BRANCH_DIRECTION,
223         DIRECTION_ENUM[convertBranchDirectionToEnum(configurator.getPreferredPositiveBranchDirection())]);
224   }
225 
226   /**
227    * Configures the layout executor of this module.
228    */
229   private void configureLayoutExecutor() {
230     final Graph2DLayoutExecutor layoutExecutor = getLayoutExecutor();
231     layoutExecutor.getLayoutMorpher().setPreferredDuration(500L);
232   }
233 
234   private void configureInEdgeGrouping() {
235     final OptionHandler oh = getOptionHandler();
236 
237     if (oh.get(IN_EDGE_GROUPING).equals(GROUPING_NONE)) {
238       return;
239     }
240 
241     final int inDegreeThreshold;
242     final int degreeThreshold;
243     if (oh.get(IN_EDGE_GROUPING).equals(GROUPING_ALL)) {
244       inDegreeThreshold = 0;
245       degreeThreshold = 0;
246     } else {
247       inDegreeThreshold = 3;
248       degreeThreshold = 4;
249     }
250 
251     final Graph2D graph = getGraph2D();
252     final EdgeMap map = Maps.createHashedEdgeMap();
253     graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, map);
254 
255     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
256       final Node node = nc.node();
257       if (node.inDegree() < 2 || node.inDegree() < inDegreeThreshold || node.degree() < degreeThreshold) {
258         continue;
259       }
260 
261       for (EdgeCursor edgeCursor = node.inEdges(); edgeCursor.ok(); edgeCursor.next()) {
262         final Edge edge = edgeCursor.edge();
263         map.set(edge, node);
264       }
265     }
266   }
267 
268   /**
269    * Returns the branch direction that corresponds to the given index of the branch direction settings array.
270    */
271   private static int convertEnumToBranchDirection(int enumIndex) {
272     switch (enumIndex) {
273       case 0:
274         return FlowchartLayouter.DIRECTION_WITH_THE_FLOW;
275       case 1:
276         return FlowchartLayouter.DIRECTION_FLATWISE;
277       case 2:
278         return FlowchartLayouter.DIRECTION_LEFT_IN_FLOW;
279       case 3:
280         return FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW;
281       case 4:
282       default:
283         return FlowchartLayouter.DIRECTION_UNDEFINED;
284     }
285   }
286 
287   /**
288    * Returns the index of the branch direction settings array that corresponds to the given branch direction.
289    */
290   private static int convertBranchDirectionToEnum(int direction) {
291     switch (direction) {
292       case FlowchartLayouter.DIRECTION_WITH_THE_FLOW:
293         return 0;
294       case FlowchartLayouter.DIRECTION_FLATWISE:
295         return 1;
296       case FlowchartLayouter.DIRECTION_LEFT_IN_FLOW:
297         return 2;
298       case FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW:
299         return 3;
300       case FlowchartLayouter.DIRECTION_UNDEFINED:
301       default:
302         return 4;
303     }
304   }
305 
306   /**
307    * Returns the layout orientation that corresponds to the given index of the orientation settings array.
308    */
309   private static byte convertEnumToLayoutOrientation(int enumIndex) {
310     return enumIndex == 0 ? LayoutOrientation.TOP_TO_BOTTOM : LayoutOrientation.LEFT_TO_RIGHT;
311   }
312 
313   private static boolean isHorizontalOrientation(FlowchartLayouter layouter) {
314     final int layoutOrientation = (int) layouter.getLayoutOrientation();
315     return layoutOrientation == (int) LayoutOrientation.LEFT_TO_RIGHT
316         || layoutOrientation == (int) LayoutOrientation.RIGHT_TO_LEFT;
317   }
318 
319 }
320