1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.layout.module;
29  
30  import y.module.LayoutModule;
31  import y.module.YModule;
32  
33  import y.base.DataProvider;
34  import y.base.Edge;
35  import y.base.EdgeCursor;
36  import y.geom.OrientedRectangle;
37  import y.layout.EdgeLabelModel;
38  import y.layout.LabelRanking;
39  import y.layout.RotatedDiscreteEdgeLabelModel;
40  import y.layout.RotatedSliderEdgeLabelModel;
41  import y.layout.labeling.GreedyMISLabeling;
42  import y.layout.labeling.MISLabelingAlgorithm;
43  import y.layout.labeling.SALabeling;
44  import y.option.OptionHandler;
45  import y.option.ConstraintManager;
46  import y.option.ConstraintManager.Condition;
47  import y.option.EnumOptionItem;
48  import y.util.DataProviderAdapter;
49  import y.view.EdgeLabel;
50  import y.view.EdgeRealizer;
51  import y.view.Graph2D;
52  import y.view.NodeLabel;
53  import y.view.YLabel;
54  
55  /**
56   * This module represents an interactive configurator and launcher for the
57   * yFiles labeling algorithms.
58   *
59   */
60  public class LabelingModule extends LayoutModule {
61  
62    //// Module 'Labeling'
63    protected static final String MODULE_DIVERSE_LABELING = "DIVERSE_LABELING";
64    
65    //// Section 'Scope'
66    protected static final String SECTION_SCOPE = "SCOPE";
67    // Section 'Scope' items
68    protected static final String ITEM_PLACE_NODE_LABELS = "PLACE_NODE_LABELS";
69    protected static final String ITEM_PLACE_EDGE_LABELS = "PLACE_EDGE_LABELS";
70    protected static final String ITEM_CONSIDER_SELECTED_FEATURES_ONLY = "CONSIDER_SELECTED_FEATURES_ONLY";
71    protected static final String ITEM_CONSIDER_INVISIBLE_LABELS = "CONSIDER_INVISIBLE_LABELS";
72    
73    //// Section 'Quality'
74    protected static final String SECTION_QUALITY = "QUALITY";
75    // Section 'Quality' items
76    protected static final String ITEM_USE_OPTIMIZATION = "USE_OPTIMIZATION";
77    protected static final String ITEM_OPTIMIZATION_STRATEGY = "OPTIMIZATION_STRATEGY";
78    protected static final String VALUE_OPTIMIZATION_BALANCED = "OPTIMIZATION_BALANCED";
79    protected static final String VALUE_OPTIMIZATION_NONE = "OPTIMIZATION_NONE";
80    protected static final String VALUE_OPTIMIZATION_EDGE_OVERLAP = "OPTIMIZATION_EDGE_OVERLAP";
81    protected static final String VALUE_OPTIMIZATION_LABEL_OVERLAP = "OPTIMIZATION_LABEL_OVERLAP";
82    protected static final String VALUE_OPTIMIZATION_NODE_OVERLAP = "OPTIMIZATION_NODE_OVERLAP";
83    protected static final String ITEM_ALLOW_NODE_OVERLAPS = "ALLOW_NODE_OVERLAPS";
84    protected static final String ITEM_ALLOW_EDGE_OVERLAPS = "ALLOW_EDGE_OVERLAPS";
85    protected static final String ITEM_USE_POSTPROCESSING = "USE_POSTPROCESSING";
86    
87    //// Section 'Model'
88    protected static final String SECTION_MODEL = "MODEL";
89    // Section 'Model' items
90    protected static final String ITEM_EDGE_LABEL_MODEL = "EDGE_LABEL_MODEL";
91    protected static final String VALUE_CENTERED = "CENTERED";
92    protected static final String VALUE_TWO_POS = "TWO_POS";
93    protected static final String VALUE_SIX_POS = "SIX_POS";
94    protected static final String VALUE_THREE_CENTER = "THREE_CENTER";
95    protected static final String VALUE_FREE = "FREE";
96    protected static final String VALUE_CENTER_SLIDER = "CENTER_SLIDER";
97    protected static final String VALUE_SIDE_SLIDER = "SIDE_SLIDER";
98    protected static final String VALUE_AS_IS = "AS_IS";
99    protected static final String VALUE_BEST = "BEST";
100   protected static final String ITEM_AUTO_ROTATE = "AUTO_ROTATE";
101 
102   // data provider keys, no i18n
103   protected static final String LABEL_SELECTION_DP_KEY = "LABEL_SELECTION";
104 
105   /**
106    * Creates an instance of this module.
107    */
108   public LabelingModule() {
109     super(MODULE_DIVERSE_LABELING);
110   }
111 
112   /**
113    * Creates an OptionHandler and adds the option items used by this module.
114    * @return the created <code>OptionHandler</code> providing module related options
115    */
116   protected OptionHandler createOptionHandler() {
117     final OptionHandler options = new OptionHandler(getModuleName());
118     final ConstraintManager optionConstraint = new ConstraintManager(options);
119 
120     //// Section 'Scope'
121     options.useSection(SECTION_SCOPE);
122     // Populate section
123     options.addBool(ITEM_PLACE_NODE_LABELS, true);
124     options.addBool(ITEM_PLACE_EDGE_LABELS, true);
125     options.addBool(ITEM_CONSIDER_SELECTED_FEATURES_ONLY, false);
126     options.addBool(ITEM_CONSIDER_INVISIBLE_LABELS, false);
127 
128     //// Section 'Quality'
129     options.useSection(SECTION_QUALITY);
130     // Populate section
131     options.addBool(ITEM_USE_OPTIMIZATION, false);
132     options.addEnum(ITEM_OPTIMIZATION_STRATEGY, new String[]{
133         VALUE_OPTIMIZATION_BALANCED,
134         VALUE_OPTIMIZATION_NONE,
135         VALUE_OPTIMIZATION_EDGE_OVERLAP,
136         VALUE_OPTIMIZATION_LABEL_OVERLAP,
137         VALUE_OPTIMIZATION_NODE_OVERLAP
138     }, 0);
139     options.addBool(ITEM_ALLOW_NODE_OVERLAPS, false);
140     options.addBool(ITEM_ALLOW_EDGE_OVERLAPS, true);
141     options.addBool(ITEM_USE_POSTPROCESSING, false);
142 
143     //// Section 'Model'
144     options.useSection(SECTION_MODEL);
145     // Populate section
146     final EnumOptionItem itemEdgeLabelModel = options.addEnum(ITEM_EDGE_LABEL_MODEL, new String[]{
147         VALUE_CENTERED,
148         VALUE_TWO_POS,
149         VALUE_SIX_POS,
150         VALUE_THREE_CENTER,
151         VALUE_FREE,
152         VALUE_CENTER_SLIDER,
153         VALUE_SIDE_SLIDER,
154         VALUE_AS_IS,
155         VALUE_BEST
156     }, 8);
157     options.addBool(ITEM_AUTO_ROTATE, false);
158     // Enable/disable items depending on specific values
159     // enable the auto rotate item for applicably label models only
160     final Condition condition =
161         optionConstraint.createConditionValueIs(itemEdgeLabelModel, new String[]{
162             VALUE_AS_IS,
163             VALUE_BEST,
164             VALUE_FREE
165         });
166     optionConstraint.setEnabledOnCondition(condition.inverse(), options.getItem(ITEM_AUTO_ROTATE));
167 
168     return options;
169   }
170 
171   /**
172    * Translates the given optimization strategy string into the corresponding
173    * byte constant of class {@link MISLabelingAlgorithm}.
174    */
175   private static byte translateOptimizationStrategy(final String optimizationStrategy) {
176     if (VALUE_OPTIMIZATION_LABEL_OVERLAP.equals(optimizationStrategy)) {
177       return MISLabelingAlgorithm.OPTIMIZATION_LABEL_OVERLAP;
178     } else if (VALUE_OPTIMIZATION_BALANCED.equals(optimizationStrategy)) {
179       return MISLabelingAlgorithm.OPTIMIZATION_BALANCED;
180     } else if (VALUE_OPTIMIZATION_EDGE_OVERLAP.equals(optimizationStrategy)) {
181       return MISLabelingAlgorithm.OPTIMIZATION_EDGE_OVERLAP;
182     } else if (VALUE_OPTIMIZATION_NODE_OVERLAP.equals(optimizationStrategy)) {
183       return MISLabelingAlgorithm.OPTIMIZATION_NODE_OVERLAP;
184     } else {
185       return MISLabelingAlgorithm.OPTIMIZATION_NONE;
186     }
187   }
188 
189   /**
190    * Main module execution routine.
191    * Launches the module's underlying algorithm on the module's graph based on user options.
192    */
193   protected void mainrun() {
194     final OptionHandler options = getOptionHandler();
195     final MISLabelingAlgorithm al = options.getBool(ITEM_USE_OPTIMIZATION)
196         ? (MISLabelingAlgorithm) new SALabeling()
197         : new GreedyMISLabeling();
198 
199     configure(al, options);
200 
201     final Graph2D graph = getGraph2D();
202     prepareGraph(graph, options);
203     try {
204       launchLayouter(al);
205     } finally {
206       restoreGraph(graph, options);
207     }
208   }
209 
210   /**
211    * Prepares a <code>graph</code> depending on the given options for the
212    * module's labeling algorithm.
213    * <br>
214    * Additional resources created by this method have to be freed up by calling
215    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
216    * layout calculation.  
217    * @param graph the graph to be prepared
218    * @param options the options for the module's labeling algorithm
219    */
220   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
221     final DataProvider labelSet = new LabelSetDP(
222             graph,
223             options.getBool(ITEM_CONSIDER_SELECTED_FEATURES_ONLY),
224             options.getBool(ITEM_PLACE_NODE_LABELS),
225             options.getBool(ITEM_PLACE_EDGE_LABELS),
226             options.getBool(ITEM_CONSIDER_INVISIBLE_LABELS));
227     graph.addDataProvider(LABEL_SELECTION_DP_KEY, labelSet);
228 
229     setupEdgeLabelModels(graph, options.getString(ITEM_EDGE_LABEL_MODEL), labelSet, options.getBool(ITEM_AUTO_ROTATE));
230   }
231 
232   /**
233    * Restores the given <code>graph</code> by freeing up resources created by
234    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
235    * @param graph the graph for which <code>prepareGraph</code> has been called
236    * @param options the options for the module's labeling algorithm
237    */
238   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
239     graph.removeDataProvider(LABEL_SELECTION_DP_KEY);
240   }
241 
242   /**
243    * Configures the module's layout algorithm according to the given options.
244    * @param al the <code>MISLabelingAlgorithm</code> to be configured
245    * @param options the layout options to set
246    */
247   protected void configure(final MISLabelingAlgorithm al, final OptionHandler options) {
248     al.setAutoFlippingEnabled(true);
249     al.setOptimizationStrategy(translateOptimizationStrategy(options.getString(ITEM_OPTIMIZATION_STRATEGY)));
250     if (al.getOptimizationStrategy() == MISLabelingAlgorithm.OPTIMIZATION_NONE) {
251       al.setProfitModel(new LabelRanking());
252     }
253     al.setRemoveNodeOverlaps(!options.getBool(ITEM_ALLOW_NODE_OVERLAPS));
254     al.setRemoveEdgeOverlaps(!options.getBool(ITEM_ALLOW_EDGE_OVERLAPS));
255     al.setApplyPostprocessing(options.getBool(ITEM_USE_POSTPROCESSING));
256 
257     al.setSelection(LABEL_SELECTION_DP_KEY);
258   }
259 
260   private static void setupEdgeLabelModels(
261           final Graph2D graph,
262           final String modelValue, final DataProvider labelFilter, final boolean autoRotate
263   ) {
264     if (VALUE_AS_IS.equals(modelValue)) {
265       return;
266     }
267 
268     final byte model;
269     if (VALUE_CENTERED.equals(modelValue)) {
270       model = EdgeLabel.CENTERED;
271     } else if (VALUE_TWO_POS.equals(modelValue)) {
272       model = EdgeLabel.TWO_POS;
273     } else if (VALUE_SIX_POS.equals(modelValue)) {
274       model = EdgeLabel.SIX_POS;
275     } else if (VALUE_THREE_CENTER.equals(modelValue)) {
276       model = EdgeLabel.THREE_CENTER;
277     } else if (VALUE_CENTER_SLIDER.equals(modelValue)) {
278       model = EdgeLabel.CENTER_SLIDER;
279     } else if (VALUE_SIDE_SLIDER.equals(modelValue)) {
280       model = EdgeLabel.SIDE_SLIDER;
281     } else {
282       // else if VALUE_FREE.equals(modelValue) or VALUE_BEST.equals(modelValue)
283       model = EdgeLabel.FREE;
284     }
285 
286     EdgeLabelModel labelModel = null;
287     if (autoRotate) {
288       labelModel = getEdgeLabelModel(model);
289     }
290 
291     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
292       final Edge e = ec.edge();
293       EdgeRealizer er = graph.getRealizer(e);
294       for (int i = 0; i < er.labelCount(); i++) {
295         final EdgeLabel label = er.getLabel(i);
296         if (labelFilter.getBool(label)) {
297           final OrientedRectangle lb = label.getOrientedBox();
298           if (labelModel != null) {
299             label.setLabelModel(labelModel, labelModel.getDefaultParameter());
300             label.setModelParameter(label.getBestModelParameterForBounds(lb));
301           } else {
302             label.setModel(model);
303             label.setModelParameter(label.getBestModelParameterForBounds(lb));
304           }
305         }
306       }
307     }
308   }
309 
310   private static EdgeLabelModel getEdgeLabelModel(byte modelValue) {
311     final EdgeLabelModel labelModel;
312     if (EdgeLabel.CENTERED == modelValue) {
313       labelModel = new RotatedDiscreteEdgeLabelModel(RotatedDiscreteEdgeLabelModel.CENTERED);
314       ((RotatedDiscreteEdgeLabelModel) labelModel).setAutoRotationEnabled(true);
315     } else if (EdgeLabel.TWO_POS == modelValue) {
316       labelModel = new RotatedDiscreteEdgeLabelModel(RotatedDiscreteEdgeLabelModel.TWO_POS);
317       ((RotatedDiscreteEdgeLabelModel) labelModel).setAutoRotationEnabled(true);
318     } else if (EdgeLabel.SIX_POS == modelValue) {
319       labelModel = new RotatedDiscreteEdgeLabelModel(RotatedDiscreteEdgeLabelModel.SIX_POS);
320       ((RotatedDiscreteEdgeLabelModel) labelModel).setAutoRotationEnabled(true);
321     } else if (EdgeLabel.THREE_CENTER == modelValue) {
322       labelModel = new RotatedDiscreteEdgeLabelModel(RotatedDiscreteEdgeLabelModel.THREE_CENTER);
323       ((RotatedDiscreteEdgeLabelModel) labelModel).setAutoRotationEnabled(true);
324     } else if (EdgeLabel.CENTER_SLIDER == modelValue) {
325       labelModel = new RotatedSliderEdgeLabelModel(RotatedSliderEdgeLabelModel.CENTER_SLIDER);
326       ((RotatedSliderEdgeLabelModel) labelModel).setAutoRotationEnabled(true);
327     } else if (EdgeLabel.SIDE_SLIDER == modelValue) {
328       labelModel = new RotatedSliderEdgeLabelModel(RotatedSliderEdgeLabelModel.SIDE_SLIDER);
329       ((RotatedSliderEdgeLabelModel) labelModel).setAutoRotationEnabled(true);
330     } else {
331       labelModel = null;
332     }
333     return labelModel;
334   }
335 
336   /**
337    * Selects the labels we want to set.
338    */
339   static class LabelSetDP extends DataProviderAdapter {
340     private final boolean considerOnlySelected;
341     private final Graph2D graph;
342     private final boolean nodes;
343     private final boolean edges;
344     private final boolean invisible;
345 
346     LabelSetDP(Graph2D g,boolean sel,boolean n,boolean e,boolean uv) {
347       considerOnlySelected = sel;
348       graph = g;
349       nodes = n;
350       edges = e;
351       invisible = uv;
352     }
353 
354     public boolean getBool(Object o) {
355       YLabel ylabel = (YLabel) o;
356       if (!ylabel.isVisible() && !invisible) {
357         return false;
358       }
359       if (o instanceof NodeLabel) {
360         final NodeLabel l = (NodeLabel) o;
361         if (l.getModel() == NodeLabel.INTERNAL) {
362           return false;
363         }
364       }
365       if (considerOnlySelected) {
366         if ((o instanceof NodeLabel) && nodes) {
367           final NodeLabel l = (NodeLabel) o;
368           return graph.isSelected(l.getNode());
369         }
370         if ((o instanceof EdgeLabel) && edges) {
371           final EdgeLabel l = (EdgeLabel) o;
372           return graph.isSelected(l.getEdge());
373         }
374         return false;
375       } else {
376         if ((o instanceof NodeLabel) && nodes) {
377           return true;
378         }
379         return (o instanceof EdgeLabel) && edges;
380       }
381     }
382   }
383 }
384