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