Mercurial > dive4elements > gnv-client
comparison gnv-artifacts/src/main/java/de/intevation/gnv/chart/TimeSeriesChart.java @ 657:af3f56758f59
merged gnv-artifacts/0.5
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:13:53 +0200 |
parents | b98d1adee7a6 |
children | 79401c871da4 |
comparison
equal
deleted
inserted
replaced
590:5f5f273c8566 | 657:af3f56758f59 |
---|---|
1 package de.intevation.gnv.chart; | |
2 | |
3 import de.intevation.gnv.artifacts.ressource.RessourceFactory; | |
4 | |
5 import de.intevation.gnv.geobackend.base.Result; | |
6 | |
7 import de.intevation.gnv.state.describedata.KeyValueDescibeData; | |
8 | |
9 import de.intevation.gnv.timeseries.gap.TimeGap; | |
10 | |
11 import java.text.DateFormat; | |
12 import java.text.SimpleDateFormat; | |
13 | |
14 import java.util.Collection; | |
15 import java.util.Date; | |
16 import java.util.HashMap; | |
17 import java.util.Iterator; | |
18 import java.util.Locale; | |
19 import java.util.TimeZone; | |
20 | |
21 import org.apache.log4j.Logger; | |
22 | |
23 import org.jfree.chart.ChartFactory; | |
24 import org.jfree.chart.ChartTheme; | |
25 | |
26 import org.jfree.chart.axis.Axis; | |
27 import org.jfree.chart.axis.DateAxis; | |
28 import org.jfree.chart.axis.DateTickUnit; | |
29 import org.jfree.chart.axis.DateTickUnitType; | |
30 import org.jfree.chart.axis.TickUnits; | |
31 import org.jfree.chart.axis.TickUnitSource; | |
32 import org.jfree.chart.axis.ValueAxis; | |
33 | |
34 import org.jfree.chart.plot.PlotOrientation; | |
35 import org.jfree.chart.plot.XYPlot; | |
36 | |
37 import org.jfree.data.general.Series; | |
38 | |
39 import org.jfree.data.time.Minute; | |
40 import org.jfree.data.time.TimeSeries; | |
41 import org.jfree.data.time.TimeSeriesCollection; | |
42 | |
43 /** | |
44 * @author Ingo Weinzierl (ingo.weinzierl@intevation.de) | |
45 */ | |
46 public class TimeSeriesChart | |
47 extends AbstractXYLineChart | |
48 { | |
49 | |
50 private static final String DATE_FORMAT = "chart.timeseries.date.format"; | |
51 | |
52 public static final String DEFAULT_DATE_FORMAT = "dd-MMM-yyyy"; | |
53 | |
54 public static final long NO_TIME_GAP = Long.MAX_VALUE - 1000; | |
55 public static final int GAP_SIZE = 5; // in percent | |
56 | |
57 private static Logger log = Logger.getLogger(TimeSeriesChart.class); | |
58 | |
59 | |
60 public TimeSeriesChart( | |
61 ChartLabels labels, | |
62 ChartTheme theme, | |
63 Collection parameters, | |
64 Collection measurements, | |
65 Collection dates, | |
66 Collection result, | |
67 Collection timeGaps, | |
68 Locale locale, | |
69 boolean linesVisible, | |
70 boolean shapesVisible | |
71 ) { | |
72 this.labels = labels; | |
73 this.theme = theme; | |
74 this.parameters = parameters; | |
75 this.measurements = measurements; | |
76 this.dates = dates; | |
77 this.resultSet = result; | |
78 this.timeGaps = timeGaps; | |
79 this.locale = locale; | |
80 this.PLOT_ORIENTATION = PlotOrientation.VERTICAL; | |
81 this.linesVisible = linesVisible; | |
82 this.shapesVisible = shapesVisible; | |
83 this.datasets = new HashMap(); | |
84 this.ranges = new HashMap(); | |
85 } | |
86 | |
87 | |
88 protected void initChart() { | |
89 chart = ChartFactory.createTimeSeriesChart( | |
90 labels.getTitle(), | |
91 labels.getDomainAxisLabel(), | |
92 null, | |
93 null, | |
94 true, | |
95 false, | |
96 false | |
97 ); | |
98 | |
99 XYPlot plot = (XYPlot) chart.getPlot(); | |
100 plot.setDomainAxis(0, new DateAxis( | |
101 labels.getDomainAxisLabel(), TimeZone.getDefault(), locale)); | |
102 } | |
103 | |
104 | |
105 protected void initData() { | |
106 log.debug("init data for timeseries chart"); | |
107 | |
108 String breakPoint1 = null; | |
109 String breakPoint2 = null; | |
110 String breakPoint3 = null; | |
111 | |
112 Iterator iter = resultSet.iterator(); | |
113 Result row = null; | |
114 String seriesName = null; | |
115 String parameter = null; | |
116 TimeSeries series = null; | |
117 | |
118 int idx = 0; | |
119 int startPos = 0; | |
120 int endPos = 0; | |
121 Date startDate = null; | |
122 Date endDate = null; | |
123 | |
124 Result[] results = | |
125 (Result[]) resultSet.toArray(new Result[resultSet.size()]); | |
126 | |
127 while (iter.hasNext()) { | |
128 row = (Result) iter.next(); | |
129 | |
130 // add current data to plot and prepare for next one | |
131 if (!row.getString("GROUP1").equals(breakPoint1) || | |
132 !row.getString("GROUP2").equals(breakPoint2) || | |
133 !row.getString("GROUP3").equals(breakPoint3) | |
134 ) { | |
135 log.debug("prepare data/plot for next dataset"); | |
136 | |
137 if(series != null) { | |
138 // add gaps before adding series to chart | |
139 startDate = results[startPos].getDate("XORDINATE"); | |
140 endDate = results[endPos-1].getDate("XORDINATE"); | |
141 addGaps(results,series,startDate,endDate,startPos,endPos); | |
142 addSeries(series, parameter, idx); | |
143 | |
144 startPos = endPos + 1; | |
145 } | |
146 | |
147 // prepare variables for next plot | |
148 breakPoint1 = row.getString("GROUP1"); | |
149 breakPoint2 = row.getString("GROUP2"); | |
150 breakPoint3 = row.getString("GROUP3"); | |
151 | |
152 seriesName = createSeriesName( | |
153 breakPoint1, | |
154 breakPoint2, | |
155 breakPoint3 | |
156 ); | |
157 parameter = findParameter(seriesName); | |
158 | |
159 log.debug("next dataset is '" + seriesName + "'"); | |
160 series = new TimeSeries(seriesName, Minute.class); | |
161 } | |
162 | |
163 addValue(row, series); | |
164 storeMaxRange(ranges, row.getDouble("YORDINATE"), parameter); | |
165 endPos++; | |
166 } | |
167 | |
168 if (startPos < results.length && endPos-1 < results.length) { | |
169 // add the last dataset if existing to plot and prepare its axis | |
170 startDate = results[startPos].getDate("XORDINATE"); | |
171 endDate = results[endPos-1].getDate("XORDINATE"); | |
172 addGaps(results, series, startDate, endDate, startPos, endPos); | |
173 addSeries(series, parameter, idx); | |
174 } | |
175 | |
176 addDatasets(); | |
177 } | |
178 | |
179 | |
180 protected void addValue(Result row, Series series) { | |
181 ((TimeSeries) series).addOrUpdate( | |
182 new Minute(row.getDate("XORDINATE")), | |
183 row.getDouble("YORDINATE") | |
184 ); | |
185 } | |
186 | |
187 | |
188 protected void addSeries(Series series, String parameter, int idx) { | |
189 log.debug("add series (" + parameter + ")to timeseries chart"); | |
190 | |
191 if (series == null) { | |
192 log.warn("no data to add"); | |
193 return; | |
194 } | |
195 | |
196 TimeSeriesCollection tsc = null; | |
197 | |
198 if (datasets.containsKey(parameter)) | |
199 tsc = (TimeSeriesCollection) datasets.get(parameter); | |
200 else | |
201 tsc = new TimeSeriesCollection(); | |
202 | |
203 tsc.addSeries((TimeSeries) series); | |
204 datasets.put(parameter, tsc); | |
205 } | |
206 | |
207 | |
208 protected void addDatasets() { | |
209 Iterator iter = parameters.iterator(); | |
210 XYPlot plot = chart.getXYPlot(); | |
211 int idx = 0; | |
212 | |
213 TimeSeriesCollection tsc = null; | |
214 KeyValueDescibeData data = null; | |
215 String key = null; | |
216 while (iter.hasNext()) { | |
217 data = (KeyValueDescibeData) iter.next(); | |
218 key = data.getValue(); | |
219 | |
220 if (datasets.containsKey(key)) { | |
221 tsc = (TimeSeriesCollection)datasets.get(key); | |
222 plot.setDataset(idx, tsc ); | |
223 log.debug("Added " + key + " parameter to plot."); | |
224 prepareAxis(key, idx); | |
225 adjustRenderer( | |
226 idx++, | |
227 tsc.getSeriesCount(), | |
228 linesVisible, | |
229 shapesVisible | |
230 ); | |
231 } | |
232 } | |
233 } | |
234 | |
235 | |
236 protected void localizeDomainAxis(Axis axis, Locale locale) { | |
237 ((ValueAxis)axis).setStandardTickUnits(createStandardDateTickUnits( | |
238 TimeZone.getDefault(), | |
239 locale)); | |
240 } | |
241 | |
242 | |
243 public static TickUnitSource createStandardDateTickUnits( | |
244 TimeZone zone, | |
245 Locale locale) | |
246 { | |
247 /* | |
248 * This method have been copied from JFreeChart's DateAxis class. | |
249 * DateFormat objects are hard coded in DateAxis and cannot be adjusted. | |
250 */ | |
251 if (zone == null) { | |
252 throw new IllegalArgumentException("Null 'zone' argument."); | |
253 } | |
254 if (locale == null) { | |
255 throw new IllegalArgumentException("Null 'locale' argument."); | |
256 } | |
257 TickUnits units = new TickUnits(); | |
258 | |
259 // date formatters | |
260 DateFormat f1 = new SimpleDateFormat("HH:mm:ss.SSS", locale); | |
261 DateFormat f2 = new SimpleDateFormat("HH:mm:ss", locale); | |
262 DateFormat f3 = new SimpleDateFormat("HH:mm", locale); | |
263 DateFormat f4 = new SimpleDateFormat("d-MMM, HH:mm", locale); | |
264 DateFormat f5 = new SimpleDateFormat("d-MMM yyyy", locale); | |
265 DateFormat f6 = new SimpleDateFormat("MMM-yyyy", locale); | |
266 DateFormat f7 = new SimpleDateFormat("yyyy", locale); | |
267 | |
268 f1.setTimeZone(zone); | |
269 f2.setTimeZone(zone); | |
270 f3.setTimeZone(zone); | |
271 f4.setTimeZone(zone); | |
272 f5.setTimeZone(zone); | |
273 f6.setTimeZone(zone); | |
274 f7.setTimeZone(zone); | |
275 | |
276 // milliseconds | |
277 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 1, f1)); | |
278 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 5, | |
279 DateTickUnitType.MILLISECOND, 1, f1)); | |
280 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 10, | |
281 DateTickUnitType.MILLISECOND, 1, f1)); | |
282 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 25, | |
283 DateTickUnitType.MILLISECOND, 5, f1)); | |
284 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 50, | |
285 DateTickUnitType.MILLISECOND, 10, f1)); | |
286 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 100, | |
287 DateTickUnitType.MILLISECOND, 10, f1)); | |
288 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 250, | |
289 DateTickUnitType.MILLISECOND, 10, f1)); | |
290 units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 500, | |
291 DateTickUnitType.MILLISECOND, 50, f1)); | |
292 | |
293 // seconds | |
294 units.add(new DateTickUnit(DateTickUnitType.SECOND, 1, | |
295 DateTickUnitType.MILLISECOND, 50, f2)); | |
296 units.add(new DateTickUnit(DateTickUnitType.SECOND, 5, | |
297 DateTickUnitType.SECOND, 1, f2)); | |
298 units.add(new DateTickUnit(DateTickUnitType.SECOND, 10, | |
299 DateTickUnitType.SECOND, 1, f2)); | |
300 units.add(new DateTickUnit(DateTickUnitType.SECOND, 30, | |
301 DateTickUnitType.SECOND, 5, f2)); | |
302 | |
303 // minutes | |
304 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 1, | |
305 DateTickUnitType.SECOND, 5, f3)); | |
306 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 2, | |
307 DateTickUnitType.SECOND, 10, f3)); | |
308 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 5, | |
309 DateTickUnitType.MINUTE, 1, f3)); | |
310 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 10, | |
311 DateTickUnitType.MINUTE, 1, f3)); | |
312 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 15, | |
313 DateTickUnitType.MINUTE, 5, f3)); | |
314 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 20, | |
315 DateTickUnitType.MINUTE, 5, f3)); | |
316 units.add(new DateTickUnit(DateTickUnitType.MINUTE, 30, | |
317 DateTickUnitType.MINUTE, 5, f3)); | |
318 | |
319 // hours | |
320 units.add(new DateTickUnit(DateTickUnitType.HOUR, 1, | |
321 DateTickUnitType.MINUTE, 5, f3)); | |
322 units.add(new DateTickUnit(DateTickUnitType.HOUR, 2, | |
323 DateTickUnitType.MINUTE, 10, f3)); | |
324 units.add(new DateTickUnit(DateTickUnitType.HOUR, 4, | |
325 DateTickUnitType.MINUTE, 30, f3)); | |
326 units.add(new DateTickUnit(DateTickUnitType.HOUR, 6, | |
327 DateTickUnitType.HOUR, 1, f3)); | |
328 units.add(new DateTickUnit(DateTickUnitType.HOUR, 12, | |
329 DateTickUnitType.HOUR, 1, f4)); | |
330 | |
331 // days | |
332 units.add(new DateTickUnit(DateTickUnitType.DAY, 1, | |
333 DateTickUnitType.HOUR, 1, f5)); | |
334 units.add(new DateTickUnit(DateTickUnitType.DAY, 2, | |
335 DateTickUnitType.HOUR, 1, f5)); | |
336 units.add(new DateTickUnit(DateTickUnitType.DAY, 7, | |
337 DateTickUnitType.DAY, 1, f5)); | |
338 units.add(new DateTickUnit(DateTickUnitType.DAY, 15, | |
339 DateTickUnitType.DAY, 1, f5)); | |
340 | |
341 // months | |
342 units.add(new DateTickUnit(DateTickUnitType.MONTH, 1, | |
343 DateTickUnitType.DAY, 1, f6)); | |
344 units.add(new DateTickUnit(DateTickUnitType.MONTH, 2, | |
345 DateTickUnitType.DAY, 1, f6)); | |
346 units.add(new DateTickUnit(DateTickUnitType.MONTH, 3, | |
347 DateTickUnitType.MONTH, 1, f6)); | |
348 units.add(new DateTickUnit(DateTickUnitType.MONTH, 4, | |
349 DateTickUnitType.MONTH, 1, f6)); | |
350 units.add(new DateTickUnit(DateTickUnitType.MONTH, 6, | |
351 DateTickUnitType.MONTH, 1, f6)); | |
352 | |
353 // years | |
354 units.add(new DateTickUnit(DateTickUnitType.YEAR, 1, | |
355 DateTickUnitType.MONTH, 1, f7)); | |
356 units.add(new DateTickUnit(DateTickUnitType.YEAR, 2, | |
357 DateTickUnitType.MONTH, 3, f7)); | |
358 units.add(new DateTickUnit(DateTickUnitType.YEAR, 5, | |
359 DateTickUnitType.YEAR, 1, f7)); | |
360 units.add(new DateTickUnit(DateTickUnitType.YEAR, 10, | |
361 DateTickUnitType.YEAR, 1, f7)); | |
362 units.add(new DateTickUnit(DateTickUnitType.YEAR, 25, | |
363 DateTickUnitType.YEAR, 5, f7)); | |
364 units.add(new DateTickUnit(DateTickUnitType.YEAR, 50, | |
365 DateTickUnitType.YEAR, 10, f7)); | |
366 units.add(new DateTickUnit(DateTickUnitType.YEAR, 100, | |
367 DateTickUnitType.YEAR, 20, f7)); | |
368 | |
369 return units; | |
370 } | |
371 | |
372 | |
373 protected String getMessage(Locale locale, String key, String def) { | |
374 return RessourceFactory.getInstance().getRessource(locale, key, def); | |
375 } | |
376 | |
377 | |
378 protected String createSeriesName( | |
379 String breakPoint1, | |
380 String breakPoint2, | |
381 String breakPoint3 | |
382 ) { | |
383 log.debug("create seriesname of timeseries chart"); | |
384 return findValueTitle(parameters, breakPoint1) + | |
385 " " + | |
386 findValueTitle(measurements, breakPoint2) + | |
387 "m"; | |
388 } | |
389 | |
390 | |
391 protected void addGaps( | |
392 Result[] results, | |
393 Series series, | |
394 Date startDate, | |
395 Date endDate, | |
396 int startPos, | |
397 int endPos | |
398 ) { | |
399 int gapID = results[startPos].getInteger("GAPID"); | |
400 long maxDiff = calculateGapSize( | |
401 startDate, endDate, startPos, endPos, gapID | |
402 ); | |
403 | |
404 if (log.isDebugEnabled()) { | |
405 log.debug("*****************************************************"); | |
406 log.debug("Values of gap detection."); | |
407 log.debug("Start date: " + startDate.toString()); | |
408 log.debug("End date: " + endDate.toString()); | |
409 long diff = endDate.getTime() - startDate.getTime(); | |
410 log.debug("Time difference (in ms): " + diff); | |
411 log.debug("Time difference (in h): " + (diff/(1000*60*60))); | |
412 log.debug("Configured gap size (in %): " + GAP_SIZE); | |
413 log.debug("Calculated gap size (in ms): " + maxDiff); | |
414 log.debug("Calculated gap size (in h): " + (maxDiff/(1000*60*60))); | |
415 log.debug("*****************************************************"); | |
416 } | |
417 | |
418 Date last = startDate; | |
419 for (int i = startPos+1; i < endPos; i++) { | |
420 Result res = results[i]; | |
421 Date now = res.getDate("XORDINATE"); | |
422 | |
423 if ((now.getTime() - last.getTime()) > maxDiff) { | |
424 // add gap, add 1 minute to last date and add null value | |
425 log.info( | |
426 "Gap between " + | |
427 last.toString() + " and " + now.toString() | |
428 ); | |
429 last.setTime(last.getTime() + 60000); | |
430 ((TimeSeries) series).addOrUpdate(new Minute(last), null); | |
431 } | |
432 | |
433 last = now; | |
434 } | |
435 } | |
436 | |
437 | |
438 protected long calculateGapSize( | |
439 Date start, | |
440 Date end, | |
441 int startPos, | |
442 int endPos, | |
443 int gapID | |
444 ){ | |
445 long maxGap = (end.getTime() - start.getTime()) / 100 * GAP_SIZE; | |
446 long interval = getTimeGapValue(start, end, startPos, endPos, gapID); | |
447 | |
448 if (maxGap < interval) | |
449 maxGap = interval + 10; | |
450 | |
451 return maxGap; | |
452 } | |
453 | |
454 | |
455 protected long getTimeGapValue( | |
456 Date dStart, | |
457 Date dEnd, | |
458 int pStart, | |
459 int pEnd, | |
460 int gapID | |
461 ){ | |
462 long gap = 0; | |
463 | |
464 if (gapID < 0 || gapID >= 99) { | |
465 | |
466 if (gapID == -1) { | |
467 // no gaps in meshes | |
468 gap = NO_TIME_GAP; | |
469 } | |
470 else if (pEnd-pStart < 60) { | |
471 gap = (3/(pEnd-pStart)) * (dEnd.getTime() - dStart.getTime()); | |
472 } | |
473 } | |
474 else{ | |
475 Iterator it = timeGaps.iterator(); | |
476 | |
477 while (it.hasNext()) { | |
478 TimeGap tempTimeGap = (TimeGap) it.next(); | |
479 | |
480 if (tempTimeGap.getKey() == gapID){ | |
481 String unit = tempTimeGap.getUnit(); | |
482 int gapValue = tempTimeGap.getValue(); | |
483 | |
484 if (unit.equals(TimeGap.TIME_UNIT_MINUTE)) { | |
485 gap = gapValue * TimeGap.MINUTE_IN_MILLIS; | |
486 } | |
487 else if (unit.equals(TimeGap.TIME_UNIT_HOUR)) { | |
488 gap = gapValue * TimeGap.HOUR_IN_MILLIS; | |
489 } | |
490 else if (unit.equals(TimeGap.TIME_UNIT_DAY)) { | |
491 gap = gapValue * TimeGap.DAY_IN_MILLIS; | |
492 } | |
493 else if (unit.equals(TimeGap.TIME_UNIT_WEEK)) { | |
494 gap = gapValue * TimeGap.WEEK_IN_MILLIS; | |
495 } | |
496 else if (unit.equals(TimeGap.TIME_UNIT_MONTH)) { | |
497 gap = gapValue * (TimeGap.DAY_IN_MILLIS *30); | |
498 } | |
499 else if (unit.equals(TimeGap.TIME_UNIT_YEAR)) { | |
500 gap = gapValue * (TimeGap.DAY_IN_MILLIS *365); | |
501 } | |
502 break; | |
503 } | |
504 } | |
505 } | |
506 | |
507 return gap; | |
508 } | |
509 } | |
510 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : |