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.isometry;
29  
30  import y.base.DataProvider;
31  import y.base.Edge;
32  import y.base.EdgeCursor;
33  import y.base.EdgeMap;
34  import y.base.Node;
35  import y.base.NodeCursor;
36  import y.geom.OrientedRectangle;
37  import y.geom.YDimension;
38  import y.geom.YInsets;
39  import y.geom.YPoint;
40  import y.layout.AbstractLayoutStage;
41  import y.layout.EdgeLabelLayout;
42  import y.layout.EdgeLayout;
43  import y.layout.LabelLayoutData;
44  import y.layout.LayoutGraph;
45  import y.layout.Layouter;
46  import y.layout.NodeLabelLayout;
47  import y.layout.NodeLayout;
48  import y.layout.grouping.GroupingKeys;
49  import y.util.DataProviderAdapter;
50  import y.util.Maps;
51  
52  import java.awt.geom.Rectangle2D;
53  
54  /**
55   * A {@link y.layout.LayoutStage} that transforms the graph to layout space before layout calculation is done and
56   * transforms the graph back to the view space afterwards. The layout space is base area of the isometric space. The
57   * view space contains the projection of the isometric space.
58   */
59  class IsometryTransformationLayoutStage extends AbstractLayoutStage {
60  
61    /**
62     * {@link DataProvider} key used to store {@link IsometryData  transformation data} to transform sizes and positions
63     * of nodes to the layout space and the view space.
64     */
65    public static final String TRANSFORMATION_DATA_DPKEY = "com.yworks.isometry.transformation_data_dpkey";
66  
67    private static final int GROUP_NODE_INSET = 20;
68  
69    private boolean fromSketchMode;
70  
71    public IsometryTransformationLayoutStage(final Layouter coreLayouter) {
72      this(coreLayouter, false);
73    }
74  
75    public IsometryTransformationLayoutStage(final Layouter coreLayouter, final boolean fromSketchMode) {
76      super(coreLayouter);
77      this.fromSketchMode = fromSketchMode;
78    }
79  
80    public boolean canLayout(final LayoutGraph graph) {
81      return canLayoutCore(graph);
82    }
83  
84    /**
85     * Transforms the graph to the layout space, lay it out using the core layouter and transforms the result back into
86     * the view space.
87     *
88     * @param graph the graph to lay out
89     */
90    public void doLayout(final LayoutGraph graph) {
91      // Since our group node configuration does not provide an AutoBoundsFeature instance, the group node insets will not
92      // be passed to the layout graph by the Graph2DLayoutExecutor. Therefore, we are set here an appropriate data
93      // provider manually.
94      final DataProvider oldGroupNodeInsets = graph.getDataProvider(GroupingKeys.GROUP_NODE_INSETS_DPKEY);
95      graph.addDataProvider(
96          GroupingKeys.GROUP_NODE_INSETS_DPKEY, new DataProviderAdapter() {
97        public Object get(final Object dataHolder) {
98          double labelHeight = 0;
99          if (dataHolder instanceof Node) {
100           final Node node = (Node) dataHolder;
101           final NodeLabelLayout[] labels = graph.getNodeLabelLayout(node);
102           labelHeight = labels[0].getBox().getHeight();
103         }
104         return new YInsets(GROUP_NODE_INSET, GROUP_NODE_INSET, GROUP_NODE_INSET + labelHeight, GROUP_NODE_INSET);
105       }
106     });
107 
108     /**
109      * To assure that a group node is always wide enough to contain its label and group state icon a minimum node size
110      * is calculated for each group node.
111      */
112     final DataProvider oldMinimumNodeSizes = graph.getDataProvider(GroupingKeys.MINIMUM_NODE_SIZE_DPKEY);
113     graph.addDataProvider(GroupingKeys.MINIMUM_NODE_SIZE_DPKEY, new DataProviderAdapter() {
114       public Object get(Object dataHolder) {
115         if (dataHolder instanceof Node) {
116           Node node = (Node) dataHolder;
117           final NodeLabelLayout label = graph.getNodeLabelLayout(node)[0];
118           return new YDimension(
119               (int) label.getBox().getWidth() + IsometryGroupPainter.ICON_WIDTH + IsometryGroupPainter.ICON_GAP * 2, 0);
120         }
121         return null;
122       }
123     });
124 
125     // Transform the graph to the layout space.
126     transformGraph(graph, false, isFromSketchMode());
127 
128     // Calculate the layout.
129     doLayoutCore(graph);
130 
131     // Transform the graph back to the view space.
132     transformGraph(graph, true, isFromSketchMode());
133 
134     // Restore the original group node insets and minimum size provider.
135     graph.removeDataProvider(GroupingKeys.GROUP_NODE_INSETS_DPKEY);
136     if (oldGroupNodeInsets != null) {
137       graph.addDataProvider(GroupingKeys.GROUP_NODE_INSETS_DPKEY, oldGroupNodeInsets);
138     }
139     graph.removeDataProvider(GroupingKeys.MINIMUM_NODE_SIZE_DPKEY);
140     if (oldMinimumNodeSizes != null) {
141       graph.addDataProvider(GroupingKeys.MINIMUM_NODE_SIZE_DPKEY, oldMinimumNodeSizes);
142     }
143   }
144 
145   /**
146    * Transforms the all edge points, node positions and sizes to the view or layout space.
147    *
148    * @param graph  the graph to transform
149    * @param toView <code>true</code> to transform the given point to the view space, <code>false</code> to the layout
150    *               space
151    */
152   private static void transformGraph(final LayoutGraph graph, final boolean toView, final boolean fromSketchMode) {
153     // The transformation changes the size of the nodes. To avoid that this changes the position of the source and
154     // target points of the edges, they are stored before the transformation and restored afterwards.
155     final EdgeMap sourcePoints = Maps.createHashedEdgeMap();
156     final EdgeMap targetPoints = Maps.createHashedEdgeMap();
157     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
158       final Edge edge = ec.edge();
159       sourcePoints.set(edge, graph.getSourcePointAbs(edge));
160       targetPoints.set(edge, graph.getTargetPointAbs(edge));
161     }
162 
163     final double[] corners = new double[16];
164     final Rectangle2D.Double bounds = new Rectangle2D.Double();
165 
166     // Transform the node sizes and locations.
167     final DataProvider transformationData = graph.getDataProvider(TRANSFORMATION_DATA_DPKEY);
168     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
169       final Node node = nc.node();
170       final NodeLayout nodeLayout = graph.getNodeLayout(node);
171       final IsometryData data = (IsometryData) transformationData.get(node);
172 
173       if (toView) {
174         final double oldWidth = nodeLayout.getWidth();
175         final double oldHeight = nodeLayout.getHeight();
176         final double oldCenterX = nodeLayout.getX() + oldWidth * 0.5;
177         final double oldCenterY = nodeLayout.getY() + oldHeight * 0.5;
178         // Store the width and height calculated by the core layouter. This is necessary for group nodes!
179         data.setWidth(oldWidth);
180         data.setDepth(oldHeight);
181 
182         data.calculateCorners(corners);
183         IsometryData.calculateViewBounds(corners, bounds);
184         final double newWidth = bounds.getWidth();
185         final double newHeight = bounds.getHeight();
186         final double newCenterX = IsometryData.toViewX(oldCenterX, oldCenterY);
187         final double newCenterY = IsometryData.toViewY(oldCenterX, oldCenterY) - data.getHeight() * 0.5;
188         nodeLayout.setSize(newWidth, newHeight);
189         nodeLayout.setLocation(newCenterX - newWidth * 0.5, newCenterY - newHeight * 0.5);
190       } else {
191         final double oldCenterX = nodeLayout.getX() + nodeLayout.getWidth() * 0.5;
192         final double oldCenterY = nodeLayout.getY() + nodeLayout.getHeight() * 0.5 + data.getHeight() * 0.5;
193         final double newCenterX = IsometryData.toLayoutX(oldCenterX, oldCenterY);
194         final double newCenterY = IsometryData.toLayoutY(oldCenterX, oldCenterY);
195         final double newWidth = data.getWidth();
196         final double newHeight = data.getDepth();
197         nodeLayout.setSize(newWidth, newHeight);
198         if (fromSketchMode) {
199           nodeLayout.setLocation(newCenterX - newWidth * 0.5, newCenterY - newHeight * 0.5);
200         }
201       }
202     }
203 
204     // Transform bends and end points for all edges in the graph.
205     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
206       final Edge edge = ec.edge();
207       final EdgeLayout edgeLayout = graph.getEdgeLayout(edge);
208       for (int i = 0; i < edgeLayout.pointCount(); i++) {
209         final YPoint point = edgeLayout.getPoint(i);
210         final YPoint transformedPoint = transformPoint(point, toView, fromSketchMode);
211         edgeLayout.setPoint(i, transformedPoint.getX(), transformedPoint.getY());
212       }
213 
214       // Restore the position of the source and target points of the edges.
215       graph.setSourcePointAbs(edge, transformPoint((YPoint) sourcePoints.get(edge), toView, fromSketchMode));
216       graph.setTargetPointAbs(edge, transformPoint((YPoint) targetPoints.get(edge), toView, fromSketchMode));
217     }
218 
219     if (toView) {
220       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
221         final Edge edge = ec.edge();
222         final DataProvider edgeLabelMap = graph.getDataProvider(LabelLayoutData.EDGE_LABEL_LAYOUT_KEY);
223         final EdgeLabelLayout[] labels = graph.getEdgeLabelLayout(edge);
224         final LabelLayoutData[] labelLayoutData = (LabelLayoutData[]) edgeLabelMap.get(edge);
225         final EdgeLayout edgeLayout = graph.getEdgeLayout(edge);
226         final NodeLayout sourceLayout = graph.getNodeLayout(edge.source());
227         final NodeLayout targetLayout = graph.getNodeLayout(edge.target());
228         if (labelLayoutData != null) {
229           for (int i = 0; i < labels.length; i++) {
230             final EdgeLabelLayout label = labels[i];
231             final LabelLayoutData labelData = labelLayoutData[i];
232             final double oldWidth = labelData.getWidth();
233             final double oldHeight = labelData.getHeight();
234             final double oldCenterX = labelData.getX() + oldWidth * 0.5;
235             final double oldCenterY = labelData.getY() + oldHeight * 0.5;
236             // Store the width and height calculated by the core layouter. This is necessary for group nodes!
237             final IsometryData data = (IsometryData) transformationData.get(label);
238             final OrientedRectangle labelBounds = labelData.getBounds();
239             data.setHorizontal(labelBounds.getUpY() == -1 || labelBounds.getUpY() == 1);
240             data.setWidth(oldWidth);
241             data.setDepth(oldHeight);
242 
243             data.calculateCorners(corners);
244             IsometryData.calculateViewBounds(corners, bounds);
245             final double newWidth = bounds.getWidth();
246             final double newHeight = bounds.getHeight();
247             final double newCenterX = IsometryData.toViewX(oldCenterX, oldCenterY);
248             final double newCenterY = IsometryData.toViewY(oldCenterX, oldCenterY) - data.getHeight() * 0.5;
249 
250             final OrientedRectangle newBounds = new OrientedRectangle(newCenterX - newWidth * 0.5,
251                 newCenterY + newHeight * 0.5, newWidth, newHeight);
252             final Object parameter = label.getLabelModel().createModelParameter(newBounds, edgeLayout, sourceLayout, targetLayout);
253             final OrientedRectangle labelPlacement = label.getLabelModel().getLabelPlacement(newBounds.getSize(), edgeLayout, sourceLayout, targetLayout, parameter);
254             label.setModelParameter(parameter);
255             label.getOrientedBox().adoptValues(labelPlacement);
256           }
257         }
258       }
259       graph.removeDataProvider(LabelLayoutData.EDGE_LABEL_LAYOUT_KEY);
260     } else {
261       final EdgeMap edgeLabelMap = Maps.createHashedEdgeMap();
262       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
263         final Edge edge = ec.edge();
264         final EdgeLabelLayout[] labels = graph.getEdgeLabelLayout(edge);
265         final LabelLayoutData[] labelLayoutData = new LabelLayoutData[labels.length];
266         for (int i = 0; i < labels.length; i++) {
267           final EdgeLabelLayout label = labels[i];
268           final IsometryData data = (IsometryData) transformationData.get(label);
269           if (!data.isHorizontal()) {
270             labelLayoutData[i] = new LabelLayoutData(data.getDepth(), data.getWidth());
271           } else {
272             labelLayoutData[i] = new LabelLayoutData(data.getWidth(), data.getDepth());
273           }
274           labelLayoutData[i].setPreferredPlacementDescriptor(label.getPreferredPlacementDescriptor());
275         }
276         edgeLabelMap.set(edge, labelLayoutData);
277       }
278       graph.addDataProvider(LabelLayoutData.EDGE_LABEL_LAYOUT_KEY, edgeLabelMap);
279     }
280   }
281 
282   /**
283    * Transforms the given point to the view or layout space.
284    *
285    * @param point  the point to transform
286    * @param toView <code>true</code> to transform the given point to the view space, <code>false</code> to the layout
287    *               space
288    */
289   private static YPoint transformPoint(final YPoint point, final boolean toView, final boolean fromSketchMode) {
290     final double x = point.getX();
291     final double y = point.getY();
292 
293     if (toView) {
294       return new YPoint(IsometryData.toViewX(x, y), IsometryData.toViewY(x, y));
295     } else {
296       if (fromSketchMode) {
297         return new YPoint(IsometryData.toLayoutX(x, y), IsometryData.toLayoutY(x, y));
298       } else {
299         return point;
300       }
301     }
302   }
303 
304   /**
305    * Determines whether or not this layout stage transforms the coordinates of the graph elements before calculating
306    * layout. This is important for incremental layout.
307    *
308    * @return <code>true</code> if graph elements get transformed before layout, <code>false</code> otherwise.
309    *
310    * @see #setFromSketchMode(boolean)
311    */
312   public boolean isFromSketchMode() {
313     return fromSketchMode;
314   }
315 
316   /**
317    * Specifies whether or not this layout stage transforms the coordinates of the graph elements before calculating
318    * layout. This is important for incremental layout.
319    *
320    * @param fromSketchMode <code>true</code> if graph elements get transformed before layout, <code>false</code>
321    *                       otherwise.
322    *
323    * @see #setFromSketchMode(boolean)
324    */
325   public void setFromSketchMode(boolean fromSketchMode) {
326     this.fromSketchMode = fromSketchMode;
327   }
328 }
329