| SectorDrawable.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.layout.radial;
29
30 import y.base.Node;
31 import y.base.NodeCursor;
32 import y.base.NodeMap;
33 import y.geom.YPoint;
34 import y.layout.radial.RadialLayouter;
35 import y.view.Drawable;
36 import y.view.Graph2D;
37 import y.view.Graph2DView;
38
39 import java.awt.BasicStroke;
40 import java.awt.Color;
41 import java.awt.Graphics2D;
42 import java.awt.Rectangle;
43 import java.awt.Stroke;
44 import java.awt.geom.Arc2D;
45 import java.awt.geom.Ellipse2D;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collections;
49 import java.util.Comparator;
50 import java.util.HashSet;
51 import java.util.Iterator;
52
53 /**
54 * Drawable to visualize the circles and sectors nodes are placed on/in by the RadialLayouter.
55 * The necessary render information are provided by RadialLayouter.NodeInfo objects filled by the RadialLayouter.
56 *
57 */
58 public final class SectorDrawable implements Drawable {
59
60 private final Graph2DView view;
61 private final NodeMap nodeInfoMap;
62 private Rectangle bounds;
63
64 private final Stroke stroke;
65 private Color[][] colors;
66 private boolean drawingCircles;
67 private boolean drawingSectors;
68
69 private double[] radii;
70 private YPoint center;
71 private ArrayList nodeInfos;
72
73 /**
74 * Creates a new drawable for circles and sectors.
75 *
76 * @param view The view containing the {@link Graph2D}.
77 * @param nodeInfoMap A node map containing {@link RadialLayouter.NodeInfo} objects for the nodes.
78 */
79 public SectorDrawable(final Graph2DView view, final NodeMap nodeInfoMap
80 ) {
81 this.view = view;
82 this.nodeInfoMap = nodeInfoMap;
83 this.bounds = new Rectangle(0, 0, 0, 0);
84
85 this.drawingCircles = true;
86 this.drawingSectors = false;
87
88 this.stroke = new BasicStroke(1.25f);
89 this.colors = new Color[][]{
90 {new Color(180, 200, 255), new Color(120, 150, 220)},
91 {new Color(180, 255, 200), new Color(120, 220, 150)}};
92 }
93
94 /**
95 * Returns whether the circles shall be drawn.
96 * <p>
97 * Default is <code>true</code>.
98 * </p>
99 *
100 * @return <code>true</code>, iff the circles shall be drawn.
101 */
102 public boolean isDrawingCircles() {
103 return drawingCircles;
104 }
105
106 /**
107 * Sets whether the circles shall be drawn.
108 *
109 * @return <code>true</code>, iff the circles shall be drawn.
110 */
111 public void setDrawingCircles(boolean drawingCircles) {
112 this.drawingCircles = drawingCircles;
113 }
114
115 /**
116 * Returns whether the sectors shall be drawn.
117 * <p>
118 * Default is <code>false</code>.
119 * </p>
120 *
121 * @return <code>true</code>, iff the sectors shall be drawn.
122 */
123 public boolean isDrawingSectors() {
124 return drawingSectors;
125 }
126
127 /**
128 * Sets whether the sectors shall be drawn.
129 *
130 * @return <code>true</code>, iff the sectors shall be drawn.
131 */
132 public void setDrawingSectors(boolean drawingSectors) {
133 this.drawingSectors = drawingSectors;
134 }
135
136 /**
137 * Returns the bounding box of the largest circle.
138 * @return The bounding box of the largest circle.
139 */
140 public Rectangle getBounds() {
141 return bounds;
142 }
143
144 /**
145 * Updates the radii and sector information based on the nodes' NodeInfo objects.
146 */
147 public void updateSectors() {
148 final Graph2D g = view.getGraph2D();
149
150 if (g.N() < 1) {
151 return;
152 }
153
154 // the common center of the circles
155 center = null;
156 // a list of RadialLayouter.NodeInfo objects drawn in the paint method
157 nodeInfos = new ArrayList(g.N());
158
159 // collect all RadialLayouter.NodeInfo objects as well as the common center and radii of the circles
160 HashSet radiiSet = new HashSet();
161 for (NodeCursor nc = g.nodes(); nc.ok(); nc.next()) {
162 Node node = nc.node();
163 RadialLayouter.NodeInfo info = (RadialLayouter.NodeInfo) nodeInfoMap.get(node);
164 if (info != null) {
165 // a layout with NodeInfos has been calculated for the current graph
166 if (center == null) { // only calculate the center once
167 YPoint nodeCenter = g.getCenter(node);
168 // RadialLayouter.NodeInfo contains the offset from the center of the circle the
169 // node is placed on to the center of the node.
170 center = new YPoint(info.getCenterOffset().getX() - nodeCenter.getX(), info.getCenterOffset().getY() - nodeCenter.getY());
171 }
172 nodeInfos.add(info);
173
174 // we collect the radii of all circles the nodes are placed on
175 radiiSet.add(new Double(info.getRadius()));
176 }
177 }
178 radii = new double[radiiSet.size()];
179
180 if (center == null) {
181 // no node data is available
182 bounds.setFrame(0, 0, 0, 0);
183 } else {
184 // fill radii array and sort it ascending
185 int index = 0;
186 for (Iterator it = radiiSet.iterator(); it.hasNext(); index++) {
187 radii[index] = ((Double) it.next()).doubleValue();
188 }
189 Arrays.sort(radii);
190
191 // sort NodeInfos by descending circleIndex and ascending wedgeStart
192 Collections.sort(nodeInfos, new Comparator() {
193 public int compare(Object o1, Object o2) {
194 RadialLayouter.NodeInfo info1 = (RadialLayouter.NodeInfo) o1;
195 RadialLayouter.NodeInfo info2 = (RadialLayouter.NodeInfo) o2;
196 if (info1.getCircleIndex() != info2.getCircleIndex()) {
197 return info2.getCircleIndex() - info1.getCircleIndex();
198 } else {
199 double dWedgeStart = info1.getSectorStart() - info2.getSectorStart();
200 return dWedgeStart < 0 ? -1 : dWedgeStart > 0 ? 1 : 0;
201 }
202 }
203 });
204
205 // update bounds to include the out-most circle
206 double maxRadius = radii[radii.length - 1];
207 bounds.setFrame(center.getX() - maxRadius, center.getY() - maxRadius, 2 * maxRadius, 2 * maxRadius);
208 }
209 g.updateViews();
210 }
211
212 public void paint(final Graphics2D g) {
213 if (radii == null || radii.length == 0) {
214 // no layout has been calculated for the current graph
215 return;
216 }
217
218 final Color oldColor = g.getColor();
219 final Stroke oldStroke = g.getStroke();
220
221 g.setStroke(stroke);
222 if (isDrawingSectors()) {
223 int colorIndex = 0;
224 for (int i = 0; i < nodeInfos.size(); i++) {
225
226 RadialLayouter.NodeInfo info = (RadialLayouter.NodeInfo) nodeInfos.get(i);
227 double radius = info.getRadius();
228
229 // the fill colors of the sectors alternate between two color sets depending on the circle index
230 // sectors on the same circle toggle their fill color between the two colors in their set
231 g.setColor(colors[info.getCircleIndex() % 2][colorIndex]);
232 colorIndex = (colorIndex + 1) % 2;
233
234 g.fill(new Arc2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius,
235 info.getSectorStart(), info.getSectorSize(), Arc2D.PIE));
236 }
237 }
238
239 if (isDrawingCircles()) {
240 g.setColor(colors[0][0]);
241
242 for (int i = 0; i < radii.length; i++) {
243 double radius = radii[i];
244
245 g.draw(new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius));
246 }
247 }
248
249 g.setStroke(oldStroke);
250 g.setColor(oldColor);
251 }
252 }
253