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.Node;
34  import y.layout.ComponentLayouter;
35  import y.layout.LayoutOrientation;
36  import y.layout.genealogy.FamilyTreeLayouter;
37  import y.layout.hierarchic.IncrementalHierarchicLayouter;
38  import y.layout.hierarchic.incremental.EdgeLayoutDescriptor;
39  import y.layout.hierarchic.incremental.NodeLayoutDescriptor;
40  import y.layout.hierarchic.incremental.RoutingStyle;
41  import y.layout.hierarchic.incremental.SimplexNodePlacer;
42  import y.option.ConstraintManager;
43  import y.option.OptionGroup;
44  import y.option.OptionHandler;
45  import y.option.OptionItem;
46  import y.util.DataProviderAdapter;
47  import y.view.Graph2D;
48  import y.view.NodeRealizer;
49  
50  import java.awt.Color;
51  
52  /**
53   * This module represents an interactive configurator and launcher for {@link y.layout.genealogy.FamilyTreeLayouter}.
54   *
55   */
56  public class FamilyTreeLayoutModule extends LayoutModule {
57  
58    //// Module 'Family Tree Layouter'
59    protected static final String MODULE_FAMILY_TREE_LAYOUTER = "FAMILY_TREE_LAYOUTER";
60    
61    // The colors used by the data provider to distinguish family nodes from individual nodes
62    //// Section 'Distinguish Node Types'
63    protected static final String SECTION_FAMILY_PROPERTIES = "FAMILY_PROPERTIES";
64    //-
65    protected static final String ITEM_FAMILY_COLOR = "FAMILY_COLOR";
66    protected static final String ITEM_MALE_COLOR = "MALE_COLOR";
67    protected static final String ITEM_FEMALE_COLOR = "FEMALE_COLOR";
68  
69    // Basic layout properties
70    //// Section 'Layout'
71    protected static final String SECTION_LAYOUT = "LAYOUT";
72    // Section 'Layout' items
73    protected static final String ITEM_ORIENTATION = "ORIENTATION";
74    protected static final String VALUE_TOP_TO_BOTTOM = "TOP_TO_BOTTOM";
75    protected static final String VALUE_LEFT_TO_RIGHT = "LEFT_TO_RIGHT";
76    protected static final String VALUE_BOTTOM_TO_TOP = "BOTTOM_TO_TOP";
77    protected static final String VALUE_RIGHT_TO_LEFT = "RIGHT_TO_LEFT";
78    protected static final String ITEM_SINGLE_DIRECT_BELOW = "SINGLE_DIRECT_BELOW";
79    protected static final String ITEM_FAMILIES_ALWAYS_BELOW = "FAMILIES_ALWAYS_BELOW";
80    protected static final String ITEM_NODE_ALIGNMENT = "NODE_ALIGNMENT";
81    protected static final String VALUE_NODE_ALIGN_TOP = "NODE_ALIGN_TOP";
82    protected static final String VALUE_NODE_ALIGN_CENTER = "NODE_ALIGN_CENTER";
83    protected static final String VALUE_NODE_ALIGN_BOTTOM = "NODE_ALIGN_BOTTOM";
84    protected static final String ITEM_SORT_BY_SEX = "SORT_BY_SEX";
85    protected static final String VALUE_DO_NOT_SORT = "DO_NOT_SORT";
86    protected static final String VALUE_FEMALE_LEFT = "FEMALE_LEFT";
87    protected static final String VALUE_FEMALE_ALWAYS_LEFT = "FEMALE_ALWAYS_LEFT";
88    protected static final String VALUE_MALE_LEFT = "MALE_LEFT";
89    protected static final String VALUE_MALE_ALWAYS_LEFT = "MALE_ALWAYS_LEFT";
90    
91    //// Section 'Minimum Distances'
92    protected static final String SECTION_DISTANCES = "DISTANCES";
93    // Section 'Minimum Distances' items
94    protected static final String TITLE_SIBLING_DISTANCES = "SIBLING_DISTANCES";
95    protected static final String ITEM_HORIZONTAL_SPACING = "HORIZONTAL_SPACING";
96    protected static final String ITEM_NODE_TO_NODE_DISTANCE = "NODE_TO_NODE_DISTANCE";
97    protected static final String TITLE_GENERATION_DISTANCES = "GENERATION_DISTANCES";
98    protected static final String ITEM_VERTICAL_SPACING = "VERTICAL_SPACING";
99    protected static final String ITEM_MINIMUM_LAYER_DISTANCE = "MINIMUM_LAYER_DISTANCE";
100   protected static final String ITEM_MINIMUM_FIRST_SEGMENT = "MINIMUM_FIRST_SEGMENT";
101   protected static final String ITEM_MINIMUM_LAST_SEGMENT = "MINIMUM_LAST_SEGMENT";
102   
103   // Advanced layout properties
104   //// Section 'Advanced Layout'
105   protected static final String SECTION_ADVANCED_LAYOUT = "ADVANCED_LAYOUT";
106   // Section 'Advanced Layout' items
107   protected static final String ITEM_USE_COMPONENT_LAYOUTER = "USE_COMPONENT_LAYOUTER";
108   protected static final String TITLE_COMPONENT_LAYOUTER = "COMPONENT_LAYOUTER";
109   protected static final String ITEM_COMPONENT_DISTANCE = "COMPONENT_DISTANCE";
110   protected static final String ITEM_COMPONENT_STYLE = "COMPONENT_STYLE";
111   protected static final String VALUE_COMPONENT_STYLE_NONE = "COMPONENT_STYLE_NONE";
112   protected static final String VALUE_COMPONENT_STYLE_ROWS = "COMPONENT_STYLE_ROWS";
113   protected static final String VALUE_COMPONENT_STYLE_SINGLE_ROW = "COMPONENT_STYLE_SINGLE_ROW";
114   protected static final String VALUE_COMPONENT_STYLE_SINGLE_COLUMN = "COMPONENT_STYLE_SINGLE_COLUMN";
115   protected static final String VALUE_COMPONENT_STYLE_PACKED_RECTANGLE = "COMPONENT_STYLE_PACKED_RECTANGLE";
116   protected static final String VALUE_COMPONENT_STYLE_PACKED_COMPACT_RECTANGLE = "COMPONENT_STYLE_PACKED_COMPACT_RECTANGLE";
117   protected static final String VALUE_COMPONENT_STYLE_PACKED_CIRCLE = "COMPONENT_STYLE_PACKED_CIRCLE";
118   protected static final String VALUE_COMPONENT_STYLE_PACKED_COMPACT_CIRCLE = "COMPONENT_STYLE_PACKED_COMPACT_CIRCLE";
119   
120   // data provider delete flag
121   private boolean isFamilyDPAddedByModule = false;
122 
123   /**
124    * Creates an instance of this module.
125    */
126   public FamilyTreeLayoutModule() {
127     super(MODULE_FAMILY_TREE_LAYOUTER);
128   }
129 
130   /**
131    * Creates an OptionHandler and adds the option items used by this module.
132    * @return the created <code>OptionHandler</code> providing module related options
133    */
134   protected OptionHandler createOptionHandler() {
135     final OptionHandler options = new OptionHandler(getModuleName());
136     final ConstraintManager optionConstraints = new ConstraintManager(options);
137 
138     //// Section 'Distinguish Node Types'
139     options.useSection(SECTION_FAMILY_PROPERTIES);
140     // Populate section
141     options.addColor(ITEM_FAMILY_COLOR, Color.black, true);
142     options.addColor(ITEM_MALE_COLOR, new Color(0xFFCC99), true);
143     options.addColor(ITEM_FEMALE_COLOR, new Color(0xCCCCFF), true);
144 
145     //// Section 'Layout'
146     options.useSection(SECTION_LAYOUT);
147     // Populate section
148     options.addEnum(ITEM_ORIENTATION, new String[]{
149         VALUE_TOP_TO_BOTTOM,
150         VALUE_LEFT_TO_RIGHT,
151         VALUE_BOTTOM_TO_TOP,
152         VALUE_RIGHT_TO_LEFT
153     }, 0);
154     options.addBool(ITEM_SINGLE_DIRECT_BELOW, true);
155     options.addBool(ITEM_FAMILIES_ALWAYS_BELOW, false);
156     options.addEnum(ITEM_NODE_ALIGNMENT, new String[]{
157         VALUE_NODE_ALIGN_TOP,
158         VALUE_NODE_ALIGN_CENTER,
159         VALUE_NODE_ALIGN_BOTTOM
160     }, 0);
161     options.addEnum(ITEM_SORT_BY_SEX, new String[]{
162         VALUE_DO_NOT_SORT,
163         VALUE_FEMALE_LEFT,
164         VALUE_FEMALE_ALWAYS_LEFT,
165         VALUE_MALE_LEFT,
166         VALUE_MALE_ALWAYS_LEFT
167     }, 0);
168     
169     //// Section 'Minimum distances'
170     options.useSection(SECTION_DISTANCES);
171     // Group 'In The Same Generation'
172     final OptionGroup siblingsGroup = new OptionGroup();
173     siblingsGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_SIBLING_DISTANCES);
174     // Populate group
175     siblingsGroup.addItem(options.addDouble(ITEM_HORIZONTAL_SPACING, 40, 0, 400));
176     siblingsGroup.addItem(options.addDouble(ITEM_NODE_TO_NODE_DISTANCE, 40, 0, 400));
177     // Group 'Between Generations'
178     final OptionGroup generationsGroup = new OptionGroup();
179     generationsGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_GENERATION_DISTANCES);
180     // Populate group
181     generationsGroup.addItem(options.addDouble(ITEM_VERTICAL_SPACING, 10, 0, 100));
182     generationsGroup.addItem(options.addDouble(ITEM_MINIMUM_LAYER_DISTANCE, 40, 0, 400));
183     generationsGroup.addItem(options.addDouble(ITEM_MINIMUM_FIRST_SEGMENT, 40, 0, 400));
184     generationsGroup.addItem(options.addDouble(ITEM_MINIMUM_LAST_SEGMENT, 20, 0, 400));
185 
186     //// Section 'Advanced Layout'
187     options.useSection(SECTION_ADVANCED_LAYOUT);
188     // Populate section
189     final OptionItem itemUseComponentLayouter = options.addBool(ITEM_USE_COMPONENT_LAYOUTER, true);
190     // Group 'Components'
191     final OptionGroup componentsGroup = new OptionGroup();
192     componentsGroup.setAttribute(OptionGroup.ATTRIBUTE_TITLE, TITLE_COMPONENT_LAYOUTER);
193     // Populate group
194     final OptionItem itemComponentDistance = componentsGroup.addItem(options.addInt(ITEM_COMPONENT_DISTANCE, 40));
195     componentsGroup.addItem(
196         options.addEnum(ITEM_COMPONENT_STYLE, new String[]{
197             VALUE_COMPONENT_STYLE_NONE,
198             VALUE_COMPONENT_STYLE_ROWS,
199             VALUE_COMPONENT_STYLE_SINGLE_ROW,
200             VALUE_COMPONENT_STYLE_SINGLE_COLUMN,
201             VALUE_COMPONENT_STYLE_PACKED_RECTANGLE,
202             VALUE_COMPONENT_STYLE_PACKED_COMPACT_RECTANGLE,
203             VALUE_COMPONENT_STYLE_PACKED_CIRCLE,
204             VALUE_COMPONENT_STYLE_PACKED_COMPACT_CIRCLE,
205         }, 1));
206     // Enable/disable items depending on specific values
207     optionConstraints.setEnabledOnValueEquals(itemUseComponentLayouter, Boolean.TRUE, itemComponentDistance);
208 
209     return options;
210   }
211 
212   /**
213    * Main module execution routine.
214    * Launches the module's underlying algorithm on the module's graph based on user options.
215    */
216   protected void mainrun() {
217     final FamilyTreeLayouter familyTree = new FamilyTreeLayouter();
218 
219     final OptionHandler options = getOptionHandler();
220     configure(familyTree, options);
221     
222     final Graph2D graph = getGraph2D();
223     prepareGraph(graph, options);
224     try {
225       launchLayouter(familyTree);
226     } finally {
227       restoreGraph(graph, options);
228     }
229   }
230 
231   /**
232    * Prepares a <code>graph</code> depending on the given options for the
233    * module's layout algorithm.
234    * <br>
235    * Additional resources created by this method have to be freed up by calling
236    * {@link #restoreGraph(y.view.Graph2D, y.option.OptionHandler)} after
237    * layout calculation.  
238    * @param graph the graph to be prepared
239    * @param options the options for the module's layout algorithm
240    */
241   protected void prepareGraph(final Graph2D graph, final OptionHandler options) {
242     // only add a family type data provider if the graph does not already have
243     // such a data provider (e.g. FamilyTreeDemo adds this data provider outside
244     // the module)
245     isFamilyDPAddedByModule = graph.getDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE) == null;
246     if (isFamilyDPAddedByModule) {
247       /* Create the family info if not already existing */
248       graph.addDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE, new DataProviderAdapter() {
249         public Object get(Object o) {
250           final NodeRealizer nr = graph.getRealizer((Node) o);
251           final Color nodeColor = nr.getFillColor();
252           if (nodeColor != null && nodeColor.equals(options.get(ITEM_MALE_COLOR))) {
253             return FamilyTreeLayouter.TYPE_MALE;
254           }
255           if (nodeColor != null && nodeColor.equals(options.get(ITEM_FEMALE_COLOR))) {
256             return FamilyTreeLayouter.TYPE_FEMALE;
257           }
258           if (nodeColor != null && nodeColor.equals(options.get(ITEM_FAMILY_COLOR))) {
259             return FamilyTreeLayouter.TYPE_FAMILY;
260           }
261           return null;
262         }
263       });
264     }
265   }
266 
267   /**
268    * Restores the given <code>graph</code> by freeing up resources created by
269    * {@link #prepareGraph(y.view.Graph2D, y.option.OptionHandler)}.
270    * @param graph the graph for which <code>prepareGraph</code> has been called
271    * @param options the options for the module's layout algorithm
272    */
273   protected void restoreGraph(final Graph2D graph, final OptionHandler options) {
274     // remove the data provider if it was added by this module
275     if (isFamilyDPAddedByModule) {
276       isFamilyDPAddedByModule = false;
277       graph.removeDataProvider(FamilyTreeLayouter.DP_KEY_FAMILY_TYPE);
278     }
279   }
280 
281   /**
282    * Configures the module's layout algorithm according to the given options.
283    * @param familyTree the <code>FamilyTreeLayouter</code> to be configured
284    * @param options the layout options to set
285    */
286   protected void configure(final FamilyTreeLayouter familyTree, final OptionHandler options) {
287     familyTree.setSpacingBetweenFamilyMembers(options.getDouble(ITEM_HORIZONTAL_SPACING));
288     familyTree.setOffsetForFamilyNodes(options.getDouble(ITEM_VERTICAL_SPACING));
289     familyTree.setFamilyNodesAlwaysBelow(options.getBool(ITEM_FAMILIES_ALWAYS_BELOW));
290     familyTree.setPartnerlessBelow(options.getBool(ITEM_SINGLE_DIRECT_BELOW));
291 
292     /* Sets the orientation */
293 //    ((OrientationLayouter) layouter.getOrientationLayouter()).setMirrorMask(
294 //        OrientationLayouter.MIRROR_BOTTOM_TO_TOP | OrientationLayouter.MIRROR_LEFT_TO_RIGHT);
295     if (options.get(ITEM_ORIENTATION).equals(VALUE_TOP_TO_BOTTOM)) {
296       familyTree.setLayoutOrientation(LayoutOrientation.TOP_TO_BOTTOM);
297     } else if (options.get(ITEM_ORIENTATION).equals(VALUE_LEFT_TO_RIGHT)) {
298       familyTree.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
299     } else if (options.get(ITEM_ORIENTATION).equals(VALUE_BOTTOM_TO_TOP)) {
300       familyTree.setLayoutOrientation(LayoutOrientation.BOTTOM_TO_TOP);
301     } else if (options.get(ITEM_ORIENTATION).equals(VALUE_RIGHT_TO_LEFT)) {
302       familyTree.setLayoutOrientation(LayoutOrientation.RIGHT_TO_LEFT);
303     }
304 
305     /* Advanced */
306 
307     /* Component Layouter */
308     familyTree.setComponentLayouterEnabled(options.getBool(ITEM_USE_COMPONENT_LAYOUTER));
309     final ComponentLayouter cl = (ComponentLayouter) familyTree.getComponentLayouter();
310     cl.setComponentSpacing(options.getInt(ITEM_COMPONENT_DISTANCE));
311     if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_NONE)) {
312       cl.setStyle(ComponentLayouter.STYLE_NONE);
313     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_PACKED_CIRCLE)) {
314       cl.setStyle(ComponentLayouter.STYLE_PACKED_CIRCLE);
315     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_PACKED_COMPACT_CIRCLE)) {
316       cl.setStyle(ComponentLayouter.STYLE_PACKED_COMPACT_CIRCLE);
317     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_PACKED_COMPACT_RECTANGLE)) {
318       cl.setStyle(ComponentLayouter.STYLE_PACKED_COMPACT_RECTANGLE);
319     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_PACKED_RECTANGLE)) {
320       cl.setStyle(ComponentLayouter.STYLE_PACKED_RECTANGLE);
321     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_ROWS)) {
322       cl.setStyle(ComponentLayouter.STYLE_MULTI_ROWS);
323     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_SINGLE_COLUMN)) {
324       cl.setStyle(ComponentLayouter.STYLE_SINGLE_COLUMN);
325     } else if (options.get(ITEM_COMPONENT_STYLE).equals(VALUE_COMPONENT_STYLE_SINGLE_ROW)) {
326       cl.setStyle(ComponentLayouter.STYLE_SINGLE_ROW);
327     }
328 
329     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
330     familyTree.setTopLayouter(ihl);
331 
332     //disable bend reduction to get centered alignment of children
333     ((SimplexNodePlacer) ihl.getNodePlacer()).setBendReductionEnabled(false);
334 
335     /* Vertical node alignment */
336     final NodeLayoutDescriptor nld = ihl.getNodeLayoutDescriptor();
337     if (options.get(ITEM_NODE_ALIGNMENT).equals(VALUE_NODE_ALIGN_TOP)) {
338       nld.setLayerAlignment(0.0);
339       familyTree.setAlignment(FamilyTreeLayouter.ALIGN_TOP);
340     } else if (options.get(ITEM_NODE_ALIGNMENT).equals(VALUE_NODE_ALIGN_CENTER)) {
341       nld.setLayerAlignment(0.5);
342       familyTree.setAlignment(FamilyTreeLayouter.ALIGN_CENTER);
343     } else if (options.get(ITEM_NODE_ALIGNMENT).equals(VALUE_NODE_ALIGN_BOTTOM)) {
344       nld.setLayerAlignment(1.0);
345       familyTree.setAlignment(FamilyTreeLayouter.ALIGN_BOTTOM);
346     }
347 
348     ihl.setMinimumLayerDistance(options.getDouble(ITEM_MINIMUM_LAYER_DISTANCE));
349     ihl.setNodeToNodeDistance(options.getDouble(ITEM_NODE_TO_NODE_DISTANCE));
350     final EdgeLayoutDescriptor eld = ihl.getEdgeLayoutDescriptor();
351     eld.setMinimumFirstSegmentLength(options.getDouble(ITEM_MINIMUM_FIRST_SEGMENT));
352     eld.setMinimumLastSegmentLength(options.getDouble(ITEM_MINIMUM_LAST_SEGMENT));
353     eld.setRoutingStyle(new RoutingStyle(RoutingStyle.EDGE_STYLE_ORTHOGONAL));
354 
355     if (options.get(ITEM_SORT_BY_SEX).equals(VALUE_DO_NOT_SORT)) {
356       familyTree.setSortFamilyMembers(FamilyTreeLayouter.DO_NOT_SORT_BY_SEX);
357     } else if (options.get(ITEM_SORT_BY_SEX).equals(VALUE_FEMALE_LEFT)) {
358       familyTree.setSortFamilyMembers(FamilyTreeLayouter.FEMALE_FIRST);
359     } else if (options.get(ITEM_SORT_BY_SEX).equals(VALUE_FEMALE_ALWAYS_LEFT)) {
360       familyTree.setSortFamilyMembers(FamilyTreeLayouter.FEMALE_ALWAYS_FIRST);
361     } else if (options.get(ITEM_SORT_BY_SEX).equals(VALUE_MALE_LEFT)) {
362       familyTree.setSortFamilyMembers(FamilyTreeLayouter.MALE_FIRST);
363     } else if (options.get(ITEM_SORT_BY_SEX).equals(VALUE_MALE_ALWAYS_LEFT)) {
364       familyTree.setSortFamilyMembers(FamilyTreeLayouter.MALE_ALWAYS_FIRST);
365     }
366   }
367 }
368