1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
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-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
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  /**
53   * This module represents an interactive configurator and launcher for
54   * {@link y.layout.orthogonal.DirectedOrthogonalLayouter}.
55   *
56   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/directed_orthogonal_layouter.html#directed_orthogonal_layouter">Section Directed Orthogonal Layout</a> in the yFiles for Java Developer's Guide
57   */
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       //determine upward edges if not already marked.
194       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     ////////////////////////////////////////////////////////////////////////////
251     // Labels
252     ////////////////////////////////////////////////////////////////////////////
253 
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; //nothing to do
300     }
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 //        el.setRotationAngle(0);
317         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   /**
329    * Automatically groups edges either on their source or target side, but never on
330    * both sides at the same time.
331    * @param graph input graph
332    * @param sgMap source group id map
333    * @param tgMap target group id map
334    */
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