Mercurial > dive4elements > river
changeset 1940:0d12e70766c8
Refactored XYChartGenerator to have better working multi-axes features.
flys-artifacts/trunk@3321 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Felix Wolfsteller <felix.wolfsteller@intevation.de> |
---|---|
date | Mon, 28 Nov 2011 14:36:56 +0000 |
parents | 2730d17df021 |
children | 0fa53fa65401 |
files | flys-artifacts/ChangeLog flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java |
diffstat | 2 files changed, 193 insertions(+), 130 deletions(-) [+] |
line wrap: on
line diff
--- a/flys-artifacts/ChangeLog Fri Nov 25 18:52:11 2011 +0000 +++ b/flys-artifacts/ChangeLog Mon Nov 28 14:36:56 2011 +0000 @@ -1,3 +1,13 @@ +2011-11-28 Felix Wolfsteller <felix.wolfsteller@intevation.de> + + Overhaul dataset/axis/renderer housekeeping in Mother of all + ChartGenerators. + + * src/main/java/de/intevation/flys/exports/XYChartGenerator.java: + Refactored, keep axis/rendering relevant information in objects + of new class AxisDataset. Removed some obsolete code while adding + documentation. + 2011-11-25 Sascha L. Teichmann <sascha.teichmann@intevation.de> * src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(relateWs):
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java Fri Nov 25 18:52:11 2011 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java Mon Nov 28 14:36:56 2011 +0000 @@ -49,15 +49,67 @@ /** * An abstract base class for creating XY charts. * + * With respect to datasets, ranges and axis, there are following requirements: + * <ul> + * <li> First in, first drawn: "Early" datasets should be of lower Z-Oder + * than later ones (only works per-axis). </li> + * <li> Visible axis should initially show the range of all datasets that + * show data for this axis (even invisible ones). Motivation: Once + * a dataset (theme) has been activated, it should be on screen. </li> + * <li> There should always be a Y-Axis on the "left". </li> + * </ul> + * * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public abstract class XYChartGenerator extends ChartGenerator { + private class AxisDataset { + /** Symbolic integer, but also coding the priority (0 goes first). */ + protected int axisSymbol; + /** List of assigned datasets (in order). */ + protected List<XYDataset> datasets; + /** Range to use to include all given datasets. */ + protected Range range; + + /** Create AxisDataset. */ + public AxisDataset(int symb) { + this.axisSymbol = symb; + datasets = new ArrayList<XYDataset>(); + } + + /** Merge (or create given range with range so far (if any). */ + private void mergeRanges(Range subRange) { + if (range == null) { + range = subRange; + return; + } + range = Range.combine(range, subRange); + } + + /** Add a dataset, include its range. */ + public void addDataset(XYSeries dataset) { + this.datasets.add(new XYSeriesCollection(dataset)); + includeRange(dataset); + } + + /** Adjust range to include given dataset. */ + public void includeRange(XYSeries dataset) { + mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY())); + } + + /** True if no datasets given. */ + public boolean isEmpty() { + return this.datasets.isEmpty(); + } + + } + + /** The logger that is used in this generator. */ private static Logger logger = Logger.getLogger(XYChartGenerator.class); /** Map of datasets ("index"). */ - protected SortedMap<Integer, List<XYDataset>> datasets; + protected SortedMap<Integer, AxisDataset> datasets; /** List of annotations to insert in plot. */ protected List<FLYSAnnotation> annotations; @@ -75,7 +127,7 @@ public XYChartGenerator() { xRanges = new HashMap<Integer, Range>(); yRanges = new HashMap<Integer, Range>(); - datasets = new TreeMap<Integer, List<XYDataset>>(); + datasets = new TreeMap<Integer, AxisDataset>(); } @@ -86,6 +138,7 @@ */ protected abstract String getChartTitle(); + /** * Returns the X-Axis label of a chart. * @@ -93,6 +146,7 @@ */ protected abstract String getXAxisLabel(); + /** * Returns the Y-Axis label of a chart. * @@ -100,7 +154,9 @@ */ protected abstract String getYAxisLabel(); - + /** + * Generate chart. + */ public void generate() throws IOException { @@ -168,25 +224,62 @@ XYPlot plot = (XYPlot) chart.getPlot(); chart.setBackgroundPaint(Color.WHITE); plot.setBackgroundPaint(Color.WHITE); - - addDatasets(plot); - addAnnotations(plot); addSubtitles(chart); adjustPlot(plot); - localizeAxes(plot); - createAxes(plot); - adjustAxes(plot); + //debugAxis(plot); + + addDatasets(plot); + + //debugDatasets(plot); recoverEmptyPlot(plot); + preparePointRanges(plot); - preparePointRanges(plot); + addAnnotationsToRenderer(plot); + + //debugAxis(plot); + + localizeAxes(plot); + adjustAxes(plot); autoZoom(plot); - applyThemes(plot); - removeEmptyRangeAxes(plot); + return chart; + } - return chart; + + /** + * Put debug output about datasets. + */ + public void debugDatasets(XYPlot plot) { + logger.debug("Number of datasets: " + plot.getDatasetCount()); + for (int i = 0; i < plot.getDatasetCount(); i++) { + if (plot.getDataset(i) == null) { + logger.debug("Dataset #" + i + " is null"); + continue; + } + logger.debug("Dataset #" + i + ":" + plot.getDataset(i)); + } + } + + + /** + * Put debug output about axes. + */ + public void debugAxis(XYPlot plot) { + logger.debug("..............."); + for (int i = 0; i < plot.getRangeAxisCount(); i++) { + if (plot.getRangeAxis(i) == null) + logger.debug("Axis #" + i + " == null"); + else { + logger.debug("Axis " + i + " != null [" + + plot.getRangeAxis(i).getRange().getLowerBound() + + " " + plot.getRangeAxis(i).getRange().getUpperBound() + + "]"); + } + + } + logger.debug("..............."); } @@ -195,14 +288,30 @@ * @param plot plot to add datasets to. */ protected void addDatasets(XYPlot plot) { - int count = 0; - for (Map.Entry<Integer, List<XYDataset>> entry: datasets.entrySet()) { - List<Integer> axisList = new ArrayList<Integer>(1); - axisList.add(entry.getKey()); - for (XYDataset dataset: entry.getValue()) { - int index = count++; - plot.setDataset(index, dataset); - plot.mapDatasetToRangeAxes(index, axisList); + // 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.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); + datasetIndex++; + } + axisIndex++; } } } @@ -220,25 +329,26 @@ return; } - if (visible) { - XYSeriesCollection collection = new XYSeriesCollection(series); - - List<XYDataset> dataset = datasets.get(index); + AxisDataset axisDataset = datasets.get(index); - if (dataset == null) { - dataset = new ArrayList<XYDataset>(); - datasets.put(index, dataset); - } - - dataset.add(collection); + if (axisDataset == null) { + axisDataset = new AxisDataset(index); + datasets.put(index, axisDataset); } - // Do this also when not visible to have axis scaled by default such - // that every data-point could be seen (except for annotations). + if (visible) { + axisDataset.addDataset(series); + } + else { + // Do this also when not visible to have axis scaled by default such + // that every data-point could be seen (except for annotations). + axisDataset.includeRange(series); + } + combineXRanges(new Range(series.getMinX(), series.getMaxX()), 0); - combineYRanges(new Range(series.getMinY(), series.getMaxY()), index); } + /** * Effect: extend range of x axis to include given limits. * @param range the given ("minimal") range. @@ -257,21 +367,6 @@ /** - * @param range the new range. - */ - private void combineYRanges(Range range, int index) { - - Range old = yRanges.get(index); - - if (old != null) { - range = Range.combine(old, range); - } - - yRanges.put(index, range); - } - - - /** * Adds annotations to list (if visible is true). */ public void addAnnotations(FLYSAnnotation annotation, boolean visible) { @@ -288,42 +383,15 @@ /** - * Create y-axes, ensure that the first axis (with data) is on the left. - */ - public void createAxes(XYPlot plot) { - logger.debug("XYChartGenerator.createAxes"); - - if (datasets.isEmpty()) { - plot.setRangeAxis(0, createYAxis(0)); - } - else { - Integer last = datasets.lastKey(); - int i = 0; - int firstVisible = 0; - - if (last != null) { - firstVisible = i = last; - for (; i >= 0; --i) { - if (datasets.containsKey(i)) { - plot.setRangeAxis(i, createYAxis(i)); - firstVisible = i; - } - } - plot.setRangeAxisLocation(firstVisible, AxisLocation.TOP_OR_LEFT); - } - } - } - - - /** * Create Y (range) axis for given index. * Shall be overriden by subclasses. */ protected NumberAxis createYAxis(int index) { - NumberAxis axis = new NumberAxis("default axis"); + NumberAxis axis = new NumberAxis(getYAxisLabel()); return axis; } + /** * If no data is visible, draw at least empty axis. */ @@ -332,33 +400,26 @@ logger.debug("debug: No range axis"); plot.setRangeAxis(createYAxis(0)); } - - } - - /** - * Remove Axes which do not have data on them. - */ - private void removeEmptyRangeAxes(XYPlot plot) { - if (datasets.isEmpty()) { - return; - } - Integer last = datasets.lastKey(); - - if (last != null) { - for (int i = last-1; i >= 0; --i) { - if (!datasets.containsKey(i)) { - plot.setRangeAxis(i, null); - } - } - } } /** - * Expands X and Y axes if only a point is shown. + * Expands a given range if it collapses into one point. + */ + private Range expandPointRange(Range range) { + if (range.getLowerBound() == range.getUpperBound()) { + return expandRange(range, 5); + } + return range; + } + + + /** + * Expands X axes if only a point is shown. */ private void preparePointRanges(XYPlot plot) { for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { + logger.debug("Check whether to expand a x axis."); Integer key = Integer.valueOf(i); Range r = xRanges.get(key); @@ -366,15 +427,6 @@ xRanges.put(key, expandRange(r, 5)); } } - - for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { - Integer key = Integer.valueOf(i); - - Range r = yRanges.get(key); - if (r != null && r.getLowerBound() == r.getUpperBound()) { - yRanges.put(key, expandRange(r, 5)); - } - } } @@ -481,7 +533,9 @@ * * @return a Range[] as follows: [x-Range, y-Range]. */ + // TODO When is this actually called? Is it valid as is? public Range[] getRangesForDataset(int index) { + logger.debug("getRangesForDataset " + index); return new Range[] { xRanges.get(Integer.valueOf(0)), yRanges.get(Integer.valueOf(index)) @@ -489,7 +543,10 @@ } - protected void addAnnotations(XYPlot plot) { + /** + * Add annotations to Renderer. + */ + protected void addAnnotationsToRenderer(XYPlot plot) { plot.clearAnnotations(); if (annotations == null) { @@ -498,13 +555,9 @@ } LegendItemCollection lic = new LegendItemCollection(); + LegendItemCollection old = plot.getFixedLegendItems(); - int idx = 0; - if (plot.getRangeAxis(idx) == null && plot.getRangeAxisCount() >= 2) { - idx = 1; - } - - XYItemRenderer renderer = plot.getRenderer(idx); + XYItemRenderer renderer = plot.getRenderer(0); for (FLYSAnnotation fa: annotations) { Document theme = fa.getTheme(); @@ -528,18 +581,27 @@ } } - // TODO Do after loop? - plot.setFixedLegendItems(lic); } + + // (Re-)Add prior legend entries. + if (old != null) { + old.addAll(lic); + } + else { + old = lic; + } + + plot.setFixedLegendItems(old); } /** * Adjusts the axes of a plot (the first axis does not include zero). - * + * To be overridden by children. * @param plot The XYPlot of the chart. */ protected void adjustAxes(XYPlot plot) { + /* NumberAxis yAxis = (NumberAxis) plot.getRangeAxis(); if (yAxis == null) { logger.warn("No Axis to setAutoRangeIncludeZero."); @@ -547,9 +609,13 @@ else { yAxis.setAutoRangeIncludesZero(false); } + */ } + /** + * Set some Stroke/Grid defaults. + */ protected void adjustPlot(XYPlot plot) { Stroke gridStroke = new BasicStroke( DEFAULT_GRID_LINE_WIDTH, @@ -632,19 +698,6 @@ } - protected void applyThemes(XYPlot plot) { - int idx = 0; - - for (Map.Entry<Integer, List<XYDataset>> entry: datasets.entrySet()) { - for (XYDataset dataset: entry.getValue()) { - if (dataset instanceof XYSeriesCollection) { - idx = applyThemes(plot, (XYSeriesCollection)dataset, idx); - } - } - } - } - - /** * @param idx "index" of dataset/series (first dataset to be drawn has * index 0), correlates with renderer index.