# HG changeset patch # User Ingo Weinzierl # Date 1328261962 0 # Node ID 7e8e1d5384c078d6cd958f6539c27961d8e688c1 # Parent 2b232871ba28c3133053348a1bb64c87fff1091d 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 diff -r 2b232871ba28 -r 7e8e1d5384c0 flys-artifacts/ChangeLog --- a/flys-artifacts/ChangeLog Fri Feb 03 09:35:46 2012 +0000 +++ b/flys-artifacts/ChangeLog Fri Feb 03 09:39:22 2012 +0000 @@ -1,3 +1,14 @@ +2012-02-03 Ingo Weinzierl + + * src/main/java/de/intevation/flys/exports/XYChartGenerator.java, + src/main/java/de/intevation/flys/exports/ChartGenerator.java: Refactoring: + moved addDatasets(), applyThemes() and some other methods into + ChartGenerator; enhanced the AxisDataset interface. + + * src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java: + Implemented necessary abstract methods and improved internal AxisDataset + class (added new methods). + 2012-02-03 Ingo Weinzierl * src/main/java/de/intevation/flys/exports/ChartHelper.java: Added a diff -r 2b232871ba28 -r 7e8e1d5384c0 flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java --- 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 axis. + * + * @param axis The index of an X axis. + * @param range The new range for the X axis at index axis. + */ + protected abstract void setXRange(int axis, Range range); + + + /** + * This method is used to set the range of the Y axis at index axis. + * + * @param axis The index of an Y axis. + * @param range The new range for the Y axis at index axis. + */ + 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 datasets to plot. + * datasets 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 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, + * StyledXYSeries.applyTheme() 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 IdentifiableNumberAxis. * * @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(); diff -r 2b232871ba28 -r 7e8e1d5384c0 flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java --- a/flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java Fri Feb 03 09:35:46 2012 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java Fri Feb 03 09:39:22 2012 +0000 @@ -11,11 +11,11 @@ import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; -import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.XYPlot; import org.jfree.data.Range; import org.jfree.data.time.TimeSeriesCollection; +import org.jfree.data.general.Series; import org.jfree.data.xy.XYDataset; @@ -60,11 +60,49 @@ @Override + public XYDataset[] getDatasets() { + return (XYDataset[]) + datasets.toArray(new XYDataset[datasets.size()]); + } + + + @Override public boolean isEmpty() { return datasets.isEmpty(); } + @Override + public void setRange(Range range) { + this.range = range; + } + + + @Override + public Range getRange() { + return range; + } + + + @Override + public void setPlotAxisIndex(int plotAxisIndex) { + this.plotAxisIndex = plotAxisIndex; + } + + + @Override + public int getPlotAxisIndex() { + return plotAxisIndex; + } + + + @Override + public boolean isArea(XYDataset dataset) { + logger.warn("This AxisDataset doesn't support Areas yet!"); + return false; + } + + protected void mergeRanges(TimeSeriesCollection dataset) { logger.debug("Range after merging: " + range); @@ -84,6 +122,8 @@ protected Map xRanges; + protected Map yRanges; + /** @@ -93,6 +133,7 @@ super(); xRanges = new HashMap(); + yRanges = new HashMap(); } @@ -124,6 +165,24 @@ } + @Override + protected Series getSeriesOf(XYDataset dataset, int idx) { + return ((TimeSeriesCollection) dataset).getSeries(idx); + } + + + @Override + protected void setXRange(int axis, Range range) { + xRanges.put(Integer.valueOf(axis), range); + } + + + @Override + protected void setYRange(int axis, Range range) { + yRanges.put(Integer.valueOf(axis), range); + } + + /** * This method creates new instances of TimeseriesAxisDataset. * @@ -153,10 +212,5 @@ xRanges.put(index, range); } } - - - protected void addDatasets(XYPlot plot) { - logger.warn("TODO: IMPLEMENT ME!"); - } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : diff -r 2b232871ba28 -r 7e8e1d5384c0 flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java --- a/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java Fri Feb 03 09:35:46 2012 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java Fri Feb 03 09:39:22 2012 +0000 @@ -5,21 +5,13 @@ import java.awt.Font; import java.awt.Paint; import java.awt.Stroke; -import java.awt.TexturePaint; - -import java.awt.geom.Rectangle2D; - -import java.awt.image.BufferedImage; - import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashMap; -import java.util.TreeMap; import java.util.List; import java.util.Map; -import java.util.SortedMap; import org.w3c.dom.Document; @@ -27,7 +19,6 @@ import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; -import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.annotations.XYBoxAnnotation; import org.jfree.chart.annotations.XYLineAnnotation; @@ -36,8 +27,8 @@ import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.PlotOrientation; 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.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.jfree.data.xy.XYDataset; @@ -45,13 +36,9 @@ import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; - import de.intevation.artifactdatabase.state.Facet; - -import de.intevation.flys.jfree.EnhancedLineAndShapeRenderer; import de.intevation.flys.jfree.FLYSAnnotation; -import de.intevation.flys.jfree.StableXYDifferenceRenderer; import de.intevation.flys.jfree.StickyAxisAnnotation; import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation; import de.intevation.flys.jfree.StyledAreaSeriesCollection; @@ -126,12 +113,32 @@ addDataset(new XYSeriesCollection(series)); } + + @Override + public void setRange(Range range) { + this.range = range; + } + + + @Override + public Range getRange() { + return range; + } + + + @Override + public XYDataset[] getDatasets() { + return (XYDataset[]) + datasets.toArray(new XYDataset[datasets.size()]); + } + public void addArea(StyledAreaSeriesCollection series) { this.datasets.add(series); } /** True if to be renedered as area. */ - public boolean isArea(XYSeriesCollection series) { + @Override + public boolean isArea(XYDataset series) { return (series instanceof StyledAreaSeriesCollection); } @@ -141,16 +148,19 @@ } /** True if no datasets given. */ + @Override public boolean isEmpty() { return this.datasets.isEmpty(); } /** Set the 'real' axis index that this axis is mapped to. */ + @Override public void setPlotAxisIndex(int axisIndex) { this.plotAxisIndex = axisIndex; } /** Get the 'real' axis index that this axis is mapped to. */ + @Override public int getPlotAxisIndex() { return this.plotAxisIndex; } @@ -241,6 +251,24 @@ @Override + protected Series getSeriesOf(XYDataset dataset, int idx) { + return ((XYSeriesCollection) dataset).getSeries(idx); + } + + + @Override + protected void setXRange(int axis, Range range) { + xRanges.put(Integer.valueOf(axis), range); + } + + + @Override + protected void setYRange(int axis, Range range) { + yRanges.put(Integer.valueOf(axis), range); + } + + + @Override protected AxisDataset createAxisDataset(int idx) { logger.debug("Create new XYAxisDataset for index: " + idx); return new XYAxisDataset(idx); @@ -288,43 +316,6 @@ /** - * Add datasets to plot. - * @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 entry: datasets.entrySet()) { - if (!entry.getValue().isEmpty()) { - // Add axis and range information. - XYAxisDataset axisDataset = (XYAxisDataset) entry.getValue(); - NumberAxis axis = createYAxis(entry.getKey()); - - plot.setRangeAxis(axisIndex, axis); - if (axis.getAutoRangeIncludesZero()) { - axisDataset.range = Range.expandToInclude(axisDataset.range, 0d); - } - yRanges.put(axisIndex, expandPointRange(axisDataset.range)); - - // Add contained datasets, mapping to axis. - for (XYDataset dataset: axisDataset.datasets) { - plot.setDataset(datasetIndex, dataset); - plot.mapDatasetToRangeAxis(datasetIndex, axisIndex); - applyThemes(plot, (XYSeriesCollection) dataset, - datasetIndex, - axisDataset.isArea((XYSeriesCollection)dataset)); - datasetIndex++; - } - axisDataset.setPlotAxisIndex(axisIndex); - axisIndex++; - } - } - } - - - /** * Registers an area to be drawn. * @param area Area to be drawn. * @param index 'axis index' @@ -356,12 +347,15 @@ * @param visible whether or not the data should be plotted. */ public void addAxisSeries(XYSeries series, int index, boolean visible) { - addAxisDataset(new XYSeriesCollection(series), index, visible); - if (series == null) { return; } + logger.debug("Y Range of XYSeries: " + + series.getMinY() + " | " + series.getMaxY()); + + addAxisDataset(new XYSeriesCollection(series), index, visible); + XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index); if (!visible) { @@ -423,18 +417,6 @@ /** - * 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 expandRange(range, 5); - } - return range; - } - - - /** * Expands X axes if only a point is shown. */ private void preparePointRanges(XYPlot plot) { @@ -444,30 +426,13 @@ Range r = xRanges.get(key); if (r != null && r.getLowerBound() == r.getUpperBound()) { - xRanges.put(key, expandRange(r, 5)); + setXRange(key, ChartHelper.expandRange(r, 5)); } } } /** - * Expand range by percent. - */ - public static Range expandRange(Range range, double percent) { - if (range == null) { - return null; - } - - double value = range.getLowerBound(); - double expand = Math.abs(value / 100 * percent); - - return expand != 0 - ? new Range(value-expand, value+expand) - : new Range(-0.01 * percent, 0.01 * percent); - } - - - /** * This method zooms the plot to the specified ranges in the attribute * document or to the ranges specified by the min/max values in the * datasets. Note: We determine the range manually if no zoom ranges @@ -922,121 +887,6 @@ /** - * @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 int applyThemes( - XYPlot plot, - XYSeriesCollection series, - int idx, - boolean isArea - ) { - LegendItemCollection lic = new LegendItemCollection(); - LegendItemCollection anno = plot.getFixedLegendItems(); - - Font legendFont = createLegendLabelFont(); - - int retidx = idx; - - if (isArea) { - logger.debug("Registering an 'area'renderer at idx: " + idx); - StyledAreaSeriesCollection area = (StyledAreaSeriesCollection) series; - - 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); - return retidx + 1; - } - - XYLineAndShapeRenderer renderer = getRenderer(plot, idx); - - for (int s = 0, num = series.getSeriesCount(); s < num; s++) { - XYSeries serie = series.getSeries(s); - - if (serie instanceof StyledXYSeries) { - ((StyledXYSeries) serie).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 (serie.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); - } - // TODO: why that? isnt renderer set per dataset not per series? - retidx++; - } - - if (anno != null) { - lic.addAll(anno); - } - - plot.setFixedLegendItems(lic); - - plot.setRenderer(idx, renderer); - - return retidx; - } - - - /** Returns a transparently textured paint. */ - // TODO why not use a transparent color? - protected static Paint createTransparentPaint() { - BufferedImage texture = new BufferedImage( - 1, 1, BufferedImage.TYPE_4BYTE_ABGR); - - return new TexturePaint( - texture, new Rectangle2D.Double(0d, 0d, 0d, 0d)); - } - - - /** - * Returns a new instance of EnhancedLineAndShapeRenderer always. - */ - protected XYLineAndShapeRenderer getRenderer(XYPlot plot, int idx) { - logger.debug("getRenderer: " + idx); - - EnhancedLineAndShapeRenderer r = - new EnhancedLineAndShapeRenderer(true, false); - - r.setPlot(plot); - - return r; - } - - - /** * Register annotations like MainValues for later plotting * * @param o list of annotations (data of facet).