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.networkmonitoring;
29  
30  import y.geom.YRectangle;
31  import y.view.DefaultLabelConfiguration;
32  import y.view.NodeLabel;
33  import y.view.NodeRealizer;
34  import y.view.YLabel;
35  
36  import java.awt.BasicStroke;
37  import java.awt.Color;
38  import java.awt.Graphics2D;
39  import java.awt.Insets;
40  import java.awt.Shape;
41  import java.awt.Stroke;
42  import java.awt.font.FontRenderContext;
43  import java.awt.geom.Arc2D;
44  import java.awt.geom.Area;
45  import java.awt.geom.Ellipse2D;
46  import java.awt.geom.GeneralPath;
47  import java.awt.geom.Line2D;
48  import java.awt.geom.Point2D;
49  import java.awt.geom.RoundRectangle2D;
50  
51  /**
52   * Paints an info label that displays a label text and additional buttons on a bubble shaped background.
53   */
54  public class NetworkInfoLabelPainter extends DefaultLabelConfiguration {
55    private static final BasicStroke STROKE_ROUNDED_CAP = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
56  
57    private static final double STATUS_ICON_WIDTH = 20;
58    private static final double STATUS_ICON_HEIGHT = 20;
59    private static final double CLOSE_ICON_WIDTH = 10;
60    private static final double CLOSE_ICON_HEIGHT = 10;
61    private static final double STATE_CHANGE_ICON_WIDTH = 15;
62    private static final double STATE_CHANGE_ICON_HEIGHT = 15;
63    private static final double GAP = 5;
64  
65    public void paintBox(YLabel label, Graphics2D gfx, double x, double y, double width, double height) {
66      final Color oldColor = gfx.getColor();
67      final Stroke oldStroke = gfx.getStroke();
68  
69  
70      if (label instanceof NodeLabel) {
71        // calculate the bubble
72        Shape shape = new RoundRectangle2D.Double(x, y, width, height, Math.min(width / 3, 10), Math.min(height / 3, 10));
73  
74        // calculate a wedge connecting the node and the rounded rectangle around the label text
75        final NodeRealizer realizer = ((NodeLabel) label).getRealizer();
76        final double nodeCenterX = realizer.getCenterX();
77        final double nodeCenterY = realizer.getCenterY();
78        final double labelCenterX = x + width * 0.5d;
79        final double labelCenterY = y + height * 0.5d;
80  
81        // calculate the tip of the wedge
82        final Point2D intersection = new Point2D.Double();
83        realizer.findIntersection(nodeCenterX, nodeCenterY, labelCenterX, labelCenterY, intersection);
84        final double intersectionX = intersection.getX();
85        final double intersectionY = intersection.getY();
86  
87        // add the wedge to the bubble shape
88        final double dx = labelCenterX - intersectionX;
89        final double dy = labelCenterY - intersectionY;
90        final double l = Math.sqrt(dx * dx + dy * dy);
91        if (l > 0) {
92          final double size = Math.min(width, height) * 0.25;
93          final GeneralPath p = new GeneralPath();
94          p.moveTo((float) intersectionX, (float) intersectionY);
95          p.lineTo((float) (labelCenterX + dy * size / l), (float) (labelCenterY - dx * size / l));
96          p.lineTo((float) (labelCenterX - dy * size / l), (float) (labelCenterY + dx * size / l));
97          p.closePath();
98          final Area area = new Area(shape);
99          area.add(new Area(p));
100         shape = area;
101       }
102 
103       // paint the bubble using the colors of the label
104       final Color backgroundColor = label.getBackgroundColor();
105       if (backgroundColor != null) {
106         // and background
107         gfx.setColor(backgroundColor);
108         gfx.fill(shape);
109       }
110 
111       // line
112       final Color lineColor = label.getLineColor();
113       if (lineColor != null) {
114         gfx.setColor(lineColor);
115         gfx.draw(shape);
116       }
117     }
118 
119     gfx.setColor(oldColor);
120     gfx.setStroke(oldStroke);
121   }
122 
123   public void paintContent(YLabel label, Graphics2D graphics, double x, double y, double width, double height) {
124     final Color oldColor = graphics.getColor();
125     final Shape oldClip = graphics.getClip();
126     final Stroke oldStroke = graphics.getStroke();
127     final Color oldTextColor = label.getTextColor();
128 
129     try {
130       if (label instanceof NodeLabel) {
131         final NodeLabel nodeLabel = (NodeLabel) label;
132         final NetworkData networkData = NetworkMonitoringFactory.getNetworkData(
133             nodeLabel.getGraph2D().getRealizer(nodeLabel.getNode()));
134         final Insets insets = getInsets(nodeLabel);
135 
136         // paint text
137         if (!networkData.isOK()) {
138           label.setTextColor(Color.GRAY);
139         }
140         super.paintContent(label, graphics, x, y, width, height);
141 
142 
143         // paint close icon
144         final double closeX = x + width - CLOSE_ICON_WIDTH - insets.right;
145         final double closeY = y + insets.top;
146         final Ellipse2D ellipse = new Ellipse2D.Double(closeX, closeY, CLOSE_ICON_WIDTH, CLOSE_ICON_HEIGHT);
147         graphics.setColor(label.getBackgroundColor().darker());
148         graphics.fill(ellipse);
149         final Line2D line = new Line2D.Double(closeX + CLOSE_ICON_WIDTH * 0.25, closeY + CLOSE_ICON_HEIGHT * 0.25,
150             closeX + CLOSE_ICON_WIDTH * 0.75, closeY + CLOSE_ICON_HEIGHT * 0.75);
151         graphics.setColor(Color.WHITE);
152         graphics.draw(line);
153         line.setLine(closeX + CLOSE_ICON_WIDTH * 0.25, closeY + CLOSE_ICON_HEIGHT * 0.75,
154             closeX + CLOSE_ICON_WIDTH * 0.75, closeY + CLOSE_ICON_HEIGHT * 0.25);
155         graphics.draw(line);
156 
157         // paint status icon (color-coded workload or broken symbol)
158         final double iconX = x + insets.left;
159         final double iconY = y + height - STATUS_ICON_HEIGHT - insets.bottom;
160         if (networkData.isBroken()) {
161           NetworkMonitoringFactory.paintWarningSign(iconX, iconY, STATUS_ICON_WIDTH, STATUS_ICON_HEIGHT, graphics);
162         } else {
163           ellipse.setFrame(iconX, iconY, STATUS_ICON_WIDTH, STATUS_ICON_HEIGHT);
164           graphics.setColor(NetworkMonitoringFactory.getStatusColor(networkData));
165           graphics.fill(ellipse);
166           graphics.setColor(Color.WHITE);
167           graphics.draw(ellipse);
168         }
169 
170         // paint state change icon (on/off button or restore sign)
171         if (networkData.isBroken()) {
172           graphics.setStroke(STROKE_ROUNDED_CAP);
173           graphics.setColor(NetworkMonitoringFactory.COLOR_WORKLOAD_NONE);
174           final double stateChangeX = x + width - STATE_CHANGE_ICON_WIDTH - insets.right;
175           final double stateChangeY = y + height - STATE_CHANGE_ICON_HEIGHT - insets.bottom;
176           final Arc2D arc = new Arc2D.Double(stateChangeX, stateChangeY, STATE_CHANGE_ICON_WIDTH,
177               STATE_CHANGE_ICON_WIDTH, 495, 270, Arc2D.OPEN);
178           graphics.draw(arc);
179           final GeneralPath path = new GeneralPath();
180           path.moveTo((float) (stateChangeX + STATE_CHANGE_ICON_WIDTH * 0.6), (float) stateChangeY);
181           path.lineTo((float) (stateChangeX + STATE_CHANGE_ICON_WIDTH * 0.95), (float) stateChangeY);
182           path.lineTo((float) (stateChangeX + STATE_CHANGE_ICON_WIDTH * 0.75),
183               (float) (stateChangeY + STATE_CHANGE_ICON_HEIGHT * 0.3));
184           path.closePath();
185           graphics.fill(path);
186         } else {
187           if (networkData.isOK()) {
188             graphics.setColor(Color.RED);
189           } else {
190             graphics.setColor(NetworkMonitoringFactory.COLOR_WORKLOAD_NONE);
191           }
192           graphics.setStroke(STROKE_ROUNDED_CAP);
193           final double stateChangeX = x + width - STATE_CHANGE_ICON_WIDTH - insets.right;
194           final double stateChangeY = y + height - STATE_CHANGE_ICON_HEIGHT - insets.bottom;
195           final Arc2D arc = new Arc2D.Double(stateChangeX, stateChangeY, STATE_CHANGE_ICON_WIDTH, STATE_CHANGE_ICON_WIDTH, 495, 270, Arc2D.OPEN);
196           graphics.draw(arc);
197           line.setLine(stateChangeX + STATE_CHANGE_ICON_WIDTH * 0.5, stateChangeY,
198               stateChangeX + STATE_CHANGE_ICON_WIDTH * 0.5, stateChangeY + STATE_CHANGE_ICON_HEIGHT * 0.5);
199           graphics.draw(line);
200         }
201       }
202     } finally {
203       graphics.setColor(oldColor);
204       graphics.setClip(oldClip);
205       graphics.setStroke(oldStroke);
206       label.setTextColor(oldTextColor);
207     }
208   }
209 
210   /**
211    * Overwritten to include the space for the buttons into the labels size.
212    */
213   public void calculateContentSize(YLabel label, FontRenderContext frc) {
214     super.calculateContentSize(label, frc);
215     if (label instanceof NodeLabel) {
216       final NodeLabel nodeLabel = (NodeLabel) label;
217       final Insets insets = getInsets(nodeLabel);
218       final double contentWidth = Math.max(nodeLabel.getContentWidth(),
219           STATUS_ICON_WIDTH + insets.left + insets.right + STATE_CHANGE_ICON_WIDTH + GAP) + GAP + CLOSE_ICON_WIDTH;
220       final double contentHeight = label.getContentHeight() + GAP + Math.max(STATUS_ICON_HEIGHT, STATE_CHANGE_ICON_HEIGHT);
221       label.setContentSize(contentWidth, contentHeight);
222     }
223   }
224 
225   /**
226    * Determines whether or not the given coordinates are contained in the area of the label's close icon.
227    */
228   public static boolean hitsCloseIcon(NodeLabel label, double x, double y) {
229     final YRectangle box = label.getBox();
230     final Insets insets = getInsets(label);
231     final double closeX = box.getX() + box.getWidth() - CLOSE_ICON_WIDTH - insets.right;
232     final double closeY = box.getY() + insets.top;
233     return x > closeX && x < closeX + CLOSE_ICON_WIDTH
234         && y > closeY && y < closeY + CLOSE_ICON_HEIGHT;
235   }
236 
237   /**
238    * Determines whether or not the given coordinates are contained in the area of the label's state change icon.
239    */
240   public static boolean hitsStateChangeIcon(NodeLabel label, double x, double y) {
241     final YRectangle box = label.getBox();
242     final Insets insets = getInsets(label);
243     final double closeX = box.getX() + box.getWidth() - STATE_CHANGE_ICON_WIDTH - insets.right;
244     final double closeY = box.getY() + box.getHeight() - STATE_CHANGE_ICON_HEIGHT - insets.bottom;
245     return x > closeX && x < closeX + STATE_CHANGE_ICON_WIDTH
246         && y > closeY && y < closeY + STATE_CHANGE_ICON_HEIGHT;
247   }
248 
249   /**
250    * Returns the insets of the given label or {@link YLabel#defaultInsets default insets} if there are no insets set for
251    * this label.
252    */
253   private static Insets getInsets(NodeLabel label) {
254     return label.getInsets() != null ? label.getInsets() : YLabel.defaultInsets;
255   }
256 }
257