# HG changeset patch # User Felix Wolfsteller # Date 1322491016 0 # Node ID 0d12e70766c84a0b49823d88f11a4589a2585bd3 # Parent 2730d17df0219d03b028d52893f1430198164498 Refactored XYChartGenerator to have better working multi-axes features. flys-artifacts/trunk@3321 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r 2730d17df021 -r 0d12e70766c8 flys-artifacts/ChangeLog --- 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 + + 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 * src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java(relateWs): diff -r 2730d17df021 -r 0d12e70766c8 flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java --- 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: + *
    + *
  • First in, first drawn: "Early" datasets should be of lower Z-Oder + * than later ones (only works per-axis).
  • + *
  • 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.
  • + *
  • There should always be a Y-Axis on the "left".
  • + *
+ * * @author Ingo Weinzierl */ 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 datasets; + /** Range to use to include all given datasets. */ + protected Range range; + + /** Create AxisDataset. */ + public AxisDataset(int symb) { + this.axisSymbol = symb; + datasets = new ArrayList(); + } + + /** 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> datasets; + protected SortedMap datasets; /** List of annotations to insert in plot. */ protected List annotations; @@ -75,7 +127,7 @@ public XYChartGenerator() { xRanges = new HashMap(); yRanges = new HashMap(); - datasets = new TreeMap>(); + datasets = new TreeMap(); } @@ -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> entry: datasets.entrySet()) { - List axisList = new ArrayList(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 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 dataset = datasets.get(index); + AxisDataset axisDataset = datasets.get(index); - if (dataset == null) { - dataset = new ArrayList(); - 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> 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.