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