Mercurial > dive4elements > gnv-client
comparison gnv-artifacts/src/main/java/de/intevation/gnv/chart/AbstractXYLineChart.java @ 1119:7c4f81f74c47
merged gnv-artifacts
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:00 +0200 |
parents | f953c9a559d8 |
children |
comparison
equal
deleted
inserted
replaced
1027:fca4b5eb8d2f | 1119:7c4f81f74c47 |
---|---|
1 /* | |
2 * Copyright (c) 2010 by Intevation GmbH | |
3 * | |
4 * This program is free software under the LGPL (>=v2.1) | |
5 * Read the file LGPL.txt coming with the software for details | |
6 * or visit http://www.gnu.org/licenses/ if it does not exist. | |
7 */ | |
8 | |
9 package de.intevation.gnv.chart; | |
10 | |
11 import de.intevation.gnv.geobackend.base.Result; | |
12 | |
13 import de.intevation.gnv.state.describedata.KeyValueDescibeData; | |
14 | |
15 import java.awt.Color; | |
16 | |
17 import java.awt.geom.Ellipse2D; | |
18 | |
19 import java.text.NumberFormat; | |
20 | |
21 import java.util.Collection; | |
22 import java.util.Iterator; | |
23 import java.util.Locale; | |
24 import java.util.Map; | |
25 | |
26 import org.apache.log4j.Logger; | |
27 | |
28 import org.jfree.chart.ChartFactory; | |
29 import org.jfree.chart.JFreeChart; | |
30 | |
31 import org.jfree.chart.axis.Axis; | |
32 import org.jfree.chart.axis.AxisLocation; | |
33 import org.jfree.chart.axis.NumberAxis; | |
34 import org.jfree.chart.axis.NumberTickUnit; | |
35 | |
36 import org.jfree.chart.plot.PlotOrientation; | |
37 import org.jfree.chart.plot.XYPlot; | |
38 | |
39 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; | |
40 | |
41 import org.jfree.chart.title.TextTitle; | |
42 | |
43 import org.jfree.data.Range; | |
44 | |
45 import org.jfree.data.general.Series; | |
46 | |
47 import org.jfree.data.xy.XYDataset; | |
48 | |
49 import org.jfree.ui.RectangleInsets; | |
50 | |
51 /** | |
52 * This abstract class defines some methods to adjust chart settings after its | |
53 * creation. | |
54 * | |
55 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> | |
56 */ | |
57 public abstract class AbstractXYLineChart | |
58 extends AbstractChart | |
59 { | |
60 /** | |
61 * Constant field used to expand the area between data and chart border. Its | |
62 * value is {@value}.<br> | |
63 * A value of 0.05 equals 5 percent. | |
64 */ | |
65 public static final double LOWER_MARGIN = 0.05D; | |
66 | |
67 /** | |
68 * Constant field used to expand the area between data and chart border. Its | |
69 * value is {@value}.<br> | |
70 * A value of 0.05 equals 5 percent. | |
71 */ | |
72 public static final double UPPER_MARGIN = 0.05D; | |
73 | |
74 /** | |
75 * Logger used to log with log4j. | |
76 */ | |
77 private static Logger log = Logger.getLogger(AbstractXYLineChart.class); | |
78 | |
79 /** | |
80 * Field of supported colors used for lines and data points in charts. | |
81 */ | |
82 protected static Color[] COLOR = { | |
83 Color.black, Color.red, Color.green, Color.blue, Color.yellow, | |
84 Color.gray, Color.orange, Color.pink, Color.cyan | |
85 }; | |
86 | |
87 /** | |
88 * Static field to remember the index of the previously used color. | |
89 */ | |
90 protected static int nextColor = 0; | |
91 | |
92 /** | |
93 * Default <code>PlotOrientation</code>. | |
94 */ | |
95 protected PlotOrientation PLOT_ORIENTATION = PlotOrientation.VERTICAL; | |
96 | |
97 /** | |
98 * Map to store datasets for each parameter. | |
99 */ | |
100 protected Map datasets; | |
101 | |
102 /** | |
103 * Map to store max ranges of each parameter (axis.setAutoRange(true) | |
104 * doesn't seem to work */ | |
105 protected Map ranges; | |
106 | |
107 /** | |
108 * This method is called by <code>Chart</code> to bring the data into the | |
109 * right form fitting to JFreeChart objects. | |
110 */ | |
111 protected abstract void initData(); | |
112 | |
113 /** | |
114 * Add a value of <code>row</code> to <code>series</code>. | |
115 * | |
116 * @param row <code>Result</code> Object returned from database. Contains | |
117 * a value used to add to <code>series</code> | |
118 * @param series A JFreeChart Series object. | |
119 */ | |
120 protected abstract void addValue(Result row, Series series); | |
121 | |
122 /** | |
123 * Add <code>series</code> to JFreeChart's Dataset object currently which is | |
124 * processing. | |
125 * | |
126 * @param series Series to add. | |
127 * @param label Label used show in legend. | |
128 * @param idx Currently not used. | |
129 */ | |
130 protected abstract void addSeries(Series series, String label, int idx); | |
131 | |
132 /** | |
133 * Abstract method which is called by <code>Chart</code> interface after | |
134 * chart creation. It turns an axis' label into a locale specific format. | |
135 * | |
136 * @param axis Axis to adjust. | |
137 * @param locale java.util.Locale object used specify the format. | |
138 */ | |
139 protected abstract void localizeDomainAxis(Axis axis, Locale locale); | |
140 | |
141 /** | |
142 * Abstract method to create a label for a series of parameters. | |
143 * | |
144 * @param breakPoint1 Identifier returned from database. These identifier | |
145 * are used to identify the results from database which are all stored in | |
146 * one big java.util.Collection. | |
147 * @param breakPoint2 Identifier returned from database. | |
148 * @param breakPoint3 Identifier returned from database. | |
149 * | |
150 * @return Concatinated string of parameter name and measurement. | |
151 */ | |
152 protected abstract String createSeriesName( | |
153 String breakPoint1, | |
154 String breakPoint2, | |
155 String breakPoint3 | |
156 ); | |
157 | |
158 | |
159 /** | |
160 * @see de.intevation.gnv.chart.Chart#generateChart() | |
161 */ | |
162 public JFreeChart generateChart() { | |
163 log.debug("generate XYLineChart"); | |
164 nextColor = 0; | |
165 | |
166 if (chart != null) | |
167 return chart; | |
168 | |
169 initChart(); | |
170 | |
171 chart.addSubtitle(new TextTitle(labels.getSubtitle())); | |
172 | |
173 theme.apply(chart); | |
174 initData(); | |
175 | |
176 adjustPlot((XYPlot)chart.getPlot()); | |
177 | |
178 return chart; | |
179 } | |
180 | |
181 | |
182 protected void initChart() { | |
183 chart = ChartFactory.createXYLineChart( | |
184 labels.getTitle(), | |
185 labels.getDomainAxisLabel(), | |
186 null, | |
187 null, | |
188 PLOT_ORIENTATION, | |
189 true, | |
190 false, | |
191 false | |
192 ); | |
193 } | |
194 | |
195 | |
196 /** | |
197 * Method used to adjust the axes after chart generation. Methods for i18n | |
198 * support ({@link #localizeDomainAxis} and {@link #localizeRangeAxis}) are | |
199 * called and axes of this series are expanded. | |
200 * | |
201 * @param seriesKey Identifier of an axis which have to be adjusted. | |
202 * @param idx Set the axis identified by <code>seriesKey</code> to position | |
203 * <code>idx</code>. | |
204 */ | |
205 protected void prepareAxis(String seriesKey, int idx) { | |
206 log.debug("prepare axis of xychart"); | |
207 | |
208 XYPlot plot = chart.getXYPlot(); | |
209 Axis xAxis = plot.getDomainAxis(); | |
210 NumberAxis yAxis = new NumberAxis(seriesKey); | |
211 | |
212 localizeDomainAxis(xAxis, locale); | |
213 localizeRangeAxis(yAxis, locale); | |
214 | |
215 // litte workarround to adjust the max range of axes. | |
216 // NumberAxis.setAutoRange(true) doesn't seem to work properly. | |
217 Range yRange = (Range) ranges.get(seriesKey); | |
218 double lo = yRange.getLowerBound(); | |
219 double hi = yRange.getUpperBound(); | |
220 | |
221 if (lo == hi) { | |
222 yRange = new Range( | |
223 lo - (lo / 100 * LOWER_MARGIN), | |
224 hi + (hi / 100 * UPPER_MARGIN)); | |
225 } | |
226 else { | |
227 yRange = Range.expand(yRange, LOWER_MARGIN, UPPER_MARGIN); | |
228 } | |
229 yAxis.setRange(yRange); | |
230 log.debug("Max Range of dataset is: " + yRange.toString()); | |
231 | |
232 if (seriesKey.contains("richtung")) { | |
233 yAxis.setTickUnit(new NumberTickUnit(30.0)); | |
234 yAxis.setUpperBound(360.0); | |
235 yAxis.setLowerBound(0.0); | |
236 } | |
237 else { | |
238 yAxis.setFixedDimension(10.0); | |
239 yAxis.setAutoRangeIncludesZero(false); | |
240 } | |
241 | |
242 plot.setRangeAxis(idx, yAxis); | |
243 yAxis.configure(); | |
244 | |
245 if (idx % 2 != 0) | |
246 plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_RIGHT); | |
247 else | |
248 plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_LEFT); | |
249 | |
250 plot.mapDatasetToRangeAxis(idx, idx); | |
251 } | |
252 | |
253 | |
254 /** | |
255 * Method to adjust the rendering of a series in a chart. Line color and | |
256 * symbols of vertices are configured here. | |
257 * | |
258 * @param idx Position of the renderer. | |
259 * @param seriesCount Maximum number of series in this chart. | |
260 * @param renderLines Lines are displayed if true, otherwise they are not. | |
261 * @param renderShapes Vertices are displayed if true, otherwise they are | |
262 * not. | |
263 */ | |
264 protected void adjustRenderer( | |
265 int idx, | |
266 int seriesCount, | |
267 boolean renderLines, | |
268 boolean renderShapes | |
269 ) { | |
270 log.debug("Adjust render of series"); | |
271 XYLineAndShapeRenderer renderer = null; | |
272 XYPlot plot = chart.getXYPlot(); | |
273 | |
274 try { | |
275 renderer = (XYLineAndShapeRenderer)((XYLineAndShapeRenderer) | |
276 (plot.getRenderer())).clone(); | |
277 } | |
278 catch (CloneNotSupportedException cnse) { | |
279 log.warn("Error while cloning renderer.", cnse); | |
280 renderer = new XYLineAndShapeRenderer(renderLines, renderShapes); | |
281 renderer.setBaseShape(new Ellipse2D.Double(-2,-2,4,4)); | |
282 } | |
283 | |
284 for (int i = 0; i < seriesCount; i++) { | |
285 renderer.setSeriesShape(i, renderer.getSeriesShape(0)); | |
286 renderer.setSeriesPaint(i, COLOR[nextColor() % COLOR.length]); | |
287 renderer.setSeriesShapesVisible(i, renderShapes); | |
288 renderer.setSeriesLinesVisible(i, renderLines); | |
289 } | |
290 plot.setRenderer(idx, renderer); | |
291 } | |
292 | |
293 | |
294 /** | |
295 * @return Index of the next color | |
296 */ | |
297 protected static synchronized int nextColor() { | |
298 return nextColor++; | |
299 } | |
300 | |
301 | |
302 /** | |
303 * Method to adjust the plot rendering. Disable horizontal grid lines if | |
304 * <code>plot</code> contains only a single y-axis. | |
305 * | |
306 * @param plot JFreeChart Plot object to be adjusted. | |
307 */ | |
308 protected void adjustPlot(XYPlot plot) { | |
309 if (plot.getRangeAxisCount() > 1) | |
310 plot.setRangeGridlinesVisible(false); | |
311 | |
312 plot.setAxisOffset(new RectangleInsets(0, 0, 0, 15)); | |
313 } | |
314 | |
315 | |
316 /** | |
317 * Abstract method which is called after chart creation. It turns an | |
318 * axis' label into a locale specific format. | |
319 * | |
320 * @param axis Axis to adjust. | |
321 * @param locale java.util.Locale object used specify the format. | |
322 * | |
323 */ | |
324 protected void localizeRangeAxis(Axis axis, Locale locale) { | |
325 if (locale == null) | |
326 return; | |
327 | |
328 log.debug( | |
329 "Set language of axis [" + axis.getLabel() + "] " + | |
330 "to " + locale.toString() | |
331 ); | |
332 | |
333 NumberFormat format = NumberFormat.getInstance(locale); | |
334 ((NumberAxis) axis).setNumberFormatOverride(format); | |
335 } | |
336 | |
337 | |
338 /** | |
339 * Return the maximum y-range of <code>dataset</code>. | |
340 * | |
341 * @param dataset Dataset to be scaned. | |
342 * | |
343 * @return JFreeChart Range object containing min and max y-value. | |
344 */ | |
345 public Range getMaxRangeOfDataset(XYDataset dataset) { | |
346 int seriesCount = dataset.getSeriesCount(); | |
347 double upper = Double.NEGATIVE_INFINITY; | |
348 double lower = Double.POSITIVE_INFINITY; | |
349 | |
350 for (int i = 0; i < seriesCount; i++) { | |
351 int itemCount = dataset.getItemCount(i); | |
352 | |
353 for (int j = 0; j < itemCount; j++) { | |
354 Number num = dataset.getY(i, j); | |
355 | |
356 if (num != null) { | |
357 double y = num.doubleValue(); | |
358 lower = y < lower ? y : lower; | |
359 upper = y > upper ? y : upper; | |
360 } | |
361 } | |
362 } | |
363 | |
364 return new Range(lower, upper); | |
365 } | |
366 | |
367 | |
368 /** | |
369 * Return the maximum y-range of <code>dataset</code> with a margin of | |
370 * <code>percent</code> percent. | |
371 * | |
372 * @param dataset Dataset to be scaned. | |
373 * @param percent Percent used to expand the range. | |
374 * @return JFreeChart Range object containing min and max y-value with a | |
375 * margin. | |
376 */ | |
377 public Range getMaxRangeOfDatasetWithMargin( | |
378 XYDataset dataset, | |
379 double percent | |
380 ) { | |
381 Range range = getMaxRangeOfDataset(dataset); | |
382 double length = range.getLength(); | |
383 double upper = range.getUpperBound() + length /100 * percent; | |
384 double lower = range.getLowerBound() - length /100 * percent; | |
385 | |
386 return new Range(lower, upper); | |
387 } | |
388 | |
389 | |
390 /** | |
391 * Method to find a parameter specified by its value. | |
392 * | |
393 * @param label Search string. | |
394 * | |
395 * @return Value of a parameter with the given label. | |
396 */ | |
397 protected String findParameter(String label) { | |
398 Iterator iter = parameters.iterator(); | |
399 | |
400 while (iter.hasNext()) { | |
401 KeyValueDescibeData data = (KeyValueDescibeData) iter.next(); | |
402 String key = data.getValue(); | |
403 | |
404 if (label.indexOf(key) > -1) | |
405 return key; | |
406 } | |
407 | |
408 return label; | |
409 } | |
410 | |
411 | |
412 /** | |
413 * Method to find a description of a given collection of values. | |
414 * | |
415 * @param values Collection to be scaned. | |
416 * @param id Identifier and search string of the searched value. | |
417 * | |
418 * @return title | |
419 */ | |
420 protected String findValueTitle(Collection values, String id) { | |
421 log.debug("find description of dataset"); | |
422 | |
423 if (values != null){ | |
424 Iterator it = values.iterator(); | |
425 while (it.hasNext()) { | |
426 KeyValueDescibeData data = (KeyValueDescibeData) it.next(); | |
427 | |
428 if (id.equals(data.getKey())) | |
429 return data.getValue(); | |
430 } | |
431 } | |
432 return ""; | |
433 } | |
434 | |
435 | |
436 /** | |
437 * Method to store the maximum range. Since we need to adjust the range of | |
438 * each range axis, we have to memorize its range - each parameter uses an | |
439 * own range axis. | |
440 * | |
441 * @param ranges Map where ranges of each axis will be stored in. | |
442 * @param value A given value on an axis. | |
443 * @param parameter Parameter name which belongs to <code>value</code>. | |
444 */ | |
445 protected void storeMaxRange(Map ranges, double value, String parameter) { | |
446 Range range = null; | |
447 | |
448 range = ranges.containsKey(parameter) | |
449 ? (Range) ranges.get(parameter) | |
450 : new Range(value, value); | |
451 | |
452 double lower = range.getLowerBound(); | |
453 double upper = range.getUpperBound(); | |
454 | |
455 lower = value < lower ? value : lower; | |
456 upper = value > upper ? value : upper; | |
457 | |
458 ranges.put(parameter, new Range(lower, upper)); | |
459 } | |
460 } | |
461 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : |