1
14 package demo.layout.module;
15
16 import y.module.LayoutModule;
17 import y.module.YModule;
18
19 import java.awt.Color;
20
21 import y.base.Edge;
22 import y.base.EdgeCursor;
23 import y.base.EdgeMap;
24 import y.base.NodeCursor;
25 import y.base.Node;
26 import y.base.DataProvider;
27 import y.layout.LabelLayoutConstants;
28 import y.layout.LabelRanking;
29 import y.layout.OrientationLayouter;
30 import y.layout.LayoutGraph;
31 import y.layout.PortConstraintKeys;
32 import y.layout.labeling.GreedyMISLabeling;
33 import y.layout.orthogonal.DirectedOrthogonalLayouter;
34 import y.layout.orthogonal.EdgeLayoutDescriptor;
35 import y.option.ArrowCellRenderer;
36 import y.option.ConstraintManager;
37 import y.option.EnumOptionItem;
38 import y.option.IntOptionItem;
39 import y.option.OptionGroup;
40 import y.option.OptionHandler;
41 import y.option.OptionItem;
42 import y.option.StrokeCellRenderer;
43 import y.util.DataProviderAdapter;
44 import y.util.pq.BHeapIntNodePQ;
45 import y.view.EdgeLabel;
46 import y.view.EdgeRealizer;
47 import y.view.Graph2D;
48 import y.view.Arrow;
49 import y.view.LineType;
50
51
52
58 public class DirectedOrthogonalLayoutModule extends LayoutModule
59 {
60 private static final String DIRECTED_ORTHOGONAL_LAYOUTER = "DIRECTED_ORTHOGONAL_LAYOUTER";
61
62 private static final String LAYOUT = "LAYOUT";
63
64 private static final String USE_EXISTING_DRAWING_AS_SKETCH = "USE_EXISTING_DRAWING_AS_SKETCH";
65 private static final String GRID = "GRID";
66
67 private static final String RIGHT_TO_LEFT = "RIGHT_TO_LEFT";
68 private static final String BOTTOM_TO_TOP = "BOTTOM_TO_TOP";
69 private static final String LEFT_TO_RIGHT = "LEFT_TO_RIGHT";
70 private static final String TOP_TO_BOTTOM = "TOP_TO_BOTTOM";
71
72 private static final String ORIENTATION = "ORIENTATION";
73
74 private static final String[] orientEnum = {
75 TOP_TO_BOTTOM,
76 LEFT_TO_RIGHT,
77 BOTTOM_TO_TOP,
78 RIGHT_TO_LEFT
79 };
80
81 private static final String AUTO_GROUP_DIRECTED_EDGES = "AUTO_GROUP_DIRECTED_EDGES";
82
83 private static final String EDGE_LABEL_MODEL = "EDGE_LABEL_MODEL";
84 private static final String EDGE_LABELING = "EDGE_LABELING";
85 private static final String LABELING = "LABELING";
86 private static final String GENERIC = "GENERIC";
87 private static final String NONE = "NONE";
88 private static final String INTEGRATED = "INTEGRATED";
89 private static final String FREE = "FREE";
90 private static final String SIDE_SLIDER = "SIDE_SLIDER";
91 private static final String CENTER_SLIDER = "CENTER_SLIDER";
92 private static final String AS_IS = "AS_IS";
93 private static final String BEST = "BEST";
94 private static final String CONSIDER_NODE_LABELS = "CONSIDER_NODE_LABELS";
95
96 private static final String IDENTIFY_DIRECTED_EDGES = "IDENTIFY_DIRECTED_EDGES";
97 private static final String USE_AS_CRITERIA = "USE_AS_CRITERIA";
98 private static final String LINE_COLOR = "LINE_COLOR";
99 private static final String TARGET_ARROW = "TARGET_ARROW";
100 private static final String LINE_TYPE = "LINE_TYPE";
101 private static final String MINIMUM_FIRST_SEGMENT_LENGTH = "MINIMUM_FIRST_SEGMENT_LENGTH";
102 private static final String MINIMUM_LAST_SEGMENT_LENGTH = "MINIMUM_LAST_SEGMENT_LENGTH";
103 private static final String MINIMUM_SEGMENT_LENGTH = "MINIMUM_SEGMENT_LENGTH";
104
105 private static final String[] edgeLabeling = {
106 NONE,
107 INTEGRATED,
108 GENERIC
109 };
110
111 private static final String[] edgeLabelModel = {
112 BEST,
113 AS_IS,
114 CENTER_SLIDER,
115 SIDE_SLIDER,
116 FREE,
117 };
118
119 public DirectedOrthogonalLayoutModule()
120 {
121 super (DIRECTED_ORTHOGONAL_LAYOUTER,"yFiles Layout Team",
122 "Directed Orthogonal Layouter");
123 setPortIntersectionCalculatorEnabled(true);
124 }
125
126 public OptionHandler createOptionHandler()
127 {
128 OptionHandler op = new OptionHandler(getModuleName());
129 op.useSection(LAYOUT);
130 op.addInt(GRID,25)
131 .setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
132 op.addEnum(ORIENTATION, orientEnum, 0);
133
134 op.addBool(USE_EXISTING_DRAWING_AS_SKETCH, false);
135
136 OptionGroup og = new OptionGroup();
137 og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, IDENTIFY_DIRECTED_EDGES);
138 OptionItem oi = op.addEnum(USE_AS_CRITERIA, new String[]{LINE_COLOR, TARGET_ARROW, LINE_TYPE}, 0);
139 og.addItem(oi);
140 oi = op.addColor(LINE_COLOR, Color.red, true);
141 og.addItem(oi);
142 EnumOptionItem eoi;
143 eoi = new EnumOptionItem(TARGET_ARROW,
144 Arrow.availableArrows().toArray(),
145 Arrow.STANDARD);
146 eoi.setAttribute(EnumOptionItem.ATTRIBUTE_RENDERER,
147 new ArrowCellRenderer());
148 eoi.setUsingIntegers(true);
149 op.addItem(eoi);
150 og.addItem(eoi);
151 eoi = new EnumOptionItem(LINE_TYPE,
152 LineType.availableLineTypes().toArray(),
153 LineType.LINE_2);
154 eoi.setAttribute(EnumOptionItem.ATTRIBUTE_RENDERER,
155 new StrokeCellRenderer());
156 eoi.setUsingIntegers(true);
157 op.addItem(eoi);
158 og.addItem(eoi);
159
160 ConstraintManager cm = new ConstraintManager(op);
161
162 cm.setEnabledOnValueEquals(USE_AS_CRITERIA, LINE_COLOR, LINE_COLOR);
163 cm.setEnabledOnValueEquals(USE_AS_CRITERIA, TARGET_ARROW, TARGET_ARROW);
164 cm.setEnabledOnValueEquals(USE_AS_CRITERIA, LINE_TYPE, LINE_TYPE);
165
166 op.addBool(AUTO_GROUP_DIRECTED_EDGES, true);
167
168 cm.setEnabledOnValueEquals(USE_EXISTING_DRAWING_AS_SKETCH, Boolean.FALSE,
169 AUTO_GROUP_DIRECTED_EDGES);
170 op.addDouble(MINIMUM_FIRST_SEGMENT_LENGTH, 10);
171 op.addDouble(MINIMUM_LAST_SEGMENT_LENGTH, 10);
172 op.addDouble(MINIMUM_SEGMENT_LENGTH, 10);
173
174 op.useSection(LABELING);
175 og = new OptionGroup();
176 cm.setEnabledOnValueEquals(op.addEnum(EDGE_LABELING, edgeLabeling, 0), NONE,
177 og.addItem(op.addEnum(EDGE_LABEL_MODEL, edgeLabelModel, 0)),
178 true);
179 og.addItem(op.addBool(CONSIDER_NODE_LABELS, false));
180
181
182 return op;
183 }
184
185 public void mainrun()
186 {
187 final OptionHandler op = getOptionHandler();
188
189 final Graph2D graph = getGraph2D();
190
191 DataProvider upwardDP = null;
192 if(graph.getDataProvider(DirectedOrthogonalLayouter.DIRECTED_EDGE_DPKEY) == null) {
193 upwardDP = new DataProviderAdapter() {
195 public boolean getBool(Object o) {
196 EdgeRealizer er = graph.getRealizer((Edge)o);
197 if(op.get(USE_AS_CRITERIA).equals(LINE_COLOR)) {
198 Color c1 = (Color)op.get(LINE_COLOR);
199 Color c2 = er.getLineColor();
200 return c1 != null && c1.equals(c2);
201 }
202 else if(op.get(USE_AS_CRITERIA).equals(TARGET_ARROW)) {
203 Arrow a1 = (Arrow)op.get(TARGET_ARROW);
204 Arrow a2 = er.getTargetArrow();
205 return a1 != null && a1.equals(a2);
206 }
207 else if (op.get(USE_AS_CRITERIA).equals(LINE_TYPE)) {
208 LineType l1 = (LineType) op.get(LINE_TYPE);
209 LineType l2 = er.getLineType();
210 return l1 != null && l1.equals(l2);
211 }
212 return false;
213 }
214 };
215 graph.addDataProvider(DirectedOrthogonalLayouter.DIRECTED_EDGE_DPKEY, upwardDP);
216 }
217
218 DataProvider sgDPOrig = null, tgDPOrig = null;
219 EdgeMap sgMap = null, tgMap = null;
220 if(op.getBool(AUTO_GROUP_DIRECTED_EDGES)) {
221 sgDPOrig = graph.getDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY);
222 tgDPOrig = graph.getDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY);
223 sgMap = graph.createEdgeMap();
224 tgMap = graph.createEdgeMap();
225 graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, sgMap);
226 graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, tgMap);
227 autoGroupEdges(graph, sgMap, tgMap, upwardDP);
228 }
229
230 DirectedOrthogonalLayouter orthogonal = new DirectedOrthogonalLayouter();
231
232 final EdgeLayoutDescriptor layoutDescriptor = orthogonal.getEdgeLayoutDescriptor();
233 layoutDescriptor.setMinimumFirstSegmentLength(op.getDouble(MINIMUM_FIRST_SEGMENT_LENGTH));
234 layoutDescriptor.setMinimumLastSegmentLength(op.getDouble(MINIMUM_LAST_SEGMENT_LENGTH));
235 layoutDescriptor.setMinimumSegmentLength(op.getDouble(MINIMUM_SEGMENT_LENGTH));
236
237 orthogonal.setGrid(op.getInt(GRID));
238 orthogonal.setUseSketchDrawing(op.getBool(USE_EXISTING_DRAWING_AS_SKETCH));
239
240 final OrientationLayouter ol = (OrientationLayouter)orthogonal.getOrientationLayouter();
241 if(op.get(ORIENTATION).equals(TOP_TO_BOTTOM))
242 ol.setOrientation(OrientationLayouter.TOP_TO_BOTTOM);
243 else if(op.get(ORIENTATION).equals(LEFT_TO_RIGHT))
244 ol.setOrientation(OrientationLayouter.LEFT_TO_RIGHT);
245 else if(op.get(ORIENTATION).equals(BOTTOM_TO_TOP))
246 ol.setOrientation(OrientationLayouter.BOTTOM_TO_TOP);
247 else if(op.get(ORIENTATION).equals(RIGHT_TO_LEFT))
248 ol.setOrientation(OrientationLayouter.RIGHT_TO_LEFT);
249
250
254 if(op.getBool(CONSIDER_NODE_LABELS)) {
255 orthogonal.setConsiderNodeLabelsEnabled(true);
256 }
257
258 String el = op.getString(EDGE_LABELING);
259 orthogonal.setIntegratedEdgeLabelingEnabled(el.equals(INTEGRATED));
260 orthogonal.setConsiderNodeLabelsEnabled(op.getBool(CONSIDER_NODE_LABELS));
261 if (!el.equals(NONE)) {
262 setupEdgeLabelModel(el, op.getString(EDGE_LABEL_MODEL));
263 } else if (!op.getBool(CONSIDER_NODE_LABELS)) {
264 orthogonal.setLabelLayouterEnabled(false);
265 }
266
267 try {
268 launchLayouter(orthogonal, true);
269 if (el.equals(GENERIC)) {
270 GreedyMISLabeling la = new GreedyMISLabeling();
271 la.setPlaceNodeLabels(false);
272 la.setPlaceEdgeLabels(true);
273 la.setProfitModel(new LabelRanking());
274 la.doLayout(graph);
275 }
276 }
277 finally {
278 if(op.getBool(AUTO_GROUP_DIRECTED_EDGES))
279 {
280 graph.removeDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY);
281 graph.removeDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY);
282 if(sgDPOrig != null)
283 graph.addDataProvider(PortConstraintKeys.SOURCE_GROUPID_KEY, sgDPOrig);
284 if(tgDPOrig != null)
285 graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, tgDPOrig);
286 if (sgMap != null)
287 graph.disposeEdgeMap(sgMap);
288 if (tgMap != null)
289 graph.disposeEdgeMap(tgMap);
290 }
291 if(upwardDP != null) {
292 graph.removeDataProvider(DirectedOrthogonalLayouter.DIRECTED_EDGE_DPKEY);
293 }
294 }
295 }
296
297 void setupEdgeLabelModel(String edgeLabeling, String edgeLabelModel) {
298 if(edgeLabeling.equals(NONE) || edgeLabelModel.equals(AS_IS)) {
299 return; }
301
302 byte model = EdgeLabel.SIDE_SLIDER;
303 if (edgeLabelModel.equals(CENTER_SLIDER)) {
304 model = EdgeLabel.CENTER_SLIDER;
305 } else if (edgeLabelModel.equals(FREE) || edgeLabelModel.equals(BEST)) {
306 model = EdgeLabel.FREE;
307 }
308
309 Graph2D graph = getGraph2D();
310 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
311 Edge e = ec.edge();
312 EdgeRealizer er = graph.getRealizer(e);
313 for (int i = 0; i < er.labelCount(); i++) {
314 EdgeLabel el = er.getLabel(i);
315 el.setModel(model);
316 int prefAlongEdge = el.getPreferredPlacement() & LabelLayoutConstants.PLACEMENT_ALONG_EDGE_MASK;
318 int prefOnSide = el.getPreferredPlacement() & LabelLayoutConstants.PLACEMENT_ON_SIDE_OF_EDGE_MASK;
319 if (model == EdgeLabel.CENTER_SLIDER && prefOnSide != LabelLayoutConstants.PLACE_ON_EDGE) {
320 el.setPreferredPlacement((byte) (LabelLayoutConstants.PLACE_ON_EDGE | prefAlongEdge));
321 } else if(model == EdgeLabel.SIDE_SLIDER && prefOnSide == LabelLayoutConstants.PLACE_ON_EDGE) {
322 el.setPreferredPlacement((byte) (LabelLayoutConstants.PLACE_RIGHT_OF_EDGE | prefAlongEdge));
323 }
324 }
325 }
326 }
327
328
335 void autoGroupEdges(LayoutGraph graph, EdgeMap sgMap, EdgeMap tgMap, DataProvider positiveDP) {
336 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
337 sgMap.set(ec.edge(), null);
338 tgMap.set(ec.edge(), null);
339 }
340
341 BHeapIntNodePQ sourceGroupPQ = new BHeapIntNodePQ(graph);
342 BHeapIntNodePQ targetGroupPQ = new BHeapIntNodePQ(graph);
343 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
344 Node n = nc.node();
345 int outDegree = 0;
346 for (EdgeCursor ec = n.outEdges(); ec.ok(); ec.next()) {
347 if (positiveDP.getBool(ec.edge()) && !ec.edge().isSelfLoop())
348 outDegree++;
349 }
350 sourceGroupPQ.add(n, -outDegree);
351 int inDegree = 0;
352 for (EdgeCursor ec = n.inEdges(); ec.ok(); ec.next()) {
353 if (positiveDP.getBool(ec.edge()) && !ec.edge().isSelfLoop())
354 inDegree++;
355 }
356 targetGroupPQ.add(n, -inDegree);
357 }
358
359 while (!sourceGroupPQ.isEmpty() && !targetGroupPQ.isEmpty()) {
360 int bestIn = 0, bestOut = 0;
361 if (!sourceGroupPQ.isEmpty()) {
362 bestOut = -sourceGroupPQ.getMinPriority();
363 }
364 if (!targetGroupPQ.isEmpty()) {
365 bestIn = -targetGroupPQ.getMinPriority();
366 }
367 if (bestIn > bestOut) {
368 Node n = targetGroupPQ.removeMin();
369 for (EdgeCursor ec = n.inEdges(); ec.ok(); ec.next()) {
370 Edge e = ec.edge();
371 if (sgMap.get(e) == null && positiveDP.getBool(e) && !e.isSelfLoop()) {
372 tgMap.set(e, n);
373 sourceGroupPQ.changePriority(e.source(), sourceGroupPQ.getPriority(e.source()) + 1);
374 }
375 }
376 } else {
377 Node n = sourceGroupPQ.removeMin();
378 for (EdgeCursor ec = n.outEdges(); ec.ok(); ec.next()) {
379 Edge e = ec.edge();
380 if (tgMap.get(e) == null && positiveDP.getBool(e) && !e.isSelfLoop()) {
381 sgMap.set(e, n);
382 targetGroupPQ.increasePriority(e.target(), targetGroupPQ.getPriority(e.target()) + 1);
383 }
384 }
385 }
386 }
387 }
388 }
389