1
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
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
100 public FlowchartLayoutModule() {
101 super(FLOWCHART_LAYOUT);
102
103 setPortIntersectionCalculatorEnabled(true);
105 }
106
107
108
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 adoptSettings(op, new FlowchartLayoutConfigurator());
142 adoptSettings(op, new FlowchartLayouter());
144
145 return op;
146 }
147
148
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
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
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
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
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
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
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
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
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