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