diff flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java @ 2242:7e8e1d5384c0

Further refactoring of XYChartGenerator / ChartGenerator with the result, that timerange charts are now able to display lines. flys-artifacts/trunk@3890 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Fri, 03 Feb 2012 09:39:22 +0000
parents 23c7c51df772
children 6aeb71517136
line wrap: on
line diff
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java	Fri Feb 03 09:35:46 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java	Fri Feb 03 09:39:22 2012 +0000
@@ -2,6 +2,10 @@
 
 import java.awt.Color;
 import java.awt.Font;
+import java.awt.Paint;
+import java.awt.TexturePaint;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -9,6 +13,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.TreeMap;
 import java.util.SortedMap;
 
@@ -21,8 +26,12 @@
 
 import org.jfree.chart.JFreeChart;
 import org.jfree.chart.LegendItem;
+import org.jfree.chart.LegendItemCollection;
 import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
 import org.jfree.data.Range;
+import org.jfree.data.general.Series;
 import org.jfree.data.xy.XYDataset;
 
 import de.intevation.artifacts.Artifact;
@@ -40,6 +49,10 @@
 
 import de.intevation.flys.artifacts.FLYSArtifact;
 import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.jfree.EnhancedLineAndShapeRenderer;
+import de.intevation.flys.jfree.StableXYDifferenceRenderer;
+import de.intevation.flys.jfree.StyledAreaSeriesCollection;
+import de.intevation.flys.jfree.StyledXYSeries;
 import de.intevation.flys.utils.FLYSUtils;
 import de.intevation.flys.utils.ThemeAccess;
 
@@ -113,8 +126,20 @@
 
         void addDataset(XYDataset dataset);
 
+        XYDataset[] getDatasets();
+
         boolean isEmpty();
 
+        void setRange(Range range);
+
+        Range getRange();
+
+        boolean isArea(XYDataset dataset);
+
+        void setPlotAxisIndex(int idx);
+
+        int getPlotAxisIndex();
+
     } // end of AxisDataset interface
 
 
@@ -146,6 +171,27 @@
     protected abstract YAxisWalker getYAxisWalker();
 
 
+    protected abstract Series getSeriesOf(XYDataset dataset, int idx);
+
+
+    /**
+     * This method is used to set the range of the X axis at index <i>axis</i>.
+     *
+     * @param axis The index of an X axis.
+     * @param range The new range for the X axis at index <i>axis</i>.
+     */
+    protected abstract void setXRange(int axis, Range range);
+
+
+    /**
+     * This method is used to set the range of the Y axis at index <i>axis</i>.
+     *
+     * @param axis The index of an Y axis.
+     * @param range The new range for the Y axis at index <i>axis</i>.
+     */
+    protected abstract void setYRange(int axis, Range range);
+
+
     /**
      * Returns the default title of a chart.
      *
@@ -794,8 +840,18 @@
 
         Range[] xyRanges = ChartHelper.getRanges(dataset);
 
+        if (xyRanges == null) {
+            logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
+            return;
+        }
+
         if (visible) {
-            logger.debug("Add new AxisDataset at index: " + idx);
+            if (logger.isDebugEnabled()) {
+                logger.debug("Add new AxisDataset at index: " + idx);
+                logger.debug("X extent: " + xyRanges[0]);
+                logger.debug("Y extent: " + xyRanges[1]);
+            }
+
             axisDataset.addDataset(dataset);
             combineXRanges(xyRanges[0], 0);
         }
@@ -1033,6 +1089,208 @@
 
 
     /**
+     * Add datasets stored in instance variable <i>datasets</i> to plot.
+     * <i>datasets</i> actually stores instances of AxisDataset, so each of this
+     * datasets is mapped to a specific axis as well.
+     *
+     * @param plot plot to add datasets to.
+     */
+    protected void addDatasets(XYPlot plot) {
+        // AxisDatasets are sorted, but some might be empty.
+        // Thus, generate numbering on the fly.
+        int axisIndex    = 0;
+        int datasetIndex = 0;
+
+        for (Map.Entry<Integer, AxisDataset> entry: datasets.entrySet()) {
+            if (!entry.getValue().isEmpty()) {
+                // Add axis and range information.
+                AxisDataset axisDataset = entry.getValue();
+                NumberAxis    axis      = createYAxis(entry.getKey());
+
+                plot.setRangeAxis(axisIndex, axis);
+
+                if (axis.getAutoRangeIncludesZero()) {
+                    axisDataset.setRange(
+                        Range.expandToInclude(axisDataset.getRange(), 0d));
+                }
+
+                setYRange(axisIndex, expandPointRange(axisDataset.getRange()));
+
+                // Add contained datasets, mapping to axis.
+                for (XYDataset dataset: axisDataset.getDatasets()) {
+                    plot.setDataset(datasetIndex, dataset);
+                    plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
+
+                    applyThemes(plot, (XYDataset) dataset,
+                        datasetIndex,
+                        axisDataset.isArea((XYDataset) dataset));
+
+                    datasetIndex++;
+                }
+
+                axisDataset.setPlotAxisIndex(axisIndex);
+                axisIndex++;
+            }
+        }
+    }
+
+
+        /**
+     * @param idx "index" of dataset/series (first dataset to be drawn has
+     *            index 0), correlates with renderer index.
+     * @param isArea true if the series describes an area and shall be rendered
+     *                as such.
+     * @return idx increased by number of items addded.
+     */
+    protected void applyThemes(
+        XYPlot    plot,
+        XYDataset series,
+        int       idx,
+        boolean   isArea
+    ) {
+        if (isArea) {
+            applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx);
+        }
+        else {
+            applyLineTheme(plot, series, idx);
+        }
+    }
+
+
+    /**
+     * This method applies the themes defined in the series itself. Therefore,
+     * <i>StyledXYSeries.applyTheme()</i> is called, which modifies the renderer
+     * for the series.
+     *
+     * @param plot The plot.
+     * @param dataset The XYDataset which needs to support Series objects.
+     * @param idx The index of the renderer / dataset.
+     */
+    protected void applyLineTheme(XYPlot plot, XYDataset dataset, int idx) {
+        LegendItemCollection lic  = new LegendItemCollection();
+        LegendItemCollection anno = plot.getFixedLegendItems();
+
+        Font legendFont = createLegendLabelFont();
+
+        XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
+
+        for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
+            Series series = getSeriesOf(dataset, s);
+
+            if (series instanceof StyledXYSeries) {
+                ((StyledXYSeries) series).applyTheme(renderer, s);
+            }
+
+            // special case: if there is just one single item, we need to enable
+            // points for this series, otherwise we would not see anything in
+            // the chart area.
+            if (series.getItemCount() == 1) {
+                renderer.setSeriesShapesVisible(s, true);
+            }
+
+            LegendItem legendItem = renderer.getLegendItem(idx, s);
+            if (legendItem != null) {
+                legendItem.setLabelFont(legendFont);
+                lic.add(legendItem);
+            }
+            else {
+                logger.warn("Could not get LegentItem for renderer: "
+                    + idx + ", series-idx " + s);
+            }
+        }
+
+        if (anno != null) {
+            lic.addAll(anno);
+        }
+
+        plot.setFixedLegendItems(lic);
+
+        plot.setRenderer(idx, renderer);
+    }
+
+
+    /**
+     * @param plot The plot.
+     * @param area A StyledAreaSeriesCollection object.
+     * @param idx The index of the dataset.
+     *
+     * @return
+     */
+    protected void applyAreaTheme(
+        XYPlot                     plot,
+        StyledAreaSeriesCollection area,
+        int                        idx
+    ) {
+        LegendItemCollection lic  = new LegendItemCollection();
+        LegendItemCollection anno = plot.getFixedLegendItems();
+
+        Font legendFont = createLegendLabelFont();
+
+        logger.debug("Registering an 'area'renderer at idx: " + idx);
+
+        StableXYDifferenceRenderer dRenderer =
+            new StableXYDifferenceRenderer();
+
+        if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
+            dRenderer.setPositivePaint(createTransparentPaint());
+        }
+
+        plot.setRenderer(idx, dRenderer);
+
+        area.applyTheme(dRenderer);
+
+        LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
+        if (legendItem != null) {
+            legendItem.setLabelFont(legendFont);
+            lic.add(legendItem);
+        }
+        else {
+            logger.warn("Could not get LegentItem for renderer: "
+                + idx + ", series-idx " + 0);
+        }
+
+        if (anno != null) {
+            lic.addAll(anno);
+        }
+
+        plot.setFixedLegendItems(lic);
+    }
+
+
+    /**
+     * Expands a given range if it collapses into one point.
+     *
+     * @param Range to be expanded if upper == lower bound.
+     */
+    private Range expandPointRange(Range range) {
+        if (range != null && range.getLowerBound() == range.getUpperBound()) {
+            return ChartHelper.expandRange(range, 5);
+        }
+        return range;
+    }
+
+
+    /**
+     * Creates a new instance of EnhancedLineAndShapeRenderer.
+     *
+     * @param plot The plot which is set for the new renderer.
+     * @param idx This value is not used in the current implementation.
+     *
+     * @return a new instance of EnhancedLineAndShapeRenderer.
+     */
+    protected XYLineAndShapeRenderer createRenderer(XYPlot plot, int idx) {
+        logger.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx);
+
+        EnhancedLineAndShapeRenderer r =
+            new EnhancedLineAndShapeRenderer(true, false);
+
+        r.setPlot(plot);
+
+        return r;
+    }
+
+
+    /**
      * Creates a new instance of <i>IdentifiableNumberAxis</i>.
      *
      * @param idx The index of the new axis.
@@ -1106,6 +1364,21 @@
     }
 
 
+    /**
+     * Returns a transparently textured paint.
+     *
+     * @return a transparently textured paint.
+     */
+    protected static Paint createTransparentPaint() {
+        // TODO why not use a transparent color?
+        BufferedImage texture = new BufferedImage(
+            1, 1, BufferedImage.TYPE_4BYTE_ABGR);
+
+        return new TexturePaint(
+            texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
+    }
+
+
     protected void preparePDFContext(CallContext context) {
         int[] dimension = getExportDimension();
 

http://dive4elements.wald.intevation.org