view gnv-artifacts/src/main/java/de/intevation/gnv/chart/TimeSeriesChart.java @ 629:d08b9ba148c5

Implemented logic to adjust number of bins corresponding to user input. gnv-artifacts/trunk@706 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Wed, 24 Feb 2010 14:30:52 +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