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 y.module.LayoutModule;
17  import y.layout.organic.SmartOrganicLayouter;
18  import y.layout.organic.OutputRestriction;
19  import y.layout.ComponentLayouter;
20  import y.option.ConstraintManager;
21  import y.option.OptionHandler;
22  import y.option.OptionGroup;
23  import y.option.DefaultEditorFactory;
24  import y.option.OptionItem;
25  import y.view.Graph2D;
26  import y.view.Selections;
27  import y.view.hierarchy.GroupLayoutConfigurator;
28  import y.view.hierarchy.HierarchyManager;
29  import y.base.NodeCursor;
30  import y.base.Node;
31  import y.base.NodeMap;
32  import y.util.Maps;
33  
34  import java.awt.Rectangle;
35  
36  /**
37   * This module represents an interactive configurator and launcher for
38   * {@link y.layout.organic.SmartOrganicLayouter}.
39   *
40   */
41  public class SmartOrganicLayoutModule extends LayoutModule
42  {
43    private static final String ACTIVATE_DETERMINISTIC_MODE = "ACTIVATE_DETERMINISTIC_MODE";
44    private static final String VISUAL = "VISUAL";
45    private static final String ALGORITHM = "ALGORITHM";
46    private static final String COMPACTNESS = "COMPACTNESS";
47    private static final String MAXIMAL_DURATION = "MAXIMAL_DURATION";
48    private static final String OBEY_NODE_SIZES = "OBEY_NODE_SIZES";
49    private static final String ALLOW_NODE_OVERLAPS = "ALLOW_NODE_OVERLAPS";
50    private static final String MINIMAL_NODE_DISTANCE = "MINIMAL_NODE_DISTANCE";
51    private static final String SCOPE = "SCOPE";
52    private static final String PREFERRED_EDGE_LENGTH = "PREFERRED_EDGE_LENGTH";
53    private static final String SMARTORGANIC = "SMARTORGANIC";
54    private static final String SCOPE_SUBSET = "SUBSET";
55    private static final String SCOPE_MAINLY_SUBSET = "MAINLY_SUBSET";
56    private static final String SCOPE_ALL = "ALL";
57    private static final String QUALITY_TIME_RATIO = "QUALITY_TIME_RATIO";
58    private static final String RESTRICT_OUTPUT = "RESTRICT_OUTPUT";
59    private static final String NONE = "NONE";
60    private static final String OUTPUT_CAGE = "OUTPUT_CAGE";
61    private static final String OUTPUT_CIRCULAR_CAGE = "OUTPUT_CIRCULAR_CAGE";
62    private static final String OUTPUT_ELLIPTICAL_CAGE = "OUTPUT_ELLIPTICAL_CAGE";
63    private static final String OUTPUT_AR = "OUTPUT_AR";
64    private static final String CAGE_X = "CAGE_X";
65    private static final String CAGE_Y = "CAGE_Y";
66    private static final String CAGE_WIDTH = "CAGE_WIDTH";
67    private static final String CAGE_HEIGHT = "CAGE_HEIGHT";
68    private static final String ELLIPTICAL_CAGE_X = "ELLIPTICAL_CAGE_X";
69    private static final String ELLIPTICAL_CAGE_Y = "ELLIPTICAL_CAGE_Y";
70    private static final String ELLIPTICAL_CAGE_WIDTH = "ELLIPTICAL_CAGE_WIDTH";
71    private static final String ELLIPTICAL_CAGE_HEIGHT = "ELLIPTICAL_CAGE_HEIGHT";
72    private static final String CAGE_CENTER_X = "CAGE_CENTER_X";
73    private static final String CAGE_CENTER_Y = "CAGE_CENTER_Y";
74    private static final String CAGE_RADIUS = "CAGE_RADIUS";
75    private static final String CAGE_RATIO = "CAGE_RATIO";
76    private static final String AR_CAGE_USE_VIEW = "AR_CAGE_USE_VIEW";
77    private static final String RECT_CAGE_USE_VIEW = "RECT_CAGE_USE_VIEW";
78    private static final String CIRC_CAGE_USE_VIEW = "CIRC_CAGE_USE_VIEW";
79    private static final String ELL_CAGE_USE_VIEW = "ELL_CAGE_USE_VIEW";
80    private static final String RESTRICTIONS = "RESTRICTIONS";
81    private static final String AVOID_NODE_EDGE_OVERLAPS = "AVOID_NODE_EDGE_OVERLAPS";
82    private static final String GROUPING      = "GROUPING";
83    private static final String GROUP_LAYOUT_POLICY = "GROUP_LAYOUT_POLICY";
84    private static final String IGNORE_GROUPS = "IGNORE_GROUPS";
85    private static final String LAYOUT_GROUPS = "LAYOUT_GROUPS";
86    private static final String FIX_GROUP_BOUNDS    = "FIX_GROUP_BOUNDS";
87    private static final String FIX_GROUP_CONTENTS = "FIX_GROUP_CONTENTS";
88  //  private static final String GROUP_NODE_COMPACTNESS = "GROUP_NODE_COMPACTNESS";
89  
90  // for the option handler
91    private final static String[] SCOPES =
92    {
93            SCOPE_ALL,
94            SCOPE_MAINLY_SUBSET,
95            SCOPE_SUBSET,
96    };
97  
98  // for the option handler
99    private final String[] GROUPING_POLICIES = new String[]{
100         LAYOUT_GROUPS,
101         FIX_GROUP_CONTENTS,
102         FIX_GROUP_BOUNDS,
103         IGNORE_GROUPS,
104   };
105 
106   private final static String[] OUTPUT_RESTRICTIONS =
107   {
108           NONE,
109           OUTPUT_CAGE,
110           OUTPUT_CIRCULAR_CAGE,
111           OUTPUT_AR,
112           OUTPUT_ELLIPTICAL_CAGE,
113   };
114 
115   private SmartOrganicLayouter organic;
116 
117   public SmartOrganicLayoutModule()
118   {
119     super (SMARTORGANIC,
120            "yWorks Graph Layout Team",
121            "Wrapper for SmartOrganicLayouter");
122   }
123 
124   /**
125    * Factory method. Responsible for creating and initializing
126    * the OptionHandler for this module.
127    */
128   protected OptionHandler createOptionHandler()
129   {
130     createOrganic();
131 
132     OptionHandler op = new OptionHandler(getModuleName());
133     ConstraintManager cm = new ConstraintManager(op);
134 
135     op.useSection(VISUAL);
136     op.addEnum(SCOPE, SCOPES,
137                organic.getScope());
138     op.addInt(PREFERRED_EDGE_LENGTH, (int)organic.getPreferredEdgeLength(), 5, 500);
139     op.addBool(OBEY_NODE_SIZES,organic.isNodeSizeAware());
140     op.addBool(ALLOW_NODE_OVERLAPS,organic.isNodeOverlapsAllowed());
141     op.addDouble(MINIMAL_NODE_DISTANCE,organic.getMinimalNodeDistance(),0,100,0);
142     op.addBool(AVOID_NODE_EDGE_OVERLAPS, false);
143     cm.setEnabledOnValueEquals(ALLOW_NODE_OVERLAPS, Boolean.FALSE, MINIMAL_NODE_DISTANCE);
144 
145     op.addDouble(COMPACTNESS,organic.getCompactness(),0,1);
146     op.useSection(RESTRICTIONS);
147 
148     final Object ctrId = new Object();
149     op.addEnum(RESTRICT_OUTPUT, OUTPUT_RESTRICTIONS, 0).setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
150 
151     OptionGroup og;
152     og = new OptionGroup();
153     cm.setEnabledOnValueEquals(RESTRICT_OUTPUT, OUTPUT_CAGE, og );
154     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, OUTPUT_CAGE);
155     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
156     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CARD_ID, OUTPUT_CAGE);
157     og.addItem( op.addBool(RECT_CAGE_USE_VIEW, true));
158 
159     ConstraintManager.Condition condition = cm.createConditionValueEquals(RESTRICT_OUTPUT, OUTPUT_CAGE).and(
160         cm.createConditionValueEquals(RECT_CAGE_USE_VIEW, Boolean.FALSE));
161 
162     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_X, 0.0d) ));
163     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_Y, 0.0d) ));
164     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_WIDTH, 1000.0d) ));
165     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_HEIGHT, 1000.0d) ));
166 
167     og = new OptionGroup();
168     cm.setEnabledOnValueEquals(RESTRICT_OUTPUT, OUTPUT_CIRCULAR_CAGE, og );
169     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, OUTPUT_CIRCULAR_CAGE);
170     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
171     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CARD_ID, OUTPUT_CIRCULAR_CAGE);
172     og.addItem( op.addBool(CIRC_CAGE_USE_VIEW, true));
173     condition = cm.createConditionValueEquals(RESTRICT_OUTPUT, OUTPUT_CIRCULAR_CAGE).and(
174         cm.createConditionValueEquals(CIRC_CAGE_USE_VIEW, Boolean.FALSE));
175     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_CENTER_X, 0.0d) ));
176     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_CENTER_Y, 0.0d) ));
177     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_RADIUS, 1000.0d) ));
178 
179     og = new OptionGroup();
180     cm.setEnabledOnValueEquals(RESTRICT_OUTPUT, OUTPUT_ELLIPTICAL_CAGE, og );
181     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, OUTPUT_ELLIPTICAL_CAGE);
182     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
183     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CARD_ID, OUTPUT_ELLIPTICAL_CAGE);
184     og.addItem( op.addBool(ELL_CAGE_USE_VIEW, true));
185     condition = cm.createConditionValueEquals(RESTRICT_OUTPUT, OUTPUT_ELLIPTICAL_CAGE).and(
186         cm.createConditionValueEquals(ELL_CAGE_USE_VIEW, Boolean.FALSE));
187     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(ELLIPTICAL_CAGE_X, 0.0d) ));
188     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(ELLIPTICAL_CAGE_Y, 0.0d) ));
189     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(ELLIPTICAL_CAGE_WIDTH, 1000.0d) ));
190     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(ELLIPTICAL_CAGE_HEIGHT, 1000.0d) ));
191 
192     og = new OptionGroup();
193     cm.setEnabledOnValueEquals(RESTRICT_OUTPUT, OUTPUT_AR, og );
194     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, OUTPUT_AR);
195     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
196     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CARD_ID, OUTPUT_AR);
197     og.addItem( op.addBool(AR_CAGE_USE_VIEW, true));
198     condition = cm.createConditionValueEquals(RESTRICT_OUTPUT, OUTPUT_AR).and(
199         cm.createConditionValueEquals(AR_CAGE_USE_VIEW, Boolean.FALSE));
200     cm.setEnabledOnCondition(condition, og.addItem( op.addDouble(CAGE_RATIO, 1.0d) ));
201 
202     op.useSection(GROUPING);
203     op.addEnum(GROUP_LAYOUT_POLICY, GROUPING_POLICIES, 0);
204 //    op.addDouble(GROUP_NODE_COMPACTNESS, organic.getGroupNodeCompactness(), 0, 1);
205 
206 
207     op.useSection(ALGORITHM);
208     OptionItem qualityItem = op.addDouble(QUALITY_TIME_RATIO, organic.getQualityTimeRatio(), 0, 1 );
209     qualityItem.setAttribute( DefaultEditorFactory.ATTRIBUTE_MIN_VALUE_LABEL_TEXT, "SPEED" );
210     qualityItem.setAttribute( DefaultEditorFactory.ATTRIBUTE_MAX_VALUE_LABEL_TEXT, "QUALITY" );
211 
212     op.addInt(MAXIMAL_DURATION,(int)(organic.getMaximumDuration()/1000));
213     op.addBool(ACTIVATE_DETERMINISTIC_MODE,organic.isDeterministic());
214     return op;
215   }
216 
217   /**
218    * Module initialization routine. Typically this method is used to 
219    * configure the underlying algorithm with the options found in the
220    * options handler of this module.
221    */
222   protected void init()
223   {
224     createOrganic();
225 
226     OptionHandler op = getOptionHandler();
227     organic.setPreferredEdgeLength(op.getInt(VISUAL, PREFERRED_EDGE_LENGTH));
228     organic.setNodeOverlapsAllowed(op.getBool(VISUAL, ALLOW_NODE_OVERLAPS));
229     organic.setMinimalNodeDistance(op.getDouble(VISUAL, MINIMAL_NODE_DISTANCE));
230     organic.setScope((byte)OptionHandler.getIndex(SCOPES,
231                                         op.getString(VISUAL, SCOPE)));
232     organic.setCompactness(op.getDouble(VISUAL, COMPACTNESS));
233     organic.setNodeSizeAware(op.getBool(VISUAL, OBEY_NODE_SIZES));
234     organic.setNodeEdgeOverlapAvoided(op.getBool(AVOID_NODE_EDGE_OVERLAPS));
235     organic.setDeterministic(op.getBool(ALGORITHM, ACTIVATE_DETERMINISTIC_MODE));
236     organic.setMaximumDuration(1000*op.getInt(ALGORITHM, MAXIMAL_DURATION));
237     organic.setQualityTimeRatio(op.getDouble(ALGORITHM, QUALITY_TIME_RATIO));
238     switch (op.getEnum(RESTRICT_OUTPUT)){
239       case 0:
240         organic.setComponentLayouterEnabled(true);
241         organic.setOutputRestriction( OutputRestriction.NONE);
242         break;
243       case 1: {
244         double x;
245         double y;
246         double w;
247         double h;
248         if (op.getBool(RECT_CAGE_USE_VIEW)) {
249           Rectangle visibleRect = getGraph2DView().getVisibleRect();
250           x = visibleRect.x;
251           y = visibleRect.y;
252           w = visibleRect.width;
253           h = visibleRect.height;
254         } else {
255           x = op.getDouble(CAGE_X);
256           y = op.getDouble(CAGE_Y);
257           w = op.getDouble(CAGE_WIDTH);
258           h = op.getDouble(CAGE_HEIGHT);
259         }
260         organic.setOutputRestriction(
261             OutputRestriction.createRectangularCageRestriction(x, y, w, h));
262         organic.setComponentLayouterEnabled(false);
263         break;
264       }
265       case 2:
266       {
267         double x;
268         double y;
269         double radius;
270         if (op.getBool(CIRC_CAGE_USE_VIEW)) {
271           Rectangle visibleRect = getGraph2DView().getVisibleRect();
272           x = visibleRect.getCenterX();
273           y = visibleRect.getCenterY();
274           radius = Math.min(visibleRect.width, visibleRect.height) * 0.5d;
275         } else {
276           x = op.getDouble(CAGE_CENTER_X);
277           y = op.getDouble(CAGE_CENTER_Y);
278           radius = op.getDouble(CAGE_RADIUS);
279         }
280         organic.setOutputRestriction( OutputRestriction.createCircularCageRestriction(x, y, radius));
281         organic.setComponentLayouterEnabled(false);
282         break;
283       }
284       case 3:
285       {
286         double ratio;
287         if (op.getBool(AR_CAGE_USE_VIEW)) {
288           Rectangle visibleRect = getGraph2DView().getVisibleRect();
289           ratio = visibleRect.getWidth()/visibleRect.getHeight();
290         } else {
291           ratio = op.getDouble(CAGE_RATIO);
292         }
293         organic.setOutputRestriction( OutputRestriction.createAspectRatioRestriction(ratio));
294         organic.setComponentLayouterEnabled(true);
295         ((ComponentLayouter) organic.getComponentLayouter()).setPreferredLayoutSize(ratio * 100, 100);
296         break;
297       }
298       case 4:
299       {
300         double x;
301         double y;
302         double w;
303         double h;
304         if (op.getBool(ELL_CAGE_USE_VIEW)) {
305           Rectangle visibleRect = getGraph2DView().getVisibleRect();
306           x = visibleRect.x;
307           y = visibleRect.y;
308           w = visibleRect.width;
309           h = visibleRect.height;
310         } else {
311           x = op.getDouble(ELLIPTICAL_CAGE_X);
312           y = op.getDouble(ELLIPTICAL_CAGE_Y);
313           w = op.getDouble(ELLIPTICAL_CAGE_WIDTH);
314           h = op.getDouble(ELLIPTICAL_CAGE_HEIGHT);
315         }
316         organic.setOutputRestriction(
317             OutputRestriction.createEllipticalCageRestriction(x, y, w, h));
318         organic.setComponentLayouterEnabled(false);
319         break;
320       }
321     }
322   }
323 
324   /**
325    * Main module execution routine. launches the hierarchic layouter.
326    */
327   protected void mainrun()
328   {
329     final OptionHandler handler = getOptionHandler();
330     final int policy = handler.getEnum(GROUP_LAYOUT_POLICY);
331     createOrganic();
332     final Graph2D graph = getGraph2D();
333     GroupLayoutConfigurator glc = null;
334     try {
335       if (policy != 3 && HierarchyManager.containsGroupNodes(graph)) {
336         glc = new GroupLayoutConfigurator(graph);
337         glc.prepareAll();
338       }
339       if (policy == 2 && glc != null) {
340         NodeMap nodeMap = Maps.createHashedNodeMap();
341         for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
342           Node node = nodeCursor.node();
343           if (HierarchyManager.getInstance(graph).isGroupNode(node)){
344             nodeMap.set(node, SmartOrganicLayouter.GROUP_NODE_MODE_FIX_BOUNDS);
345           }
346         }
347         graph.addDataProvider(SmartOrganicLayouter.GROUP_NODE_MODE_DATA, nodeMap);
348       } else if (policy == 1 && glc != null) {
349         NodeMap nodeMap = Maps.createHashedNodeMap();
350         for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
351           Node node = nodeCursor.node();
352           if (HierarchyManager.getInstance(graph).isGroupNode(node)){
353             nodeMap.set(node, SmartOrganicLayouter.GROUP_NODE_MODE_FIX_CONTENTS);
354           }
355         }
356         graph.addDataProvider(SmartOrganicLayouter.GROUP_NODE_MODE_DATA, nodeMap);
357       }
358       graph.addDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA,
359           Selections.createSelectionNodeMap(graph));
360       launchLayouter(organic);
361     } finally {
362       graph.removeDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA);
363       if (glc != null){
364         glc.restoreAll();
365       }
366       if (policy == 2 || policy == 1) {
367         graph.removeDataProvider(SmartOrganicLayouter.GROUP_NODE_MODE_DATA);
368       }
369     }
370   }
371 
372   /**
373    * clean up the module, clear temporarily bound dataproviders and
374    * references to the wrapped algorithm.
375    */
376   protected void dispose()
377   {
378     organic = null;
379   }
380 
381   private void createOrganic()
382   {
383     if(organic == null)
384     {
385       organic = new SmartOrganicLayouter();
386     }
387   }
388 }
389