Mercurial > dive4elements > gnv-client
diff gnv-artifacts/src/main/java/de/intevation/gnv/chart/TimeSeriesChart.java @ 540:80630520e25a
merged gnv-artifacts/0.4
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:13:49 +0200 |
parents | 4cbcc130cffc |
children | 83f8f98a4f11 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/chart/TimeSeriesChart.java Fri Sep 28 12:13:49 2012 +0200 @@ -0,0 +1,495 @@ +package de.intevation.gnv.chart; + +import de.intevation.gnv.artifacts.ressource.RessourceFactory; + +import de.intevation.gnv.geobackend.base.Result; + +import de.intevation.gnv.state.describedata.KeyValueDescibeData; + +import de.intevation.gnv.timeseries.gap.TimeGap; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.log4j.Logger; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartTheme; + +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.DateAxis; +import org.jfree.chart.axis.DateTickUnit; +import org.jfree.chart.axis.DateTickUnitType; +import org.jfree.chart.axis.TickUnits; +import org.jfree.chart.axis.TickUnitSource; +import org.jfree.chart.axis.ValueAxis; + +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; + +import org.jfree.data.general.Series; + +import org.jfree.data.time.Minute; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; + +/** + * @author Ingo Weinzierl (ingo.weinzierl@intevation.de) + */ +public class TimeSeriesChart +extends AbstractXYLineChart +{ + + private static final String DATE_FORMAT = "chart.timeseries.date.format"; + + public static final String DEFAULT_DATE_FORMAT = "dd-MMM-yyyy"; + + private static final long NO_TIME_GAP = Long.MAX_VALUE - 1000; + + private static Logger log = Logger.getLogger(TimeSeriesChart.class); + + + public TimeSeriesChart( + ChartLabels labels, + ChartTheme theme, + Collection parameters, + Collection measurements, + Collection dates, + Collection result, + Collection timeGaps, + Locale locale, + boolean linesVisible, + boolean shapesVisible + ) { + this.labels = labels; + this.theme = theme; + this.parameters = parameters; + this.measurements = measurements; + this.dates = dates; + this.resultSet = result; + this.timeGaps = timeGaps; + this.locale = locale; + this.PLOT_ORIENTATION = PlotOrientation.VERTICAL; + this.linesVisible = linesVisible; + this.shapesVisible = shapesVisible; + this.datasets = new HashMap(); + this.ranges = new HashMap(); + } + + + protected void initChart() { + chart = ChartFactory.createTimeSeriesChart( + labels.getTitle(), + labels.getDomainAxisLabel(), + null, + null, + true, + false, + false + ); + + XYPlot plot = (XYPlot) chart.getPlot(); + plot.setDomainAxis(0, new DateAxis( + labels.getDomainAxisLabel(), TimeZone.getDefault(), locale)); + } + + + protected void initData() { + log.debug("init data for timeseries chart"); + + String breakPoint1 = null; + String breakPoint2 = null; + String breakPoint3 = null; + + Iterator iter = resultSet.iterator(); + Result row = null; + String seriesName = null; + String parameter = null; + TimeSeries series = null; + + int idx = 0; + int startPos = 0; + int endPos = 0; + Date startDate = null; + Date endDate = null; + + Result[] results = + (Result[]) resultSet.toArray(new Result[resultSet.size()]); + + while (iter.hasNext()) { + row = (Result) iter.next(); + + // add current data to plot and prepare for next one + if (!row.getString("GROUP1").equals(breakPoint1) || + !row.getString("GROUP2").equals(breakPoint2) || + !row.getString("GROUP3").equals(breakPoint3) + ) { + log.debug("prepare data/plot for next dataset"); + + if(series != null) { + // add gaps before adding series to chart + startDate = results[startPos].getDate("XORDINATE"); + endDate = results[endPos-1].getDate("XORDINATE"); + addGaps(results,series,startDate,endDate,startPos,endPos); + addSeries(series, parameter, idx); + + startPos = endPos + 1; + } + + // prepare variables for next plot + breakPoint1 = row.getString("GROUP1"); + breakPoint2 = row.getString("GROUP2"); + breakPoint3 = row.getString("GROUP3"); + + seriesName = createSeriesName( + breakPoint1, + breakPoint2, + breakPoint3 + ); + parameter = findParameter(seriesName); + + log.debug("next dataset is '" + seriesName + "'"); + series = new TimeSeries(seriesName, Minute.class); + } + + addValue(row, series); + storeMaxRange(row.getDouble("YORDINATE"), parameter); + endPos++; + } + + if (startPos < results.length && endPos-1 < results.length) { + // add the last dataset if existing to plot and prepare its axis + startDate = results[startPos].getDate("XORDINATE"); + endDate = results[endPos-1].getDate("XORDINATE"); + addGaps(results, series, startDate, endDate, startPos, endPos); + addSeries(series, parameter, idx); + } + + addDatasets(); + } + + + protected void addValue(Result row, Series series) { + ((TimeSeries) series).addOrUpdate( + new Minute(row.getDate("XORDINATE")), + row.getDouble("YORDINATE") + ); + } + + + protected void addSeries(Series series, String parameter, int idx) { + log.debug("add series (" + parameter + ")to timeseries chart"); + + if (series == null) { + log.warn("no data to add"); + return; + } + + TimeSeriesCollection tsc = null; + + if (datasets.containsKey(parameter)) + tsc = (TimeSeriesCollection) datasets.get(parameter); + else + tsc = new TimeSeriesCollection(); + + tsc.addSeries((TimeSeries) series); + datasets.put(parameter, tsc); + } + + + protected void addDatasets() { + Iterator iter = parameters.iterator(); + XYPlot plot = chart.getXYPlot(); + int idx = 0; + + TimeSeriesCollection tsc = null; + KeyValueDescibeData data = null; + String key = null; + while (iter.hasNext()) { + data = (KeyValueDescibeData) iter.next(); + key = data.getValue(); + + if (datasets.containsKey(key)) { + tsc = (TimeSeriesCollection)datasets.get(key); + plot.setDataset(idx, tsc ); + log.debug("Added " + key + " parameter to plot."); + prepareAxis(key, idx); + adjustRenderer( + idx++, + tsc.getSeriesCount(), + linesVisible, + shapesVisible + ); + } + } + } + + + protected void localizeDomainAxis(Axis axis, Locale locale) { + ((ValueAxis)axis).setStandardTickUnits(createStandardDateTickUnits( + TimeZone.getDefault(), + locale)); + } + + + public static TickUnitSource createStandardDateTickUnits( + TimeZone zone, + Locale locale) + { + /* + * This method have been copied from JFreeChart's DateAxis class. + * DateFormat objects are hard coded in DateAxis and cannot be adjusted. + */ + if (zone == null) { + throw new IllegalArgumentException("Null 'zone' argument."); + } + if (locale == null) { + throw new IllegalArgumentException("Null 'locale' argument."); + } + TickUnits units = new TickUnits(); + + // date formatters + DateFormat f1 = new SimpleDateFormat("HH:mm:ss.SSS", locale); + DateFormat f2 = new SimpleDateFormat("HH:mm:ss", locale); + DateFormat f3 = new SimpleDateFormat("HH:mm", locale); + DateFormat f4 = new SimpleDateFormat("d-MMM, HH:mm", locale); + DateFormat f5 = new SimpleDateFormat("d-MMM yyyy", locale); + DateFormat f6 = new SimpleDateFormat("MMM-yyyy", locale); + DateFormat f7 = new SimpleDateFormat("yyyy", locale); + + f1.setTimeZone(zone); + f2.setTimeZone(zone); + f3.setTimeZone(zone); + f4.setTimeZone(zone); + f5.setTimeZone(zone); + f6.setTimeZone(zone); + f7.setTimeZone(zone); + + // milliseconds + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 1, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 5, + DateTickUnitType.MILLISECOND, 1, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 10, + DateTickUnitType.MILLISECOND, 1, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 25, + DateTickUnitType.MILLISECOND, 5, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 50, + DateTickUnitType.MILLISECOND, 10, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 100, + DateTickUnitType.MILLISECOND, 10, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 250, + DateTickUnitType.MILLISECOND, 10, f1)); + units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 500, + DateTickUnitType.MILLISECOND, 50, f1)); + + // seconds + units.add(new DateTickUnit(DateTickUnitType.SECOND, 1, + DateTickUnitType.MILLISECOND, 50, f2)); + units.add(new DateTickUnit(DateTickUnitType.SECOND, 5, + DateTickUnitType.SECOND, 1, f2)); + units.add(new DateTickUnit(DateTickUnitType.SECOND, 10, + DateTickUnitType.SECOND, 1, f2)); + units.add(new DateTickUnit(DateTickUnitType.SECOND, 30, + DateTickUnitType.SECOND, 5, f2)); + + // minutes + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 1, + DateTickUnitType.SECOND, 5, f3)); + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 2, + DateTickUnitType.SECOND, 10, f3)); + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 5, + DateTickUnitType.MINUTE, 1, f3)); + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 10, + DateTickUnitType.MINUTE, 1, f3)); + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 15, + DateTickUnitType.MINUTE, 5, f3)); + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 20, + DateTickUnitType.MINUTE, 5, f3)); + units.add(new DateTickUnit(DateTickUnitType.MINUTE, 30, + DateTickUnitType.MINUTE, 5, f3)); + + // hours + units.add(new DateTickUnit(DateTickUnitType.HOUR, 1, + DateTickUnitType.MINUTE, 5, f3)); + units.add(new DateTickUnit(DateTickUnitType.HOUR, 2, + DateTickUnitType.MINUTE, 10, f3)); + units.add(new DateTickUnit(DateTickUnitType.HOUR, 4, + DateTickUnitType.MINUTE, 30, f3)); + units.add(new DateTickUnit(DateTickUnitType.HOUR, 6, + DateTickUnitType.HOUR, 1, f3)); + units.add(new DateTickUnit(DateTickUnitType.HOUR, 12, + DateTickUnitType.HOUR, 1, f4)); + + // days + units.add(new DateTickUnit(DateTickUnitType.DAY, 1, + DateTickUnitType.HOUR, 1, f5)); + units.add(new DateTickUnit(DateTickUnitType.DAY, 2, + DateTickUnitType.HOUR, 1, f5)); + units.add(new DateTickUnit(DateTickUnitType.DAY, 7, + DateTickUnitType.DAY, 1, f5)); + units.add(new DateTickUnit(DateTickUnitType.DAY, 15, + DateTickUnitType.DAY, 1, f5)); + + // months + units.add(new DateTickUnit(DateTickUnitType.MONTH, 1, + DateTickUnitType.DAY, 1, f6)); + units.add(new DateTickUnit(DateTickUnitType.MONTH, 2, + DateTickUnitType.DAY, 1, f6)); + units.add(new DateTickUnit(DateTickUnitType.MONTH, 3, + DateTickUnitType.MONTH, 1, f6)); + units.add(new DateTickUnit(DateTickUnitType.MONTH, 4, + DateTickUnitType.MONTH, 1, f6)); + units.add(new DateTickUnit(DateTickUnitType.MONTH, 6, + DateTickUnitType.MONTH, 1, f6)); + + // years + units.add(new DateTickUnit(DateTickUnitType.YEAR, 1, + DateTickUnitType.MONTH, 1, f7)); + units.add(new DateTickUnit(DateTickUnitType.YEAR, 2, + DateTickUnitType.MONTH, 3, f7)); + units.add(new DateTickUnit(DateTickUnitType.YEAR, 5, + DateTickUnitType.YEAR, 1, f7)); + units.add(new DateTickUnit(DateTickUnitType.YEAR, 10, + DateTickUnitType.YEAR, 1, f7)); + units.add(new DateTickUnit(DateTickUnitType.YEAR, 25, + DateTickUnitType.YEAR, 5, f7)); + units.add(new DateTickUnit(DateTickUnitType.YEAR, 50, + DateTickUnitType.YEAR, 10, f7)); + units.add(new DateTickUnit(DateTickUnitType.YEAR, 100, + DateTickUnitType.YEAR, 20, f7)); + + return units; + } + + + protected String getMessage(Locale locale, String key, String def) { + return RessourceFactory.getInstance().getRessource(locale, key, def); + } + + + protected String createSeriesName( + String breakPoint1, + String breakPoint2, + String breakPoint3 + ) { + log.debug("create seriesname of timeseries chart"); + return findValueTitle(parameters, breakPoint1) + + " " + + findValueTitle(measurements, breakPoint2) + + "m"; + } + + + protected void addGaps( + Result[] results, + Series series, + Date startDate, + Date endDate, + int startPos, + int endPos + ) { + int gapID = results[startPos].getInteger("GAPID"); + long maxDiff = calculateGapSize( + startDate, endDate, startPos, endPos, gapID + ); + + Date last = startDate; + for (int i = startPos+1; i < endPos; i++) { + Result res = results[i]; + Date now = res.getDate("XORDINATE"); + + if ((now.getTime() - last.getTime()) > maxDiff) { + // add gap, add 1 minute to last date and add null value + log.info( + "Gap between " + + last.toString() + " and " + now.toString() + ); + last.setTime(last.getTime() + 60000); + ((TimeSeries) series).addOrUpdate(new Minute(last), null); + } + + last = now; + } + } + + + protected long calculateGapSize( + Date start, + Date end, + int startPos, + int endPos, + int gapID + ){ + long maxGap = (end.getTime() - start.getTime()) / 20; + long interval = getTimeGapValue(start, end, startPos, endPos, gapID); + + if (maxGap < interval) + maxGap = interval + 10; + + return maxGap; + } + + + protected long getTimeGapValue( + Date dStart, + Date dEnd, + int pStart, + int pEnd, + int gapID + ){ + long gap = 0; + + if (gapID < 0 || gapID >= 99) { + + if (gapID == -1) { + // no gaps in meshes + gap = NO_TIME_GAP; + } + else if (pEnd-pStart < 60) { + gap = (3/(pEnd-pStart)) * (dEnd.getTime() - dStart.getTime()); + } + } + else{ + Iterator it = timeGaps.iterator(); + + while (it.hasNext()) { + TimeGap tempTimeGap = (TimeGap) it.next(); + + if (tempTimeGap.getKey() == gapID){ + String unit = tempTimeGap.getUnit(); + int gapValue = tempTimeGap.getValue(); + + if (unit.equals(TimeGap.TIME_UNIT_MINUTE)) { + gap = gapValue * TimeGap.MINUTE_IN_MILLIS; + } + else if (unit.equals(TimeGap.TIME_UNIT_HOUR)) { + gap = gapValue * TimeGap.HOUR_IN_MILLIS; + } + else if (unit.equals(TimeGap.TIME_UNIT_DAY)) { + gap = gapValue * TimeGap.DAY_IN_MILLIS; + } + else if (unit.equals(TimeGap.TIME_UNIT_WEEK)) { + gap = gapValue * TimeGap.WEEK_IN_MILLIS; + } + else if (unit.equals(TimeGap.TIME_UNIT_MONTH)) { + gap = gapValue * (TimeGap.DAY_IN_MILLIS *30); + } + else if (unit.equals(TimeGap.TIME_UNIT_YEAR)) { + gap = gapValue * (TimeGap.DAY_IN_MILLIS *365); + } + break; + } + } + } + + return gap; + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :