diff gnv-artifacts/src/main/java/de/intevation/gnv/chart/AbstractXYLineChart.java @ 1119:7c4f81f74c47

merged gnv-artifacts
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:00 +0200
parents f953c9a559d8
children
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/AbstractXYLineChart.java	Fri Sep 28 12:14:00 2012 +0200
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2010 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+
+package de.intevation.gnv.chart;
+
+import de.intevation.gnv.geobackend.base.Result;
+
+import de.intevation.gnv.state.describedata.KeyValueDescibeData;
+
+import java.awt.Color;
+
+import java.awt.geom.Ellipse2D;
+
+import java.text.NumberFormat;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+
+import org.jfree.chart.axis.Axis;
+import org.jfree.chart.axis.AxisLocation;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.NumberTickUnit;
+
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
+
+import org.jfree.chart.title.TextTitle;
+
+import org.jfree.data.Range;
+
+import org.jfree.data.general.Series;
+
+import org.jfree.data.xy.XYDataset;
+
+import org.jfree.ui.RectangleInsets;
+
+/**
+ * This abstract class defines some methods to adjust chart settings after its
+ * creation.
+ *
+ * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
+ */
+public abstract class AbstractXYLineChart
+extends               AbstractChart
+{
+    /**
+     * Constant field used to expand the area between data and chart border. Its
+     * value is {@value}.<br>
+     * A value of 0.05 equals 5 percent.
+     */
+    public static final double LOWER_MARGIN = 0.05D;
+
+    /**
+     * Constant field used to expand the area between data and chart border. Its
+     * value is {@value}.<br>
+     * A value of 0.05 equals 5 percent.
+     */
+    public static final double UPPER_MARGIN = 0.05D;
+
+    /**
+     * Logger used to log with log4j.
+     */
+    private static Logger log      = Logger.getLogger(AbstractXYLineChart.class);
+
+    /**
+     * Field of supported colors used for lines and data points in charts.
+     */
+    protected static Color[] COLOR = {
+        Color.black, Color.red, Color.green, Color.blue, Color.yellow,
+        Color.gray, Color.orange, Color.pink, Color.cyan
+    };
+
+    /**
+     * Static field to remember the index of the previously used color.
+     */
+    protected static int nextColor = 0;
+
+    /**
+     * Default <code>PlotOrientation</code>.
+     */
+    protected PlotOrientation PLOT_ORIENTATION = PlotOrientation.VERTICAL;
+
+    /**
+     * Map to store datasets for each parameter.
+     */
+    protected Map datasets;
+
+    /**
+     * Map to store max ranges of each parameter (axis.setAutoRange(true)
+     * doesn't seem to work */
+    protected Map ranges;
+
+    /**
+     * This method is called by <code>Chart</code> to bring the data into the
+     * right form fitting to JFreeChart objects.
+     */
+    protected abstract void initData();
+
+    /**
+     * Add a value of <code>row</code> to <code>series</code>.
+     *
+     * @param row <code>Result</code> Object returned from database. Contains
+     * a value used to add to <code>series</code>
+     * @param series A JFreeChart Series object.
+     */
+    protected abstract void addValue(Result row, Series series);
+
+    /**
+     * Add <code>series</code> to JFreeChart's Dataset object currently which is
+     * processing.
+     *
+     * @param series Series to add.
+     * @param label Label used show in legend.
+     * @param idx Currently not used.
+     */
+    protected abstract void addSeries(Series series, String label, int idx);
+
+    /**
+     * Abstract method which is called by <code>Chart</code> interface after
+     * chart creation. It turns an axis' label into a locale specific format.
+     *
+     * @param axis Axis to adjust.
+     * @param locale java.util.Locale object used specify the format.
+     */
+    protected abstract void localizeDomainAxis(Axis axis, Locale locale);
+
+    /**
+     * Abstract method to create a label for a series of parameters.
+     *
+     * @param breakPoint1 Identifier returned from database. These identifier
+     * are used to identify the results from database which are all stored in
+     * one big java.util.Collection.
+     * @param breakPoint2 Identifier returned from database.
+     * @param breakPoint3 Identifier returned from database.
+     *
+     * @return Concatinated string of parameter name and measurement.
+     */
+    protected abstract String createSeriesName(
+        String breakPoint1,
+        String breakPoint2,
+        String breakPoint3
+    );
+
+
+    /**
+     * @see de.intevation.gnv.chart.Chart#generateChart()
+     */
+    public JFreeChart generateChart() {
+        log.debug("generate XYLineChart");
+        nextColor = 0;
+
+        if (chart != null)
+            return chart;
+
+        initChart();
+
+        chart.addSubtitle(new TextTitle(labels.getSubtitle()));
+
+        theme.apply(chart);
+        initData();
+
+        adjustPlot((XYPlot)chart.getPlot());
+
+        return chart;
+    }
+
+
+    protected void initChart() {
+        chart = ChartFactory.createXYLineChart(
+            labels.getTitle(),
+            labels.getDomainAxisLabel(),
+            null,
+            null,
+            PLOT_ORIENTATION,
+            true,
+            false,
+            false
+        );
+    }
+
+
+    /**
+     * Method used to adjust the axes after chart generation. Methods for i18n
+     * support ({@link #localizeDomainAxis} and {@link #localizeRangeAxis}) are
+     * called and axes of this series are expanded.
+     *
+     * @param seriesKey Identifier of an axis which have to be adjusted.
+     * @param idx Set the axis identified by <code>seriesKey</code> to position
+     * <code>idx</code>.
+     */
+    protected void prepareAxis(String seriesKey, int idx) {
+        log.debug("prepare axis of xychart");
+
+        XYPlot plot      = chart.getXYPlot();
+        Axis xAxis       = plot.getDomainAxis();
+        NumberAxis yAxis = new NumberAxis(seriesKey);
+
+        localizeDomainAxis(xAxis, locale);
+        localizeRangeAxis(yAxis, locale);
+
+        // litte workarround to adjust the max range of axes.
+        // NumberAxis.setAutoRange(true) doesn't seem to work properly.
+        Range yRange = (Range) ranges.get(seriesKey);
+        double lo    = yRange.getLowerBound();
+        double hi    = yRange.getUpperBound();
+
+        if (lo == hi) {
+            yRange = new Range(
+                lo - (lo / 100 * LOWER_MARGIN),
+                hi + (hi / 100 * UPPER_MARGIN));
+        }
+        else {
+            yRange = Range.expand(yRange, LOWER_MARGIN, UPPER_MARGIN);
+        }
+        yAxis.setRange(yRange);
+        log.debug("Max Range of dataset is: " + yRange.toString());
+
+        if (seriesKey.contains("richtung")) {
+            yAxis.setTickUnit(new NumberTickUnit(30.0));
+            yAxis.setUpperBound(360.0);
+            yAxis.setLowerBound(0.0);
+        }
+        else {
+            yAxis.setFixedDimension(10.0);
+            yAxis.setAutoRangeIncludesZero(false);
+        }
+
+        plot.setRangeAxis(idx, yAxis);
+        yAxis.configure();
+
+        if (idx % 2 != 0)
+            plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_RIGHT);
+        else
+            plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_LEFT);
+
+        plot.mapDatasetToRangeAxis(idx, idx);
+    }
+
+
+    /**
+     * Method to adjust the rendering of a series in a chart. Line color and
+     * symbols of vertices are configured here.
+     *
+     * @param idx Position of the renderer.
+     * @param seriesCount Maximum number of series in this chart.
+     * @param renderLines Lines are displayed if true, otherwise they are not.
+     * @param renderShapes Vertices are displayed if true, otherwise they are
+     * not.
+     */
+    protected void adjustRenderer(
+        int     idx,
+        int     seriesCount,
+        boolean renderLines,
+        boolean renderShapes
+    ) {
+        log.debug("Adjust render of series");
+        XYLineAndShapeRenderer renderer = null;
+        XYPlot                 plot     = chart.getXYPlot();
+
+        try {
+            renderer = (XYLineAndShapeRenderer)((XYLineAndShapeRenderer)
+                (plot.getRenderer())).clone();
+        }
+        catch (CloneNotSupportedException cnse) {
+            log.warn("Error while cloning renderer.", cnse);
+            renderer = new XYLineAndShapeRenderer(renderLines, renderShapes);
+            renderer.setBaseShape(new Ellipse2D.Double(-2,-2,4,4));
+        }
+
+        for (int i = 0; i < seriesCount; i++) {
+            renderer.setSeriesShape(i, renderer.getSeriesShape(0));
+            renderer.setSeriesPaint(i, COLOR[nextColor() % COLOR.length]);
+            renderer.setSeriesShapesVisible(i, renderShapes);
+            renderer.setSeriesLinesVisible(i, renderLines);
+        }
+        plot.setRenderer(idx, renderer);
+    }
+
+
+    /**
+     * @return Index of the next color
+     */
+    protected static synchronized int nextColor() {
+        return nextColor++;
+    }
+
+
+    /**
+     * Method to adjust the plot rendering. Disable horizontal grid lines if
+     * <code>plot</code> contains only a single y-axis.
+     *
+     * @param plot JFreeChart Plot object to be adjusted.
+     */
+    protected void adjustPlot(XYPlot plot) {
+        if (plot.getRangeAxisCount() > 1)
+            plot.setRangeGridlinesVisible(false);
+
+        plot.setAxisOffset(new RectangleInsets(0, 0, 0, 15));
+    }
+
+
+    /**
+     * Abstract method which is called after chart creation. It turns an
+     * axis' label into a locale specific format.
+     *
+     * @param axis Axis to adjust.
+     * @param locale java.util.Locale object used specify the format.
+     *
+     */
+    protected void localizeRangeAxis(Axis axis, Locale locale) {
+        if (locale == null)
+            return;
+
+        log.debug(
+            "Set language of axis [" + axis.getLabel() + "] " +
+            "to " + locale.toString()
+        );
+
+        NumberFormat format = NumberFormat.getInstance(locale);
+        ((NumberAxis) axis).setNumberFormatOverride(format);
+    }
+
+
+    /**
+     * Return the maximum y-range of <code>dataset</code>.
+     *
+     * @param dataset Dataset to be scaned.
+     *
+     * @return JFreeChart Range object containing min and max y-value.
+     */
+    public Range getMaxRangeOfDataset(XYDataset dataset) {
+        int    seriesCount = dataset.getSeriesCount();
+        double upper       = Double.NEGATIVE_INFINITY;
+        double lower       = Double.POSITIVE_INFINITY;
+
+        for (int i = 0; i < seriesCount; i++) {
+            int itemCount = dataset.getItemCount(i);
+
+            for (int j = 0; j < itemCount; j++) {
+                Number num = dataset.getY(i, j);
+
+                if (num != null) {
+                    double y = num.doubleValue();
+                    lower    = y < lower ? y : lower;
+                    upper    = y > upper ? y : upper;
+                }
+            }
+        }
+
+        return new Range(lower, upper);
+    }
+
+
+    /**
+     * Return the maximum y-range of <code>dataset</code> with a margin of
+     * <code>percent</code> percent.
+     *
+     * @param dataset Dataset to be scaned.
+     * @param percent Percent used to expand the range.
+     * @return JFreeChart Range object containing min and max y-value with a
+     * margin.
+     */
+    public Range getMaxRangeOfDatasetWithMargin(
+        XYDataset dataset,
+        double    percent
+    ) {
+        Range range   = getMaxRangeOfDataset(dataset);
+        double length = range.getLength();
+        double upper  = range.getUpperBound() + length /100 * percent;
+        double lower  = range.getLowerBound() - length /100 * percent;
+
+        return new Range(lower, upper);
+    }
+
+
+    /**
+     * Method to find a parameter specified by its value.
+     *
+     * @param label Search string.
+     *
+     * @return Value of a parameter with the given label.
+     */
+    protected String findParameter(String label) {
+        Iterator iter = parameters.iterator();
+
+        while (iter.hasNext()) {
+            KeyValueDescibeData data = (KeyValueDescibeData) iter.next();
+            String              key  = data.getValue();
+
+            if (label.indexOf(key) > -1)
+                return key;
+        }
+
+        return label;
+    }
+
+
+    /**
+     * Method to find a description of a given collection of values.
+     *
+     * @param values Collection to be scaned.
+     * @param id Identifier and search string of the searched value.
+     *
+     * @return title
+     */
+    protected String findValueTitle(Collection values, String id) {
+        log.debug("find description of dataset");
+
+        if (values != null){
+            Iterator it = values.iterator();
+            while (it.hasNext()) {
+                KeyValueDescibeData data = (KeyValueDescibeData) it.next();
+
+                if (id.equals(data.getKey()))
+                    return data.getValue();
+            }
+        }
+        return "";
+    }
+
+
+    /**
+     * Method to store the maximum range. Since we need to adjust the range of
+     * each range axis, we have to memorize its range - each parameter uses an
+     * own range axis.
+     *
+     * @param ranges Map where ranges of each axis will be stored in.
+     * @param value A given value on an axis.
+     * @param parameter Parameter name which belongs to <code>value</code>.
+     */
+    protected void storeMaxRange(Map ranges, double value, String parameter) {
+        Range  range     = null;
+
+        range = ranges.containsKey(parameter)
+            ? (Range) ranges.get(parameter)
+            : new Range(value, value);
+
+        double lower = range.getLowerBound();
+        double upper = range.getUpperBound();
+
+        lower = value < lower ? value : lower;
+        upper = value > upper ? value : upper;
+
+        ranges.put(parameter, new Range(lower, upper));
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :

http://dive4elements.wald.intevation.org