view gnv-artifacts/src/main/java/de/intevation/gnv/chart/TimeSeriesChart.java @ 605:e8ebdbc7f1e3

First step of removing the cache blob. The static part of the describe document will be created by using the input data stored at each state. Some TODOs left (see ChangeLog). gnv-artifacts/trunk@671 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 09 Feb 2010 14:27:55 +0000
parents 4cbcc130cffc
children 83f8f98a4f11
line wrap: on
line source
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 :

http://dive4elements.wald.intevation.org