1
28 package demo.view.flowchart.layout;
29
30 import y.base.DataProvider;
31 import y.base.Edge;
32 import y.base.EdgeCursor;
33 import y.base.EdgeMap;
34 import y.base.Graph;
35 import y.base.NodeMap;
36 import y.base.YCursor;
37 import y.geom.YDimension;
38 import y.layout.DiscreteEdgeLabelModel;
39 import y.layout.DiscreteNodeLabelModel;
40 import y.layout.EdgeLabelLayout;
41 import y.layout.LayoutGraph;
42 import y.layout.LayoutOrientation;
43 import y.layout.Layouter;
44 import y.layout.NodeLabelLayout;
45 import y.layout.grid.ColumnDescriptor;
46 import y.layout.grid.PartitionGrid;
47 import y.layout.hierarchic.IncrementalHierarchicLayouter;
48 import y.layout.hierarchic.incremental.EdgeLayoutDescriptor;
49 import y.layout.hierarchic.incremental.HierarchicLayouter;
50 import y.layout.grid.RowDescriptor;
51 import y.layout.hierarchic.incremental.RoutingStyle;
52 import y.layout.hierarchic.incremental.SimplexNodePlacer;
53 import y.layout.labeling.AbstractLabelingAlgorithm;
54 import y.layout.labeling.GreedyMISLabeling;
55 import y.util.DataProviderAdapter;
56 import y.util.Maps;
57
58
62 public class FlowchartLayouter implements Layouter {
63
64
68 public static final Object NODE_TYPE_DPKEY =
69 "demo.view.flowchart.layout.FlowchartLayouter.NODE_TYPE_DPKEY";
70
71
75 public static final Object EDGE_TYPE_DPKEY =
76 "demo.view.flowchart.layout.FlowchartLayouter.EDGE_TYPE_DPKEY";
77
78
82 public static final Object PREFERRED_DIRECTION_KEY =
83 "demo.view.flowchart.layout.FlowchartLayouter.DIRECTION_KEY";
84
85
93 public static final Object LABEL_LAYOUT_DPKEY =
94 "demo.view.flowchart.layout.FlowchartLayouter.LABEL_LAYOUT_DPKEY";
95
96
97 public static final int DIRECTION_UNDEFINED = 0x0;
99 public static final int DIRECTION_WITH_THE_FLOW = 0x1;
100 public static final int DIRECTION_AGAINST_THE_FLOW = 0x2;
101 public static final int DIRECTION_LEFT_IN_FLOW = 0x4;
102 public static final int DIRECTION_RIGHT_IN_FLOW = 0x8;
103 public static final int DIRECTION_STRAIGHT = DIRECTION_WITH_THE_FLOW | DIRECTION_AGAINST_THE_FLOW;
104 public static final int DIRECTION_FLATWISE = DIRECTION_LEFT_IN_FLOW | DIRECTION_RIGHT_IN_FLOW;
105
106 private boolean allowFlatwiseEdges;
107 private byte layoutOrientation;
108 private double laneInsets;
109 private double minimumEdgeLength;
110 private double minimumEdgeDistance;
111 private double minimumNodeDistance;
112 private double minimumPoolDistance;
113 private final double minimumLabelDistance;
114
115 private FlowchartTransformerStage transformerStage;
116
117 public FlowchartLayouter() {
118 transformerStage = new FlowchartTransformerStage();
119 allowFlatwiseEdges = true;
120 layoutOrientation = LayoutOrientation.TOP_TO_BOTTOM;
121 laneInsets = 10.0;
122 minimumEdgeDistance = 15.0;
123 minimumEdgeLength = 30.0;
124 minimumLabelDistance = 20.0;
125 minimumNodeDistance = 30.0;
126 minimumPoolDistance = 30.0;
127 }
128
129
134 public boolean isAllowFlatwiseEdges() {
135 return allowFlatwiseEdges;
136 }
137
138
143 public void setAllowFlatwiseEdges(boolean allow) {
144 this.allowFlatwiseEdges = allow;
145 }
146
147
154 public double getLaneInsets() {
155 return laneInsets;
156 }
157
158
167 public void setLaneInsets(double laneInsets) {
168 this.laneInsets = laneInsets;
169 }
170
171
178 public double getMinimumNodeDistance() {
179 return minimumNodeDistance;
180 }
181
182
189 public void setMinimumNodeDistance(double minimumNodeDistance) {
190 this.minimumNodeDistance = minimumNodeDistance;
191 }
192
193
200 public double getMinimumEdgeDistance() {
201 return minimumEdgeDistance;
202 }
203
204
211 public void setMinimumEdgeDistance(double minimumEdgeDistance) {
212 this.minimumEdgeDistance = minimumEdgeDistance;
213 }
214
215
222 public double getMinimumEdgeLength() {
223 return minimumEdgeLength;
224 }
225
226
233 public void setMinimumEdgeLength(double minimumEdgeLength) {
234 this.minimumEdgeLength = minimumEdgeLength;
235 }
236
237
244 public double getMinimumPoolDistance() {
245 return minimumPoolDistance;
246 }
247
248
255 public void setMinimumPoolDistance(double distance) {
256 this.minimumPoolDistance = distance;
257 }
258
259
266 public byte getLayoutOrientation() {
267 return layoutOrientation;
268 }
269
270
280 public void setLayoutOrientation(byte layoutOrientation) {
281 switch (layoutOrientation) {
282 case LayoutOrientation.TOP_TO_BOTTOM:
283 case LayoutOrientation.LEFT_TO_RIGHT:
284 this.layoutOrientation = layoutOrientation;
285 break;
286 default:
287 throw new IllegalArgumentException("Invalid layout orientation: " + layoutOrientation);
288 }
289 }
290
291
295 public boolean canLayout(LayoutGraph graph) {
296 return true;
297 }
298
299
302 public void doLayout(LayoutGraph graph) {
303 if (graph.isEmpty()) {
304 return;
305 }
306
307 PartitionGrid grid = PartitionGrid.getPartitionGrid(graph);
308 if (grid != null) {
309 for (YCursor cur = grid.getColumns().cursor(); cur.ok(); cur.next()) {
311 ColumnDescriptor column = (ColumnDescriptor) cur.current();
312 column.setLeftInset(laneInsets);
313 column.setRightInset(laneInsets);
314 }
315 for (YCursor cur = grid.getRows().cursor(); cur.ok(); cur.next()) {
316 RowDescriptor row = (RowDescriptor) cur.current();
317 row.setTopInset(laneInsets);
318 row.setBottomInset(laneInsets);
319 }
320 }
321
322 try {
323 final IncrementalHierarchicLayouter ihl = configureHierarchicLayouter();
324
325 transformerStage = new FlowchartTransformerStage();
326 transformerStage.setCoreLayouter(ihl);
327
328 final NodeMap layerIds = Maps.createHashedNodeMap();
329 try {
330 graph.addDataProvider(HierarchicLayouter.LAYER_VALUE_HOLDER_DPKEY, layerIds);
331
332 transformerStage.doLayout(graph);
333 } finally {
334 graph.removeDataProvider(HierarchicLayouter.LAYER_VALUE_HOLDER_DPKEY);
335 }
336
337 final EdgeMap edge2LayoutDescriptor = Maps.createHashedEdgeMap();
338 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
339 edge2LayoutDescriptor.set(ec.edge(), createEdgeLayoutDescriptor(ec.edge(), graph,
340 ihl.getEdgeLayoutDescriptor(), isHorizontalOrientation()));
341 }
342
343 try {
345 graph.addDataProvider(FlowchartTransformerStage.LAYER_ID_DP_KEY, layerIds);
346 graph.addDataProvider(HierarchicLayouter.EDGE_LAYOUT_DESCRIPTOR_DPKEY, edge2LayoutDescriptor);
347
348 transformerStage.doLayout(graph);
349 } finally {
350 graph.removeDataProvider(FlowchartTransformerStage.LAYER_ID_DP_KEY);
351 graph.removeDataProvider(HierarchicLayouter.EDGE_LAYOUT_DESCRIPTOR_DPKEY);
352 }
353
354 } finally {
355 graph.removeDataProvider(FlowchartPortOptimizer.NODE_TO_ALIGN_DP_KEY);
357 }
358
359 doLabelPlacement(graph);
360 }
361
362
365 private IncrementalHierarchicLayouter configureHierarchicLayouter() {
366 IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
367 ihl.setOrthogonallyRouted(true);
368 ihl.setRecursiveGroupLayeringEnabled(false);
369 ihl.setComponentLayouterEnabled(false);
370 ihl.setMinimumLayerDistance(minimumNodeDistance);
371 ihl.setNodeToNodeDistance(minimumNodeDistance);
372 ihl.setEdgeToEdgeDistance(minimumEdgeDistance);
373 ihl.setBackloopRoutingEnabled(false);
374 ihl.setLayoutOrientation(isHorizontalOrientation() ?
375 LayoutOrientation.LEFT_TO_RIGHT : LayoutOrientation.TOP_TO_BOTTOM);
376 ihl.setIntegratedEdgeLabelingEnabled(false);
377 ihl.setConsiderNodeLabelsEnabled(true);
378
379 final EdgeLayoutDescriptor descriptor = new EdgeLayoutDescriptor();
380 descriptor.setMinimumDistance(minimumEdgeDistance);
381 descriptor.setMinimumLength(15.0);
382 descriptor.setMinimumFirstSegmentLength(15.0);
383 descriptor.setMinimumLastSegmentLength(15.0);
384 descriptor.setRoutingStyle(new RoutingStyle(RoutingStyle.EDGE_STYLE_ORTHOGONAL));
385 ihl.setEdgeLayoutDescriptor(descriptor);
386
387 ihl.getHierarchicLayouter().setPortConstraintOptimizer(new FlowchartPortOptimizer(getLayoutOrientation()));
388
389 final FlowchartLayerer layerer = new FlowchartLayerer();
390 layerer.setAllowFlatwiseDefaultFlow(isAllowFlatwiseEdges());
391 ihl.setFromScratchLayerer(layerer);
392
393 SimplexNodePlacer nodePlacer = (SimplexNodePlacer) ihl.getNodePlacer();
394 nodePlacer.setBaryCenterModeEnabled(true);
395 nodePlacer.setEdgeStraighteningOptimizationEnabled(true);
396
397 return ihl;
398 }
399
400
404 private EdgeLayoutDescriptor createEdgeLayoutDescriptor(Edge e, LayoutGraph g, EdgeLayoutDescriptor defaultDescriptor,
405 boolean horizontal) {
406 final EdgeLabelLayout[] ell = g.getEdgeLabelLayout(e);
407
408 double minLength = 0.0;
409 for (int i = 0; i < ell.length; i++) {
410 final YDimension labelSize = ell[i].getBox();
411 if (FlowchartElements.isRegularEdge(g, e)) {
412 minLength += horizontal ? labelSize.getWidth() : labelSize.getHeight();
413 } else {
414 minLength += horizontal ? labelSize.getHeight() : labelSize.getWidth();
415 }
416 }
417
418 if (ell.length > 0) {
420 minLength += minimumNodeDistance + (double) (ell.length - 1) * minimumLabelDistance;
421 }
422
423 EdgeLayoutDescriptor descriptor = new EdgeLayoutDescriptor();
424 descriptor.setMinimumDistance(defaultDescriptor.getMinimumDistance());
425 descriptor.setMinimumLength(Math.max(minLength, defaultDescriptor.getMinimumLength()));
426 descriptor.setMinimumFirstSegmentLength(defaultDescriptor.getMinimumFirstSegmentLength());
427 descriptor.setMinimumLastSegmentLength(defaultDescriptor.getMinimumLastSegmentLength());
428 descriptor.setRoutingStyle(defaultDescriptor.getRoutingStyle());
429
430 return descriptor;
431 }
432
433
436 private static void doLabelPlacement(final LayoutGraph graph) {
437 final GreedyMISLabeling labeling = new GreedyMISLabeling();
438 labeling.setSelection(LABEL_LAYOUT_DPKEY);
439 labeling.setPlaceNodeLabels(true);
440 labeling.setPlaceEdgeLabels(true);
441 labeling.setProfitModel(new FlowchartLabelProfitModel(graph));
442 labeling.setCustomProfitModelRatio(0.25);
443
444 try {
445 graph.addDataProvider(AbstractLabelingAlgorithm.LABEL_MODEL_DPKEY, new DataProviderAdapter() {
446 public Object get(final Object dataHolder) {
447 if (dataHolder instanceof NodeLabelLayout) {
448 return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.CENTER);
449 } else if (dataHolder instanceof EdgeLabelLayout) {
450 return new DiscreteEdgeLabelModel(DiscreteEdgeLabelModel.SIX_POS);
451 } else {
452 return null;
453 }
454 }
455 });
456
457 labeling.doLayout(graph);
458 } finally {
459 graph.removeDataProvider(AbstractLabelingAlgorithm.LABEL_MODEL_DPKEY);
460 }
461 }
462
463 private boolean isHorizontalOrientation() {
464 return (int) layoutOrientation == (int) LayoutOrientation.LEFT_TO_RIGHT;
465 }
466
467 static boolean isFlatwiseBranch(DataProvider branchTypes, Object dataHolder) {
468 return branchTypes != null && isFlatwiseBranchType(branchTypes.getInt(dataHolder));
469 }
470
471 static boolean isStraightBranch(DataProvider branchTypes, Object dataHolder) {
472 return branchTypes != null && isStraightBranchType(branchTypes.getInt(dataHolder));
473 }
474
475 static boolean isStraightBranch(Graph graph, Object dataHolder) {
476 return isStraightBranch(graph.getDataProvider(FlowchartLayouter.PREFERRED_DIRECTION_KEY), dataHolder);
477 }
478
479 static boolean isFlatwiseBranchType(int type) {
480 return (type & DIRECTION_FLATWISE) != 0;
481 }
482
483 static boolean isStraightBranchType(int type) {
484 return (type & DIRECTION_STRAIGHT) != 0;
485 }
486
487 static void restoreDataProvider(LayoutGraph graph, DataProvider dataProvider, Object key) {
488 graph.removeDataProvider(key);
489 if (dataProvider != null) {
490 graph.addDataProvider(key, dataProvider);
491 }
492 }
493
494 }
495