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.labeling;
29  
30  import y.geom.YPoint;
31  import y.geom.YVector;
32  import y.layout.PreferredPlacementDescriptor;
33  import y.view.DefaultLabelConfiguration;
34  import y.view.EdgeLabel;
35  import y.view.YLabel;
36  import y.view.YRenderingHints;
37  
38  import java.awt.Color;
39  import java.awt.Font;
40  import java.awt.GradientPaint;
41  import java.awt.Graphics2D;
42  import java.awt.Paint;
43  import java.awt.geom.Arc2D;
44  import java.awt.geom.GeneralPath;
45  import java.awt.geom.Line2D;
46  
47  /**
48   * A label painter for edge labels that visualizes the settings of the {@link PreferredPlacementDescriptor} of the label.
49   */
50  class VisualizingDescriptorLabelConfiguration extends DefaultLabelConfiguration {
51  
52    private static final Color COLOR_ROTATION = new Color(51, 102, 153);
53    private static final Color COLOR_DISTANCE = new Color(102,204,51);
54    private static final Color COLOR_PREFERRED_SIDE = new Color(204, 153, 51);
55    private static final Color COLOR_ADDITIONAL_ROTATION = new Color(153, 51, 51);
56    private static final double TWO_PI = Math.PI * 2;
57  
58    private static Font smallFont;
59  
60    public void paintContent(YLabel label, Graphics2D gfx, double x, double y, double width, double height) {
61      // We don't want the label text to be upside down, so we flip the text in case it would be
62      final boolean flipText = label.getOrientedBox().getUpY() > 0;
63      try {
64        if (flipText) {
65          gfx.rotate(Math.PI, x + width/2-2, y + height/2);
66        }
67  
68        super.paintContent(label, gfx, x, y, width, height);
69      } finally {
70        if (flipText) {
71          gfx.rotate(Math.PI, x + width/2-2, y + height/2);
72        }
73      }
74    }
75  
76    public void paintBox(YLabel label, Graphics2D gfx, double x, double y, double width, double height) {
77      if (label instanceof EdgeLabel) {
78        final Color oldColor = gfx.getColor();
79        final Paint oldPaint = gfx.getPaint();
80        final Font oldFont = gfx.getFont();
81  
82        try {
83          final double x2 = x + width;
84          final double y2 = y + height;
85          final double cx = x + width / 2;
86          final double cy = y + height / 2;
87  
88          final PreferredPlacementDescriptor placementDescriptor = ((EdgeLabel) label).getPreferredPlacementDescriptor();
89          final double dist = Math.max(1, placementDescriptor.getDistanceToEdge());
90  
91          // check if the label flow and/or the angle rotation is flipped and calculate the real rotation angle
92          final boolean labelFlowIsFlipped =
93            placementDescriptor.isRightOfEdge() && placementDescriptor.isAngleOffsetOnRightSide180();
94          final boolean angleRotationIsFlipped =
95            placementDescriptor.isRightOfEdge() && placementDescriptor.isAngleOnRightSideCounterRotating();
96  
97          double angle = placementDescriptor.getAngle();
98          if (angleRotationIsFlipped) {
99            angle = -angle;
100         }
101 
102         final double angleWithoutOffset = angle;
103         if (labelFlowIsFlipped) {
104           angle += Math.PI;
105         }
106 
107         // add a triangle as arrow head to our label bounds to visualize the direction the label points to
108         final GeneralPath labelShapePath = new GeneralPath();
109         labelShapePath.moveTo((float) x, (float) y);
110         labelShapePath.lineTo((float) (x2 - height / 4), (float) y);
111         labelShapePath.lineTo((float) x2, (float) cy);
112         labelShapePath.lineTo((float) (x2 - height / 4), (float) y2);
113         labelShapePath.lineTo((float) x, (float) y2);
114 
115         // use a gradient as background of this shape
116         gfx.setPaint(
117           new GradientPaint(
118             (float) x,
119             (float) y,
120             Color.white,
121             (float) (x + width + height / 2),
122             (float) y, COLOR_ROTATION, true));
123         gfx.fill(labelShapePath);
124 
125         YVector rightVector = new YVector(width, 0);
126         rightVector = rightVector.rotate(-angle);
127 
128         // visualize the distance between the edge segment and the center of the closest label border side
129         if (placementDescriptor.isAngleRelativeToEdgeFlow() && !placementDescriptor.isOnEdge()) {
130           // only if the angle is relative to the edge we are able to recalculate the edge orientation
131           // if the label is placed on the edge, there is no distance to the edge
132 
133           // we calculate the orientation of the edge to decide on which side of the edge the label is placed
134           double edgeAngle = -label.getOrientedBox().getAngle() - angle;
135           edgeAngle = normalizeAngle(edgeAngle);
136 
137           final boolean labelIsLeftOfEdgeInFlow = isLabelLeftOfEdgeInFlow(placementDescriptor, edgeAngle);
138           // with this information we can determine if the bottom or upper side of the label is closer to the edge
139           final boolean bottomSideIsCloserToEdge = placementDescriptor.isLeftOfEdge()
140                   ? labelIsLeftOfEdgeInFlow
141                   : labelIsLeftOfEdgeInFlow ^ labelFlowIsFlipped;
142 
143           final double yValue = bottomSideIsCloserToEdge ? y + height : y;
144           // center of the label bound side that is closest to the edge
145           final YPoint cLabelBorder = new YPoint(cx, yValue);
146 
147           final double distToEdge =
148             dist // distance from the closest corner of the label to the edge
149             + Math.abs(rightVector.getY() / 2) // distance the center of the closest side is further away from the edge then the closest corner
150             - 0.5; // half the edge width
151           YVector lineToEdge = new YVector(0, labelIsLeftOfEdgeInFlow ? distToEdge : -distToEdge);
152           lineToEdge = lineToEdge.rotate(-angle);
153 
154           // foot of a dropped perpendicular from cLabelBorder on the edge
155           final YPoint cEdge = new YPoint(cLabelBorder.getX() + lineToEdge.getX(), cLabelBorder.getY() + lineToEdge.getY());
156 
157           gfx.setColor(COLOR_DISTANCE);
158           gfx.fill(new Arc2D.Double(cLabelBorder.getX() - 2, cLabelBorder.getY() - 2, 4, 4, bottomSideIsCloserToEdge ? 0 : 180, -180, Arc2D.CHORD));
159 
160           gfx.draw(new Line2D.Double(cLabelBorder.getX(), cLabelBorder.getY(), cEdge.getX(), cEdge.getY()));
161 
162           gfx.fill(new Arc2D.Double(cEdge.getX() - 2, cEdge.getY() - 2, 4, 4, Math.toDegrees(angle), labelIsLeftOfEdgeInFlow ? 180 : -180, Arc2D.CHORD));
163         }
164 
165         // visualize how the angle was used to rotate the label
166         if (angleWithoutOffset != 0) {
167           // paint a blue line indicating where the upper bound of the label
168           // would be if the label would not be rotated
169           gfx.setColor(COLOR_ROTATION);
170           if (labelFlowIsFlipped) {
171             gfx.draw(new Line2D.Double(x, y, x - rightVector.getX(), y - rightVector.getY()));
172           } else {
173             gfx.draw(new Line2D.Double(x, y, x + rightVector.getX(), y + rightVector.getY()));
174           }
175           // indicate the angle by a little arc between this line and the upper label bound
176           gfx.draw(new Arc2D.Double(x - 10, y - 10, 20, 20, 0, angleWithoutOffset, Arc2D.OPEN));
177         }
178 
179         // visualize how the 180 degree angle offset affects the label orientation
180         if (labelFlowIsFlipped) {
181           // paint a red line indicating where the upper bounds of the label
182           // would be if the label would neither be rotated nor the label flow would be flipped
183           gfx.setColor(COLOR_ADDITIONAL_ROTATION);
184           gfx.draw(new Line2D.Double(x, y, x + rightVector.getX()/2, y + rightVector.getY()/2));
185           // indicate the 180 degree angle offset for label flow flipping using a little red arc
186           // between the red and blue lines
187           gfx.draw(new Arc2D.Double(x - 5, y - 5, 10, 10, Math.toDegrees(angle), -180, Arc2D.OPEN));
188         }
189 
190         // mark the label with the preferred side of the label
191         gfx.setColor(COLOR_PREFERRED_SIDE);
192         gfx.setFont(getSmallFont(gfx));
193         gfx.rotate(-Math.PI/2, x, y);
194         final String position = isOnAnySide(placementDescriptor) ? "Any"
195             : placementDescriptor.isLeftOfEdge() ? "Left"
196             : placementDescriptor.isRightOfEdge() ? "Right"
197             : "OnEdge";
198         gfx.drawString(position, (float) (x - height + 1), (float) (y - 1));
199         gfx.rotate(Math.PI/2, x, y);
200 
201         // draw selection box
202         if (useSelectionStyle(label, gfx)) {
203           gfx.setColor(Color.RED);
204           gfx.draw(labelShapePath);
205         }
206       } finally {
207         // restore old context
208         gfx.setFont(oldFont);
209         gfx.setColor(oldColor);
210         gfx.setPaint(oldPaint);
211       }
212     }
213   }
214 
215   private static double normalizeAngle(final double angle) {
216     return angle - TWO_PI * Math.floor(angle / TWO_PI);
217   }
218 
219 
220   private boolean isOnAnySide(final PreferredPlacementDescriptor placementDescriptor) {
221     return placementDescriptor.getSideOfEdge() ==
222            (PreferredPlacementDescriptor.PLACE_LEFT_OF_EDGE |
223             PreferredPlacementDescriptor.PLACE_ON_EDGE |
224             PreferredPlacementDescriptor.PLACE_RIGHT_OF_EDGE);
225   }
226 
227   private boolean isLabelLeftOfEdgeInFlow(final PreferredPlacementDescriptor placementDescriptor, final double edgeAngle) {
228     if (placementDescriptor.isSideRelativeToEdgeFlow()) {
229       return placementDescriptor.isLeftOfEdge();
230     } else if (0 < edgeAngle && edgeAngle < Math.PI) {
231       // edge points downwards
232       return placementDescriptor.isRightOfEdge();
233     } else if (Math.PI < edgeAngle && edgeAngle < 2 * Math.PI) {
234       // edge points upwards
235       return placementDescriptor.isLeftOfEdge();
236     } else if (edgeAngle == 0) {
237       // edge points to the right
238       return placementDescriptor.isLeftOfEdge() ^ placementDescriptor.isSideAbsoluteWithRightInNorth();
239     } else {
240       // edge points to the left
241       return placementDescriptor.isLeftOfEdge() ^ placementDescriptor.isSideAbsoluteWithLeftInNorth();
242     }
243   }
244 
245   private boolean useSelectionStyle(final YLabel label, final Graphics2D gfx) {
246     return label.isSelected() && YRenderingHints.isSelectionPaintingEnabled(gfx);
247   }
248 
249   private Font getSmallFont(final Graphics2D gfx) {
250     if (smallFont == null) {
251       final Font font = gfx.getFont();
252       smallFont = new Font(font.getName(), font.getStyle(), 6);
253     }
254     return smallFont;
255   }
256 }
257