| GradientEdgePainter.java |
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