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.mindmap;
29  
30  import y.view.BendList;
31  import y.view.EdgeRealizer;
32  import y.view.GenericEdgeRealizer;
33  import y.view.NodeRealizer;
34  
35  import java.awt.Color;
36  import java.awt.Graphics2D;
37  import java.awt.geom.GeneralPath;
38  import java.awt.geom.PathIterator;
39  
40  /**
41   * Implementation of {@link y.view.GenericEdgeRealizer.Painter} interface.
42   * Draws edge with gradient from source fill color to target fill color.
43   * If source line width and target line width differs, it smoothly changes the edge width
44   */
45  class GradientEdgePainter implements GenericEdgeRealizer.Painter {
46  
47    /**
48     * We don't want to change the appearance, paintSloppy just calls paint
49     * @param context the EdgeRealizer context that is used for the painting
50     * @param bends the list of bends that the current context holds
51     * @param path the GeneralPath instance that is associated with the current context
52     * @param gfx current <code>Graphics2D</code>
53     * @param selected whether the edge should be painted in selected state
54     */
55    public void paintSloppy(final EdgeRealizer context, final BendList bends, final GeneralPath path,
56                            final Graphics2D gfx, final boolean selected) {
57      paintImpl(context, path, gfx, 2);
58    }
59  
60    /**
61     * Paint an edge with gradient and changing line width.
62     * Fill color of source and target used for start and endcolor,
63     * LineType of source and target used for line width
64     * @param context the EdgeRealizer context that is used for the painting
65     * @param bends the list of bends that the current context holds
66     * @param path the GeneralPath instance that is associated with the current context
67     * @param gfx current <code>Graphics2D</code>
68     * @param selected whether the edge should be painted in selected state
69     */
70    public void paint(final EdgeRealizer context, final BendList bends, final GeneralPath path, final Graphics2D gfx,
71                      final boolean selected) {
72      paintImpl(context, path, gfx, 30);
73    }
74  
75    private void paintImpl(final EdgeRealizer context, final GeneralPath path, final Graphics2D gfx, final int totalParts) {
76      final boolean isRoot = ViewModel.instance.isRoot(context.getSourceRealizer().getNode());
77  
78      //determine start color, end color, start line width and end line width
79      Color currentColor;
80      NodeRealizer nodeRealizer = context.getTargetRealizer();
81      final Color endColor = nodeRealizer.getFillColor();
82      final double endLineWidth = nodeRealizer.getLineType().getLineWidth();
83      double currentLineWidth;
84      if (!isRoot) {
85        nodeRealizer = context.getSourceRealizer();
86        currentColor = nodeRealizer.getFillColor();
87        currentLineWidth = nodeRealizer.getLineType().getLineWidth();
88      } else {
89        currentColor = endColor;
90        currentLineWidth = 15.0;
91      }
92  
93      gfx.setColor(currentColor);
94  
95      //determine number of segments this edge contains of and the distance between source and target
96      double dist = 0;//calcDist(start, last);
97      double[] seg = new double[6];
98      int count = 0;
99      PathIterator pi = path.getPathIterator(null);
100     pi.currentSegment(seg);
101     pi.next();
102     for (; !pi.isDone(); pi.next()) {
103       final double oldX = seg[0];
104       final double oldY = seg[1];
105       pi.currentSegment(seg);
106       dist += calcDist(oldX,oldY,seg[0],seg[1]);
107       count++;
108     }
109     //a bezier curve consists of some very small segments and a few very large segments.
110     //to make the gradient smooth, large segments are divided into smaller parts.
111     //this way, the painter could also be used for straight edges or any other type of edges.
112     final double maxDist = dist / totalParts;
113     //determine the necessary change of color and line width for each step
114     final int greenStep = (endColor.getGreen() - currentColor.getGreen()) / count;
115     final int redStep = (endColor.getRed() - currentColor.getRed()) / count;
116     final int blueStep = (endColor.getBlue() - currentColor.getBlue()) / count;
117     final double lineWidthStep = (currentLineWidth - endLineWidth) / count;
118     //draw every segment as an rectangle, where the line segment is the middle line of the rectangle.
119     //xpos and ypos are the horizontal and vertical offset to get from the start and endpoint of the line
120     //segment to the corners of the rectangle
121     double angle = 0;
122     boolean first = true;
123     double[] start = new double[6];
124     double[] end = new double[2];
125     pi = path.getPathIterator(null);
126     pi.currentSegment(start);
127     pi.next();
128     final GeneralPath gp = new GeneralPath();
129     for (; !pi.isDone(); pi.next()) {
130       //calculate length of current segment
131       pi.currentSegment(seg);
132       double xdiff = seg[0] - start[0];
133       double ydiff = seg[1] - start[1];
134       dist = calcDist(seg[0],seg[1], start[0],start[1]);
135       double oldAngle = angle;
136       angle = Math.atan2(ydiff, xdiff);
137       double midAngle = (oldAngle+angle) / 2;
138       double xpos = (Math.sin(angle) * currentLineWidth * 0.5);
139       double ypos = (Math.cos(angle) * currentLineWidth * 0.5);
140       //determine if segment is to large
141       if (dist > maxDist) {
142         //Due to clipping and thick edges, the connection at the center item doesn't look
143         //good when it is very small or very large.
144         //In this case, an additional rectangle is drawn
145         if (first && isRoot) {
146           gfx.setColor(currentColor);
147           end[0] = start[0] - (xdiff/dist) * currentLineWidth;
148           end[1] = start[1] - (ydiff/dist) * currentLineWidth;
149           gfx.fill(calcLineSegment(start, end, xpos, ypos, 0, 0, 0, gp));
150         }
151         //calculate in how many parts this segment has to be split
152         final int parts = (int) Math.ceil(dist / maxDist) + 1;
153         //adjust step width
154         xdiff /= parts;
155         ydiff /= parts;
156         if (!first) {
157           //draw transition line segment, to make the curve smoother
158           double midXPos = (Math.sin(midAngle) * currentLineWidth * 0.5);
159           double midYPos = (Math.cos(midAngle) * currentLineWidth * 0.5);
160           gfx.fill(calcLineSegment(start, start, midXPos, midYPos, xdiff, ydiff, 0.5, gp));
161           gfx.setColor(currentColor);
162         }
163         for (int i = 1; i <= parts; i++) {
164           //draw line segment
165           gfx.fill(calcLineSegment(start, start, xpos, ypos, xdiff, ydiff, i, gp));
166           //adjust color and line width
167           currentColor = getNewColor(currentColor, greenStep, redStep, blueStep, parts);
168           gfx.setColor(currentColor);
169           currentLineWidth = Math.max(currentLineWidth - (lineWidthStep / parts), endLineWidth);
170           //as line width may changed, the position of the corners have slightly changed
171           xpos = (Math.sin(angle) * currentLineWidth * 0.5);
172           ypos = (Math.cos(angle) * currentLineWidth * 0.5);
173         }
174       } else {
175         gfx.setColor(currentColor);
176         if (!first) {
177           //draw transition line segment, to make the curve smoother
178           double midXPos = (Math.sin(midAngle) * currentLineWidth * 0.5);
179           double midYPos = (Math.cos(midAngle) * currentLineWidth * 0.5);
180           gfx.fill(calcLineSegment(start, start, midXPos, midYPos, xdiff, ydiff, 0.5, gp));
181         } else if (isRoot) {
182           //Due to clipping and thick edges, the connection at the center item doesn't look
183           //good when it is very small or very large.
184           //In this case, an additional rectangle is drawn
185           end[0] = start[0] - (xdiff/dist) * currentLineWidth;
186           end[1] = start[1] - (ydiff/dist) * currentLineWidth;
187           gfx.fill(calcLineSegment(start, end, xpos, ypos, 0, 0, 0, gp));
188         }
189         //draw line segment
190         gfx.fill(calcLineSegment(start, seg, xpos, ypos, 0, 0, 0, gp));
191         //adjust color and line width
192         currentColor = getNewColor(currentColor, greenStep, redStep, blueStep, 1);
193         currentLineWidth = Math.max(currentLineWidth - lineWidthStep, endLineWidth);
194       }
195       //end point is now start point
196       start[0] = seg[0];
197       start[1] = seg[1];
198       first = false;
199     }
200   }
201   /**
202    * Calculate distance between two points
203    * @param x1 x coordinate of first point
204    * @param y1 y coordinate of first point
205    * @param x2 x coordinate of second point
206    * @param y2 y coordinate of second point
207    * @return euclidean distance between two points
208    */
209   private double calcDist(final double x1,final double y1,final double x2,final double y2) {
210     final double dx = x1 - x2;
211     final double dy = y1 - y2;
212     return Math.sqrt(dx * dx + dy * dy);
213   }
214 
215   /**
216    * Calculate a line segment.
217    * A line segment is represented by a polygon that is a rotated rectangle.
218    * @param start start point of the middle line of the resulting rectangle
219    * @param end end point of the middle line of the resulting rectangle
220    * @param xpos horizontal offset to get from start/end point to an rectangle corner
221    * @param ypos vertical offset to get from start/end point to an rectangle corner
222    * @param xstep used for split line segments. Move along the line segment in horizontal direction
223    * @param ystep used for split line segments. Move along the line segment in vertical direction
224    * @param i index of current part of a split line segment.
225    * @return rotated rectangle
226    */
227   private GeneralPath calcLineSegment(final double[] start, final double[] end, final double xpos, final double ypos,
228                                       final double xstep, final double ystep, final double i, final GeneralPath gp) {
229     gp.reset();
230     gp.moveTo((float) ((start[0] + (i - 1) * xstep) - xpos), (float) ((start[1] + (i - 1) * ystep) + ypos));
231     gp.lineTo((float) ((start[0] + (i - 1) * xstep) + xpos), (float) ((start[1] + (i - 1) * ystep) - ypos));
232     gp.lineTo((float) ((end[0] + (i + 0.5) * xstep) + xpos), (float) ((end[1] + (i + 0.5) * ystep) - ypos));
233     gp.lineTo((float) ((end[0] + (i + 0.5) * xstep) - xpos), (float) ((end[1] + (i + 0.5) * ystep) + ypos));
234     return gp;
235   }
236 
237   /**
238    * Calculate the next color of a gradient edge.
239    * @param startColor the old color
240    * @param greenStep change of green part
241    * @param redStep change of red part
242    * @param blueStep change of blue part
243    * @param parts make gradient smoother
244    * @return new color
245    */
246   private Color getNewColor(final Color startColor, final int greenStep, final int redStep, final int blueStep,
247                             final int parts) {
248     return new Color(
249         Math.max(0, Math.min(255, startColor.getRed() + (redStep / parts))),
250         Math.max(0, Math.min(255, startColor.getGreen() + (greenStep / parts))),
251         Math.max(0, Math.min(255, startColor.getBlue() + (blueStep / parts))));
252   }
253 }
254