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.flowchart.layout;
29  
30  import y.base.Edge;
31  import y.base.NodeCursor;
32  import y.geom.LineSegment;
33  import y.geom.YLineSegmentCursor;
34  import y.geom.YPoint;
35  import y.geom.YPointPath;
36  import y.geom.YRectangle;
37  import y.layout.DiscreteNodeLabelModel;
38  import y.layout.EdgeLabelLayout;
39  import y.layout.LabelCandidate;
40  import y.layout.LayoutGraph;
41  import y.layout.NodeLabelLayout;
42  import y.layout.NodeLabelModel;
43  import y.layout.ProfitModel;
44  
45  import java.util.HashMap;
46  import java.util.Map;
47  
48  /**
49   * A label profit model for the {@link FlowchartLayouter}.
50   */
51  class FlowchartLabelProfitModel implements ProfitModel {
52    private static final double MIN_PREFERRED_PLACEMENT_DISTANCE = 3.0;
53    private static final double MAX_PREFERRED_PLACEMENT_DISTANCE = 40.0;
54  
55    private final LayoutGraph graph;
56    private final Map label2OriginalBox;
57  
58    FlowchartLabelProfitModel(LayoutGraph graph) {
59      this.graph = graph;
60      this.label2OriginalBox = new HashMap();
61  
62      for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
63        final NodeLabelLayout[] nll = graph.getLabelLayout(nc.node());
64  
65        for (int i = 0; i < nll.length; i++) {
66          final NodeLabelModel nlm = nll[i].getLabelModel();
67          if (nlm instanceof DiscreteNodeLabelModel) {
68            label2OriginalBox.put(nll[i], nll[i].getModelParameter());
69          }
70        }
71      }
72    }
73  
74    public double getProfit(LabelCandidate candidate) {
75      return candidate.getOwner() instanceof EdgeLabelLayout ?
76          calcEdgeLabelProfit(graph, candidate) :
77          calcNodeLabelProfit(candidate);
78    }
79  
80    private double calcNodeLabelProfit(LabelCandidate candidate) {
81      final NodeLabelLayout nl = (NodeLabelLayout) candidate.getOwner();
82  
83      if (nl.getLabelModel() instanceof DiscreteNodeLabelModel) {
84        final int pos = ((Number) candidate.getParameter()).intValue();
85        final int originalPos = ((Number) label2OriginalBox.get(nl)).intValue();
86  
87        if (pos == originalPos) {
88          return 1.0;
89        } else {
90          switch (pos) {
91            case DiscreteNodeLabelModel.NORTH:
92            case DiscreteNodeLabelModel.SOUTH:
93            case DiscreteNodeLabelModel.WEST:
94            case DiscreteNodeLabelModel.EAST:
95              return 0.95;
96            case DiscreteNodeLabelModel.NORTH_EAST:
97            case DiscreteNodeLabelModel.NORTH_WEST:
98            case DiscreteNodeLabelModel.SOUTH_EAST:
99            case DiscreteNodeLabelModel.SOUTH_WEST:
100             return 0.9;
101           default:
102             return 0.0;
103         }
104       }
105     } else {
106       return 0.0;
107     }
108   }
109 
110   private static double calcEdgeLabelProfit(LayoutGraph g, LabelCandidate candidate) {
111     final Edge e = g.getFeature((EdgeLabelLayout) candidate.getOwner());
112 
113     if (FlowchartElements.isRegularEdge(g, e)) {
114       final double eLength = calcPathLength(g.getPath(e));
115       final double maxPreferredPlacementDistance = Math.max(MAX_PREFERRED_PLACEMENT_DISTANCE, eLength * 0.2);
116       final double minDistToSource = getDistance(candidate.getBoundingBox(), g.getSourcePointAbs(e));
117 
118       if (minDistToSource > maxPreferredPlacementDistance) {
119         return 0.0;
120       } else if (minDistToSource < MIN_PREFERRED_PLACEMENT_DISTANCE) {
121         return 0.5;
122       } else {
123         return 1.0 - (minDistToSource / maxPreferredPlacementDistance);
124       }
125     } else {
126       return 0.0;
127     }
128   }
129 
130   static double calcPathLength(final YPointPath path) {
131     double length = 0.0;
132     for (YLineSegmentCursor cur = path.lineSegments(); cur.ok(); cur.next()) {
133       length += cur.lineSegment().length();
134     }
135     return length;
136   }
137 
138   static double getDistance(YRectangle r, YPoint q) {
139     if (r.contains(q)) {
140       return 0.0;
141     } else {
142       //determine corners of the rectangle
143       YPoint upperLeft = r.getLocation();
144       YPoint lowerLeft = new YPoint(upperLeft.x, upperLeft.y + r.getHeight());
145       YPoint lowerRight = new YPoint(lowerLeft.x + r.getWidth(), lowerLeft.y);
146       YPoint upperRight = new YPoint(lowerRight.x, upperLeft.y);
147 
148       //determine minDist to one of the four border segments
149       double minDist = Double.MAX_VALUE;
150       LineSegment rLeftSeg = new LineSegment(upperLeft, lowerLeft);
151       minDist = Math.min(minDist, getDistance(rLeftSeg, q));
152       LineSegment rRightSeg = new LineSegment(upperRight, lowerRight);
153       minDist = Math.min(minDist, getDistance(rRightSeg, q));
154       LineSegment rTopSeg = new LineSegment(upperLeft, upperRight);
155       minDist = Math.min(minDist, getDistance(rTopSeg, q));
156       LineSegment rBottomSeg = new LineSegment(lowerLeft, lowerRight);
157       minDist = Math.min(minDist, getDistance(rBottomSeg, q));
158       return minDist;
159     }
160   }
161 
162   static double getDistance(LineSegment line, YPoint q) {
163     final double x1 = line.getFirstEndPoint().x;
164     final double y1 = line.getFirstEndPoint().y;
165 
166     //adjust vectors relative to first endpoints of line
167     final double x2 = line.getSecondEndPoint().x - x1;
168     final double y2 = line.getSecondEndPoint().y - y1;
169     double pX = q.getX() - x1;
170     double pY = q.getY() - y1;
171 
172     //calculate distance
173     final double projSquaredDist;
174     if ((pX * x2 + pY * y2) <= 0.0) {
175       projSquaredDist = 0.0;
176     } else {
177       pX = x2 - pX;
178       pY = y2 - pY;
179       final double tmp = pX * x2 + pY * y2;
180       projSquaredDist = tmp <= 0.0 ? 0.0 : tmp * tmp / (x2 * x2 + y2 * y2);
181     }
182 
183     final double squaredDist = pX * pX + pY * pY - projSquaredDist;
184     return squaredDist < 0.0 ? 0.0 : Math.sqrt(squaredDist);
185   }
186 }
187