177a11f4b24be5b2e1a2aa72028dea6857e5d48b
[public/netxms.git] / src / java / netxms-eclipse / Charts / src / org / netxms / ui / eclipse / charts / widgets / DialChartWidget.java
1 /**
2 * NetXMS - open source network management system
3 * Copyright (C) 2003-2015 Victor Kirhenshtein
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 package org.netxms.ui.eclipse.charts.widgets;
20
21 import java.text.DecimalFormat;
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.graphics.Color;
24 import org.eclipse.swt.graphics.Font;
25 import org.eclipse.swt.graphics.GC;
26 import org.eclipse.swt.graphics.Image;
27 import org.eclipse.swt.graphics.Point;
28 import org.eclipse.swt.graphics.RGB;
29 import org.eclipse.swt.graphics.Rectangle;
30 import org.eclipse.swt.widgets.Composite;
31 import org.netxms.ui.eclipse.charts.Messages;
32 import org.netxms.ui.eclipse.charts.widgets.internal.DataComparisonElement;
33 import org.netxms.ui.eclipse.tools.WidgetHelper;
34
35 /**
36 * Dial chart implementation
37 */
38 public class DialChartWidget extends GaugeWidget
39 {
40 private static final int NEEDLE_PIN_RADIUS = 8;
41 private static final int SCALE_OFFSET = 30; // In percents
42 private static final int SCALE_WIDTH = 10; // In percents
43
44 private Font[] scaleFonts = null;
45 private Font[] valueFonts = null;
46
47 /**
48 * @param parent
49 * @param style
50 */
51 public DialChartWidget(Composite parent, int style)
52 {
53 super(parent, style);
54 }
55
56 /* (non-Javadoc)
57 * @see org.netxms.ui.eclipse.charts.widgets.GaugeWidget#createFonts()
58 */
59 @Override
60 protected void createFonts()
61 {
62 scaleFonts = new Font[16];
63 for(int i = 0; i < scaleFonts.length; i++)
64 scaleFonts[i] = new Font(getDisplay(), fontName, i + 6, SWT.NORMAL); //$NON-NLS-1$
65
66 valueFonts = new Font[16];
67 for(int i = 0; i < valueFonts.length; i++)
68 valueFonts[i] = new Font(getDisplay(), fontName, i + 6, SWT.BOLD); //$NON-NLS-1$
69 }
70
71 /* (non-Javadoc)
72 * @see org.netxms.ui.eclipse.charts.widgets.GaugeWidget#disposeFonts()
73 */
74 @Override
75 protected void disposeFonts()
76 {
77 if (scaleFonts != null)
78 {
79 for(int i = 0; i < scaleFonts.length; i++)
80 {
81 scaleFonts[i].dispose();
82 }
83 }
84 if (valueFonts != null)
85 {
86 for(int i = 0; i < valueFonts.length; i++)
87 valueFonts[i].dispose();
88 }
89 }
90
91 /* (non-Javadoc)
92 * @see org.netxms.ui.eclipse.charts.widgets.GaugeWidget#renderElement(org.eclipse.swt.graphics.GC, org.netxms.ui.eclipse.charts.widgets.internal.DataComparisonElement, int, int, int, int)
93 */
94 @Override
95 protected void renderElement(GC gc, DataComparisonElement dci, int x, int y, int w, int h)
96 {
97 Rectangle rect = new Rectangle(x + INNER_MARGIN_WIDTH, y + INNER_MARGIN_HEIGHT, w - INNER_MARGIN_WIDTH * 2, h - INNER_MARGIN_HEIGHT * 2);
98
99 if (legendVisible && !legendInside)
100 {
101 rect.height -= gc.textExtent("MMM").y + 4; //$NON-NLS-1$
102 }
103 if (rect.height > rect.width)
104 {
105 rect.y += (rect.height - rect.width) / 2;
106 rect.height = rect.width;
107 }
108 else
109 {
110 rect.x += (rect.width - rect.height) / 2;
111 rect.width = rect.height;
112 }
113
114 double angleValue = (maxValue - minValue) / 270;
115 int outerRadius = (rect.width + 1) / 2;
116 int scaleOuterOffset = ((rect.width / 2) * SCALE_OFFSET / 100);
117 int scaleInnerOffset = ((rect.width / 2) * (SCALE_OFFSET + SCALE_WIDTH) / 100);
118
119 int cx = rect.x + rect.width / 2 + 1;
120 int cy = rect.y + rect.height / 2 + 1;
121 gc.setBackground(getColorFromPreferences("Chart.Colors.PlotArea")); //$NON-NLS-1$
122 gc.fillArc(rect.x, rect.y, rect.width, rect.height, 0, 360);
123
124 // Draw zones
125 switch(colorMode)
126 {
127 case ZONE:
128 int startAngle = 225;
129 startAngle = drawZone(gc, rect, startAngle, minValue, leftRedZone, angleValue, RED_ZONE_COLOR);
130 startAngle = drawZone(gc, rect, startAngle, leftRedZone, leftYellowZone, angleValue, YELLOW_ZONE_COLOR);
131 startAngle = drawZone(gc, rect, startAngle, leftYellowZone, rightYellowZone, angleValue, GREEN_ZONE_COLOR);
132 startAngle = drawZone(gc, rect, startAngle, rightYellowZone, rightRedZone, angleValue, YELLOW_ZONE_COLOR);
133 startAngle = drawZone(gc, rect, startAngle, rightRedZone, maxValue, angleValue, RED_ZONE_COLOR);
134 break;
135 case CUSTOM:
136 drawZone(gc, rect, 225, minValue, maxValue, angleValue, customColor);
137 break;
138 default:
139 break;
140 }
141
142 // Draw center part and border
143 gc.setBackground(getColorFromPreferences("Chart.Colors.PlotArea")); //$NON-NLS-1$
144 gc.setForeground(getColorFromPreferences("Chart.Axis.Y.Color")); //$NON-NLS-1$
145 gc.fillArc(rect.x + scaleInnerOffset, rect.y + scaleInnerOffset, rect.width - scaleInnerOffset * 2, rect.height - scaleInnerOffset * 2, 0, 360);
146 gc.setLineWidth(2);
147 gc.drawArc(rect.x, rect.y, rect.width, rect.height, 0, 360);
148 gc.setLineWidth(1);
149
150 // Draw scale
151 Color scaleColor = getColorFromPreferences("Chart.Colors.DialScale"); //$NON-NLS-1$
152 Color scaleTextColor = getColorFromPreferences("Chart.Colors.DialScaleText"); //$NON-NLS-1$
153 gc.setForeground(scaleColor);
154 int textOffset = ((rect.width / 2) * SCALE_OFFSET / 200);
155 double arcLength = (outerRadius - scaleOuterOffset) * 4.7123889803846898576939650749193; // r * (270 degrees angle in radians)
156 int step = (arcLength >= 200) ? 27 : 54;
157 double valueStep = Math.abs((maxValue - minValue) / ((arcLength >= 200) ? 10 : 20));
158 int textWidth = (int)(Math.sqrt((outerRadius - scaleOuterOffset) * (outerRadius - scaleOuterOffset) / 2) * 0.7);
159 final Font markFont = WidgetHelper.getBestFittingFont(gc, scaleFonts, "900MM", textWidth, outerRadius - scaleOuterOffset); //$NON-NLS-1$
160 gc.setFont(markFont);
161 for(int i = 225; i >= -45; i -= step)
162 {
163 if (gridVisible)
164 {
165 gc.setForeground(scaleColor);
166 Point l1 = positionOnArc(cx, cy, outerRadius - scaleOuterOffset, i);
167 Point l2 = positionOnArc(cx, cy, outerRadius - scaleInnerOffset, i);
168 gc.drawLine(l1.x, l1.y, l2.x, l2.y);
169 }
170
171 String value = roundedMarkValue(i, angleValue, valueStep);
172 Point t = positionOnArc(cx, cy, outerRadius - textOffset, i);
173 Point ext = gc.textExtent(value, SWT.DRAW_TRANSPARENT);
174 gc.setForeground(scaleTextColor);
175 gc.drawText(value, t.x - ext.x / 2, t.y - ext.y / 2, SWT.DRAW_TRANSPARENT);
176 }
177 gc.setForeground(scaleColor);
178 gc.drawArc(rect.x + scaleOuterOffset, rect.y + scaleOuterOffset, rect.width - scaleOuterOffset * 2, rect.height - scaleOuterOffset * 2, -45, 270);
179 gc.drawArc(rect.x + scaleInnerOffset, rect.y + scaleInnerOffset, rect.width - scaleInnerOffset * 2, rect.height - scaleInnerOffset * 2, -45, 270);
180
181 // Draw needle
182 gc.setBackground(getColorFromPreferences("Chart.Colors.DialNeedle")); //$NON-NLS-1$
183 double dciValue = dci.getValue();
184 if (dciValue < minValue)
185 dciValue = minValue;
186 if (dciValue > maxValue)
187 dciValue = maxValue;
188 int angle = (int)(225 - (dciValue - minValue) / angleValue);
189 Point needleEnd = positionOnArc(cx, cy, outerRadius - ((rect.width / 2) * (SCALE_WIDTH / 2) / 100), angle);
190 Point np1 = positionOnArc(cx, cy, NEEDLE_PIN_RADIUS / 2, angle - 90);
191 Point np2 = positionOnArc(cx, cy, NEEDLE_PIN_RADIUS / 2, angle + 90);
192 gc.fillPolygon(new int[] { np1.x, np1.y, needleEnd.x, needleEnd.y, np2.x, np2.y });
193 gc.fillArc(cx - NEEDLE_PIN_RADIUS, cy - NEEDLE_PIN_RADIUS, NEEDLE_PIN_RADIUS * 2 - 1, NEEDLE_PIN_RADIUS * 2 - 1, 0, 360);
194 gc.setBackground(getColorFromPreferences("Chart.Colors.DialNeedlePin")); //$NON-NLS-1$
195 gc.fillArc(cx - NEEDLE_PIN_RADIUS / 2, cy - NEEDLE_PIN_RADIUS / 2, NEEDLE_PIN_RADIUS - 1, NEEDLE_PIN_RADIUS - 1, 0, 360);
196
197 // Draw current value
198 String value = getValueAsDisplayString(dci);
199 gc.setFont(WidgetHelper.getMatchingSizeFont(valueFonts, markFont));
200 Point ext = gc.textExtent(value, SWT.DRAW_TRANSPARENT);
201 gc.setLineWidth(3);
202 gc.setBackground(getColorFromPreferences("Chart.Colors.DialValueBackground")); //$NON-NLS-1$
203 int boxW = Math.max(outerRadius - scaleInnerOffset - 6, ext.x + 8);
204 gc.fillRoundRectangle(cx - boxW / 2, cy + rect.height / 4, boxW, ext.y + 6, 3, 3);
205 gc.setForeground(getColorFromPreferences("Chart.Colors.DialValueText")); //$NON-NLS-1$
206 gc.drawText(value, cx - ext.x / 2, cy + rect.height / 4 + 3, true);
207
208 // Draw legend, ignore legend position
209 if (legendVisible)
210 {
211 gc.setFont(legendInside ? markFont : null);
212 ext = gc.textExtent(dci.getName(), SWT.DRAW_TRANSPARENT);
213 gc.setForeground(getColorFromPreferences("Chart.Colors.Legend")); //$NON-NLS-1$
214 if (legendInside)
215 {
216 gc.drawText(dci.getName(), rect.x + ((rect.width - ext.x) / 2), rect.y + scaleInnerOffset / 2 + rect.height / 4, true);
217 }
218 else
219 {
220 gc.drawText(dci.getName(), rect.x + ((rect.width - ext.x) / 2), rect.y + rect.height + 4, true);
221 }
222 }
223 }
224
225 /**
226 * Draw colored zone.
227 *
228 * @param gc
229 * @param rect
230 * @param startAngle
231 * @param maxValue2
232 * @param leftRedZone2
233 * @param angleValue
234 * @param color color
235 * @return
236 */
237 private int drawZone(GC gc, Rectangle rect, int startAngle, double minValue, double maxValue, double angleValue, RGB color)
238 {
239 if (minValue >= maxValue)
240 return startAngle; // Ignore incorrect zone settings
241
242 int angle = (int)((maxValue - minValue) / angleValue);
243 if (angle <= 0)
244 return startAngle;
245
246 int offset = ((rect.width / 2) * SCALE_OFFSET / 100);
247
248 gc.setBackground(colors.create(color));
249 gc.fillArc(rect.x + offset, rect.y + offset, rect.width - offset * 2, rect.height - offset * 2, startAngle, -angle);
250 return startAngle - angle;
251 }
252
253 /**
254 * Find point coordinates on arc by given angle and radius. Angles are
255 * interpreted such that 0 degrees is at the 3 o'clock position.
256 *
257 * @param cx center point X coordinate
258 * @param cy center point Y coordinate
259 * @param radius radius
260 * @param angle angle
261 * @return point coordinates
262 */
263 private Point positionOnArc(int cx, int cy, int radius, int angle)
264 {
265 return new Point((int)(radius * Math.cos(Math.toRadians(angle)) + cx), (int)(radius * -Math.sin(Math.toRadians(angle)) + cy));
266 }
267
268 /**
269 * Get rounded value for scale mark
270 *
271 * @param angle
272 * @param angleValue
273 * @return
274 */
275 private String roundedMarkValue(int angle, double angleValue, double step)
276 {
277 double value = (225 - angle) * angleValue + minValue;
278 double absValue = Math.abs(value);
279 if (absValue >= 10000000000L)
280 {
281 return Long.toString(Math.round(value / 1000000000)) + Messages.get().DialChartWidget_G;
282 }
283 else if (absValue >= 1000000000)
284 {
285 return new DecimalFormat("#.#").format(value / 1000000000) + Messages.get().DialChartWidget_G; //$NON-NLS-1$
286 }
287 else if (absValue >= 10000000)
288 {
289 return Long.toString(Math.round(value / 1000000)) + Messages.get().DialChartWidget_M;
290 }
291 else if (absValue >= 1000000)
292 {
293 return new DecimalFormat("#.#").format(value / 1000000) + Messages.get().DialChartWidget_M; //$NON-NLS-1$
294 }
295 else if (absValue >= 10000)
296 {
297 return Long.toString(Math.round(value / 1000)) + Messages.get().DialChartWidget_K;
298 }
299 else if (absValue >= 1000)
300 {
301 return new DecimalFormat("#.#").format(value / 1000) + Messages.get().DialChartWidget_K; //$NON-NLS-1$
302 }
303 else if ((absValue >= 1) && (step >= 1))
304 {
305 return Long.toString(Math.round(value));
306 }
307 else if (absValue == 0)
308 {
309 return "0"; //$NON-NLS-1$
310 }
311 else
312 {
313 if (step < 0.00001)
314 return Double.toString(value);
315 if (step < 0.0001)
316 return new DecimalFormat("#.#####").format(value); //$NON-NLS-1$
317 if (step < 0.001)
318 return new DecimalFormat("#.####").format(value); //$NON-NLS-1$
319 if (step < 0.01)
320 return new DecimalFormat("#.###").format(value); //$NON-NLS-1$
321 return new DecimalFormat("#.##").format(value); //$NON-NLS-1$
322 }
323 }
324
325 /**
326 * Take snapshot of network map
327 *
328 * @return
329 */
330 public Image takeSnapshot()
331 {
332 Rectangle rect = getClientArea();
333 Image image = new Image(getDisplay(), rect.width, rect.height);
334 GC gc = new GC(image);
335 this.print(gc);
336 gc.dispose();
337 return image;
338 }
339
340 /**
341 * Get minimal element size
342 *
343 * @return
344 */
345 @Override
346 protected Point getMinElementSize()
347 {
348 return new Point(40, 40);
349 }
350 }