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.view.orgchart;
29  
30  import y.algo.Trees;
31  import y.base.DataProvider;
32  import y.base.EdgeCursor;
33  import y.base.ListCell;
34  import y.base.Node;
35  import y.base.NodeCursor;
36  import y.base.NodeMap;
37  import y.base.YList;
38  import y.geom.LineSegment;
39  import y.geom.YLineSegmentCursor;
40  import y.geom.YPoint;
41  import y.geom.YPointPath;
42  import y.layout.AbstractLayoutStage;
43  import y.layout.EdgeLayout;
44  import y.layout.LayoutGraph;
45  import y.layout.Layouter;
46  import y.layout.tree.AssistantPlacer;
47  import y.layout.tree.DefaultNodePlacer;
48  import y.layout.tree.GenericTreeLayouter;
49  import y.layout.tree.LeftRightPlacer;
50  import y.layout.tree.NodePlacer;
51  import y.util.Maps;
52  
53  /**
54   * A layout algorithm for tree-structured organization charts. It allows to specify different 
55   * layout strategies for the child nodes of a node. Furthermore, it supports special placement for
56   * nodes that are marked as assistants,
57   */
58  public class OrgChartLayouter implements Layouter {
59  
60    /**
61     * Child layout specifier. The children of a node shall be arranged left to right on the same layer.
62     */
63    public static final Object CHILD_LAYOUT_SAME_LAYER = "SAME_LAYER";
64    
65    /**
66     * Child layout specifier. The children of a node shall be arranged below each other and placed left of a common bus.
67     */
68    public static final Object CHILD_LAYOUT_LEFT_BELOW = "LEFT_BELOW";
69  
70    /**
71     * Child layout specifier. The children of a node shall be arranged below each other and placed right of a common bus.
72     */
73    public static final Object CHILD_LAYOUT_RIGHT_BELOW = "RIGHT_BELOW";
74    
75    /**
76     * Child layout specifier. The children of a node shall be arranged on both sides of a common vertical bus. Children on both sides
77     * are placed below each.    
78     */
79    public static final Object CHILD_LAYOUT_BELOW = "BELOW";
80    
81    /**
82     * DataProvider key used to register a DataProvider with the input graph. For each node in the graph 
83     * the registered DataProvider returns either of {@link #CHILD_LAYOUT_BELOW}, {@link #CHILD_LAYOUT_LEFT_BELOW}, 
84     * {@link #CHILD_LAYOUT_RIGHT_BELOW}, or {@link #CHILD_LAYOUT_SAME_LAYER}.
85     */
86    public static final Object CHILD_LAYOUT_DPKEY = "OrgChartLayouter#CHILD_LAYOUT_DPKEY";
87    
88    /**
89     * DataProvider key used to register a DataProvider with the input graph. For each node in the graph 
90     * the registered DataProvider returns a boolean value that signifies whether or not the
91     * node is to be considered an assistant to its parent node. Assistants are always placed along to the left or right of the
92     * the vertical bus leaving the parent node. For non-assistant child nodes the child layout specified for the
93     * parent node will be applied.
94     */
95    public static final Object ASSISTANT_DPKEY = "OrgChartLayouter#ASSISTANT_DPKEY";
96    
97    private boolean duplicateBendsOnSharedBus = false;
98  
99  
100   /**
101    * Sets whether or not to duplicate the control points of the returned edge paths 
102    * that are placed on an path segment of another edge. For example, if an edge
103    * has the control points, [a,b,c], and a and b are placed on a shared bus, then the
104    * resulting edge path is [a,a,b,b,c]. Duplicating control points on a shared bus,
105    * allows the edge rendering facility to treat such control points differently.
106    * By default this feature is disabled.
107    */
108   public void setDuplicateBendsOnSharedBus(final boolean duplicateBendsOnSharedBus) {
109     this.duplicateBendsOnSharedBus = duplicateBendsOnSharedBus;
110   }
111 
112   /**
113    * Returns whether or not to duplicate the control points of the returned edge paths 
114    * that are placed on an path segment of another edge. For example, if an edge 
115    * has the control points, [a,b,c], and a and b are placed on a shared bus, then the
116    * resulting edge path is [a,a,b,b,c]. Duplicating control points on a shared bus,
117    * allows the edge rendering facility to treat such control points differently.
118    * By default this feature is disabled.
119    */
120   public boolean isDuplicateBendsOnSharedBus() {
121     return duplicateBendsOnSharedBus;
122   }
123 
124   /**
125    * Assigns coordinates to the elements of the input graph.
126    */
127   public void doLayout(final LayoutGraph graph) {
128     final GenericTreeLayouter gtl = new GenericTreeLayouter();
129     gtl.setGroupingSupported(true);
130     configureNodePlacers(graph);    
131     gtl.doLayout(graph);
132     if(isDuplicateBendsOnSharedBus()) {
133       final Layouter bendDuplicator = new BendDuplicatorStage(null);
134       bendDuplicator.doLayout(graph);
135     }
136   }
137   
138   /**
139    * Configures the layout algorithm using the information provided by the
140    * DataProviders registered with the keys {@link #ASSISTANT_DPKEY} and {@link #CHILD_LAYOUT_DPKEY}.   
141    */
142   protected void configureNodePlacers(final LayoutGraph graph) {
143     final DataProvider childLayoutDP = graph.getDataProvider(CHILD_LAYOUT_DPKEY);
144     final NodeMap nodePlacerMap = Maps.createHashedNodeMap();
145     if(childLayoutDP != null) {
146       graph.addDataProvider(GenericTreeLayouter.NODE_PLACER_DPKEY, nodePlacerMap);
147       for(final NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
148         final Node n = nc.node();
149         nodePlacerMap.set(n, createNodePlacer(childLayoutDP.get(n)));        
150       }
151       graph.addDataProvider(GenericTreeLayouter.NODE_PLACER_DPKEY, nodePlacerMap);
152     }
153 
154     final DataProvider assistDP = graph.getDataProvider(ASSISTANT_DPKEY);
155     for(NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
156       final Node n = nc.node();
157       if(assistDP != null && assistDP.getBool(n)) {
158         if(n.inDegree() > 0 && n.firstInEdge().source().outDegree() > 1) {
159           final AssistantPlacer placer = new AssistantPlacer();
160           final NodePlacer parentPlacer = (NodePlacer) nodePlacerMap.get(n.firstInEdge().source());
161           placer.setChildNodePlacer(parentPlacer);
162           nodePlacerMap.set(n.firstInEdge().source(), placer);
163         }
164       }
165     }
166     graph.addDataProvider(AssistantPlacer.ASSISTANT_DPKEY, assistDP);     
167   }
168   
169   /**
170    * Creates a NodePlacer for the given child layout specifier.
171    */
172   protected NodePlacer createNodePlacer(final Object childLayout) {
173     if(childLayout == CHILD_LAYOUT_LEFT_BELOW) {      
174       final DefaultNodePlacer placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_CENTER, DefaultNodePlacer.ROUTING_FORK, 20.0d, 80.d);
175       placer.setChildPlacement(DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT);
176       placer.setRootAlignment(DefaultNodePlacer.ALIGNMENT_LEADING_ON_BUS);
177       placer.setRoutingStyle(DefaultNodePlacer.ROUTING_FORK_AT_ROOT);
178       return placer;
179     }
180     else if(childLayout == CHILD_LAYOUT_RIGHT_BELOW) {
181       final DefaultNodePlacer placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_CENTER, DefaultNodePlacer.ROUTING_FORK, 20.0d, 80.d);
182       placer.setChildPlacement(DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT);
183       placer.setRootAlignment(DefaultNodePlacer.ALIGNMENT_LEADING_ON_BUS);
184       placer.setRoutingStyle(DefaultNodePlacer.ROUTING_FORK_AT_ROOT);
185       return placer;
186     }
187     else if(childLayout == CHILD_LAYOUT_BELOW) {
188       final LeftRightPlacer placer = new LeftRightPlacer();
189       placer.setPlaceLastOnBottom(false);
190       return placer;
191     }
192     else { //default
193       final DefaultNodePlacer placer = new DefaultNodePlacer();
194       placer.setChildPlacement(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD);
195       placer.setRootAlignment(DefaultNodePlacer.ALIGNMENT_MEDIAN);
196       return placer;
197     }        
198   }
199     
200   /**
201    * The input graph needs to be a tree or a collection of trees.
202    */
203   public boolean canLayout(final LayoutGraph graph) {
204     return Trees.isForest(graph); //simplified
205   }
206   
207   /**
208    * LayoutStage that duplicates bends that share a common bus.
209    */
210   static class BendDuplicatorStage extends AbstractLayoutStage {
211 
212     public BendDuplicatorStage(final Layouter coreLayouter) {
213       super(coreLayouter);
214     }
215     
216     public boolean canLayout(final LayoutGraph graph) {
217       return true;
218     }
219 
220     public void doLayout(final LayoutGraph graph) {
221       doLayoutCore(graph);
222       
223       for(final NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
224         final Node n = nc.node();
225         for(final EdgeCursor ec = n.outEdges(); ec.ok(); ec.next()) {
226           boolean lastSegmentOverlap = false;
227           final EdgeLayout er = graph.getEdgeLayout(ec.edge());
228           
229           if(er.pointCount() > 0) {
230             //last bend point
231             final YPoint bendPoint = er.getPoint(er.pointCount()-1);
232             
233             loop:for(final EdgeCursor ecc = n.outEdges(); ecc.ok(); ecc.next()) {
234               
235               if(ecc.edge() != ec.edge()) {
236                 final YPointPath path = graph.getPath(ecc.edge());
237                 for(final YLineSegmentCursor lc = path.lineSegments(); lc.ok(); lc.next()) {
238                   final LineSegment seg = lc.lineSegment();
239                   if(seg.contains(bendPoint)) {
240                     lastSegmentOverlap = true;
241                     break loop;
242                   }
243                 }
244               }
245             }      
246           }
247           
248           final YList points = graph.getPointList(ec.edge());
249           for(ListCell c = points.firstCell(); c != null; c = c.succ()) {
250             final YPoint p = (YPoint) c.getInfo();
251             if(c.succ() == null && !lastSegmentOverlap) {
252               break;
253             }
254             points.insertBefore(new YPoint(p.x,p.y), c);
255           }
256           graph.setPoints(ec.edge(), points);
257         }
258       }    
259     }
260   }
261 }
262