ingo@369: package de.intevation.flys.exports;
ingo@369:
ingo@419: import java.awt.BasicStroke;
ingo@369: import java.awt.Color;
ingo@2053: import java.awt.Font;
felix@2020: import java.awt.Paint;
ingo@419: import java.awt.Stroke;
ingo@369:
ingo@1645: import java.text.NumberFormat;
ingo@1645:
ingo@1679: import java.util.ArrayList;
ingo@1684: import java.util.HashMap;
ingo@1679: import java.util.List;
ingo@1684: import java.util.Map;
ingo@1679:
ingo@1679: import org.w3c.dom.Document;
ingo@1679:
ingo@369: import org.apache.log4j.Logger;
ingo@369:
ingo@369: import org.jfree.chart.ChartFactory;
ingo@369: import org.jfree.chart.JFreeChart;
ingo@1679: import org.jfree.chart.LegendItemCollection;
felix@2138: import org.jfree.chart.annotations.XYBoxAnnotation;
felix@2161: import org.jfree.chart.annotations.XYLineAnnotation;
ingo@1679: import org.jfree.chart.annotations.XYTextAnnotation;
ingo@369: import org.jfree.chart.axis.NumberAxis;
ingo@652: import org.jfree.chart.axis.ValueAxis;
ingo@369: import org.jfree.chart.plot.PlotOrientation;
ingo@369: import org.jfree.chart.plot.XYPlot;
ingo@652: import org.jfree.data.Range;
ingo@2242: import org.jfree.data.general.Series;
ingo@923: import org.jfree.data.xy.XYSeries;
ingo@923: import org.jfree.data.xy.XYSeriesCollection;
felix@1931: import org.jfree.data.xy.XYDataset;
ingo@654:
ingo@654: import org.jfree.ui.RectangleInsets;
felix@2161: import org.jfree.ui.TextAnchor;
ingo@369:
ingo@2325: import de.intevation.artifactdatabase.state.ArtifactAndFacet;
felix@1849: import de.intevation.artifactdatabase.state.Facet;
felix@1849:
ingo@2330: import de.intevation.flys.jfree.Bounds;
ingo@1679: import de.intevation.flys.jfree.FLYSAnnotation;
raimund@1738: import de.intevation.flys.jfree.StickyAxisAnnotation;
felix@2161: import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
ingo@2074: import de.intevation.flys.jfree.StyledAreaSeriesCollection;
ingo@2074: import de.intevation.flys.jfree.StyledXYSeries;
ingo@369:
sascha@1754: import de.intevation.flys.utils.ThemeAccess;
ingo@369:
felix@2138: import de.intevation.flys.artifacts.model.HYKFactory;
felix@2138:
felix@2206: import org.json.JSONArray;
felix@2206: import org.json.JSONException;
felix@2206:
felix@2206:
ingo@369: /**
ingo@369: * An abstract base class for creating XY charts.
ingo@369: *
felix@1940: * With respect to datasets, ranges and axis, there are following requirements:
felix@1940: *
felix@1940: * - First in, first drawn: "Early" datasets should be of lower Z-Oder
felix@1940: * than later ones (only works per-axis).
felix@1940: * - Visible axis should initially show the range of all datasets that
felix@1940: * show data for this axis (even invisible ones). Motivation: Once
felix@1940: * a dataset (theme) has been activated, it should be on screen.
felix@1940: * - There should always be a Y-Axis on the "left".
felix@1940: *
felix@1940: *
ingo@369: * @author Ingo Weinzierl
ingo@369: */
ingo@369: public abstract class XYChartGenerator extends ChartGenerator {
ingo@369:
felix@2020: // TODO Consider storing the renderer here.
ingo@2238: private class XYAxisDataset implements AxisDataset {
felix@1940: /** Symbolic integer, but also coding the priority (0 goes first). */
felix@1940: protected int axisSymbol;
felix@1940: /** List of assigned datasets (in order). */
felix@1940: protected List datasets;
felix@1940: /** Range to use to include all given datasets. */
felix@1940: protected Range range;
felix@2163: /** Index of axis in plot. */
felix@2163: protected int plotAxisIndex;
felix@1940:
felix@1940: /** Create AxisDataset. */
ingo@2238: public XYAxisDataset(int symb) {
felix@1940: this.axisSymbol = symb;
felix@1940: datasets = new ArrayList();
felix@1940: }
felix@1940:
felix@1940: /** Merge (or create given range with range so far (if any). */
felix@1940: private void mergeRanges(Range subRange) {
felix@1959: // Avoid merging NaNs, as they take min/max place forever.
felix@1959: if (subRange == null ||
felix@1959: Double.isNaN(subRange.getLowerBound()) ||
felix@1959: Double.isNaN(subRange.getUpperBound())) {
felix@1959: return;
felix@1959: }
felix@1940: if (range == null) {
felix@1940: range = subRange;
felix@1940: return;
felix@1940: }
felix@1940: range = Range.combine(range, subRange);
felix@1940: }
felix@1940:
ingo@2238:
ingo@2238: @Override
ingo@2238: public void addDataset(XYDataset dataset) {
ingo@2238: datasets.add(dataset);
ingo@2238: includeYRange(((XYSeriesCollection) dataset).getSeries(0));
ingo@2238: }
ingo@2238:
felix@1940: /** Add a dataset, include its range. */
ingo@2238: public void addDataset(XYSeries series) {
ingo@2238: addDataset(new XYSeriesCollection(series));
felix@1940: }
felix@1940:
ingo@2242:
ingo@2242: @Override
ingo@2242: public void setRange(Range range) {
ingo@2242: this.range = range;
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2242: public Range getRange() {
ingo@2242: return range;
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2242: public XYDataset[] getDatasets() {
ingo@2242: return (XYDataset[])
ingo@2242: datasets.toArray(new XYDataset[datasets.size()]);
ingo@2242: }
ingo@2242:
felix@2020: public void addArea(StyledAreaSeriesCollection series) {
felix@2020: this.datasets.add(series);
felix@2005: }
felix@2005:
felix@2005: /** True if to be renedered as area. */
ingo@2242: @Override
ingo@2242: public boolean isArea(XYDataset series) {
felix@2020: return (series instanceof StyledAreaSeriesCollection);
felix@2005: }
felix@2005:
felix@1940: /** Adjust range to include given dataset. */
felix@1958: public void includeYRange(XYSeries dataset) {
felix@1940: mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY()));
felix@1940: }
felix@1940:
felix@1940: /** True if no datasets given. */
ingo@2242: @Override
felix@1940: public boolean isEmpty() {
felix@1940: return this.datasets.isEmpty();
felix@1940: }
felix@2163:
felix@2163: /** Set the 'real' axis index that this axis is mapped to. */
ingo@2242: @Override
felix@2163: public void setPlotAxisIndex(int axisIndex) {
felix@2163: this.plotAxisIndex = axisIndex;
felix@2163: }
felix@2163:
felix@2163: /** Get the 'real' axis index that this axis is mapped to. */
ingo@2242: @Override
felix@2163: public int getPlotAxisIndex() {
felix@2163: return this.plotAxisIndex;
felix@2163: }
felix@2005: } // class AxisDataset
felix@1940:
felix@2246: protected abstract YAxisWalker getYAxisWalker();
ingo@2000:
felix@1048: /** The logger that is used in this generator. */
ingo@654: private static Logger logger = Logger.getLogger(XYChartGenerator.class);
ingo@369:
ingo@1679: /** List of annotations to insert in plot. */
ingo@1679: protected List annotations;
ingo@1679:
felix@1685: /** The max X range to include all X values of all series for each axis. */
ingo@1684: protected Map xRanges;
ingo@1684:
felix@1685: /** The max Y range to include all Y values of all series for each axis. */
ingo@1684: protected Map yRanges;
ingo@1684:
ingo@419:
felix@1931: public XYChartGenerator() {
ingo@2238: super();
ingo@2238:
felix@1931: xRanges = new HashMap();
felix@1931: yRanges = new HashMap();
felix@1931: }
felix@1931:
felix@1931:
ingo@369: /**
felix@1930: * Generate the chart anew (including localized axis and all).
felix@1930: */
ingo@653: public JFreeChart generateChart() {
ingo@653: logger.debug("XYChartGenerator.generateChart");
ingo@653:
ingo@369: JFreeChart chart = ChartFactory.createXYLineChart(
ingo@369: getChartTitle(),
ingo@369: getXAxisLabel(),
ingo@2051: getYAxisLabel(0),
ingo@375: null,
ingo@369: PlotOrientation.VERTICAL,
ingo@2047: isLegendVisible(),
ingo@369: false,
ingo@369: false);
ingo@369:
felix@1931: XYPlot plot = (XYPlot) chart.getPlot();
ingo@369: chart.setBackgroundPaint(Color.WHITE);
felix@1931: plot.setBackgroundPaint(Color.WHITE);
ingo@414: addSubtitles(chart);
ingo@419: adjustPlot(plot);
ingo@923:
felix@1940: //debugAxis(plot);
felix@1940:
felix@1940: addDatasets(plot);
felix@1940:
felix@1940: //debugDatasets(plot);
ingo@923:
felix@1935: recoverEmptyPlot(plot);
felix@1940: preparePointRanges(plot);
felix@1935:
felix@1940: //debugAxis(plot);
felix@1940:
felix@1940: localizeAxes(plot);
felix@1940: adjustAxes(plot);
ingo@673: autoZoom(plot);
ingo@652:
felix@2138: // These have to go after the autozoom.
felix@2161: addAnnotationsToRenderer(plot);
felix@2138:
felix@1940: return chart;
felix@1940: }
ingo@924:
felix@1940:
ingo@2238: @Override
ingo@2242: protected Series getSeriesOf(XYDataset dataset, int idx) {
ingo@2242: return ((XYSeriesCollection) dataset).getSeries(idx);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2242: protected void setXRange(int axis, Range range) {
ingo@2242: xRanges.put(Integer.valueOf(axis), range);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2242: protected void setYRange(int axis, Range range) {
ingo@2242: yRanges.put(Integer.valueOf(axis), range);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2238: protected AxisDataset createAxisDataset(int idx) {
ingo@2238: logger.debug("Create new XYAxisDataset for index: " + idx);
ingo@2238: return new XYAxisDataset(idx);
ingo@2238: }
ingo@2238:
ingo@2238:
felix@1940: /**
felix@1940: * Put debug output about datasets.
felix@1940: */
felix@1940: public void debugDatasets(XYPlot plot) {
felix@1940: logger.debug("Number of datasets: " + plot.getDatasetCount());
felix@1940: for (int i = 0; i < plot.getDatasetCount(); i++) {
felix@1940: if (plot.getDataset(i) == null) {
felix@1940: logger.debug("Dataset #" + i + " is null");
felix@1940: continue;
felix@1940: }
felix@1940: logger.debug("Dataset #" + i + ":" + plot.getDataset(i));
felix@1958: XYSeriesCollection series = (XYSeriesCollection) plot.getDataset(i);
felix@1958: logger.debug("X-Extend of Dataset: " + series.getSeries(0).getMinX()
felix@1958: + " " + series.getSeries(0).getMaxX());
felix@1958: logger.debug("Y-Extend of Dataset: " + series.getSeries(0).getMinY()
felix@1958: + " " + series.getSeries(0).getMaxY());
felix@1940: }
felix@1940: }
felix@1940:
felix@1940:
felix@1940: /**
felix@1940: * Put debug output about axes.
felix@1940: */
felix@1940: public void debugAxis(XYPlot plot) {
felix@1940: logger.debug("...............");
felix@1940: for (int i = 0; i < plot.getRangeAxisCount(); i++) {
felix@1940: if (plot.getRangeAxis(i) == null)
felix@2005: logger.debug("Range-Axis #" + i + " == null");
felix@1940: else {
felix@2005: logger.debug("Range-Axis " + i + " != null [" +
felix@1940: plot.getRangeAxis(i).getRange().getLowerBound() +
felix@1940: " " + plot.getRangeAxis(i).getRange().getUpperBound() +
felix@1940: "]");
felix@1940: }
felix@1940:
felix@1940: }
felix@1940: logger.debug("...............");
ingo@369: }
ingo@369:
ingo@369:
felix@1685: /**
felix@2005: * Registers an area to be drawn.
felix@2163: * @param area Area to be drawn.
felix@2163: * @param index 'axis index'
felix@2163: * @param visible Whether or not to be visible (important for range calculations).
felix@2005: */
felix@2020: public void addAreaSeries(StyledAreaSeriesCollection area, int index, boolean visible) {
felix@2020: if (area == null) {
felix@2005: logger.warn("Cannot yet render above/under curve.");
felix@2005: return;
felix@2005: }
felix@2005:
ingo@2238: XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index);
felix@2005:
felix@2020: if (visible) {
felix@2020: axisDataset.addArea(area);
felix@2020: }
felix@2005: else {
felix@2005: // TODO only range merging.
felix@2005: }
felix@2005: //TODO range merging.
felix@2005: }
felix@2005:
felix@2020:
felix@2005: /**
felix@1935: * Add given series if visible, if not visible adjust ranges (such that
felix@1935: * all points in data would be plotted once visible).
felix@1931: * @param series the dataseries to include in plot.
felix@2163: * @param index ('symbolic') index of the series and of its axis.
felix@1931: * @param visible whether or not the data should be plotted.
felix@1931: */
felix@1931: public void addAxisSeries(XYSeries series, int index, boolean visible) {
felix@1931: if (series == null) {
ingo@1684: return;
ingo@1684: }
ingo@1684:
ingo@2242: logger.debug("Y Range of XYSeries: " +
ingo@2242: series.getMinY() + " | " + series.getMaxY());
ingo@2242:
ingo@2242: addAxisDataset(new XYSeriesCollection(series), index, visible);
ingo@2242:
ingo@2238: XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index);
ingo@1684:
ingo@2238: if (!visible) {
felix@1940: // Do this also when not visible to have axis scaled by default such
felix@1940: // that every data-point could be seen (except for annotations).
felix@1958: axisDataset.includeYRange(series);
felix@1940: }
felix@1931: }
felix@1931:
felix@1940:
felix@1931: /**
felix@1931: * Effect: extend range of x axis to include given limits.
felix@1931: * @param range the given ("minimal") range.
felix@1931: * @param index index of axis to be merged.
felix@1931: */
ingo@2238: protected void combineXRanges(Range range, int index) {
felix@1931:
felix@1959: if (range == null
felix@1959: || Double.isNaN(range.getLowerBound())
felix@1959: || Double.isNaN(range.getUpperBound())) {
felix@1959: return;
felix@1959: }
felix@1959:
felix@1931: Range old = xRanges.get(index);
felix@1931:
felix@1931: if (old != null) {
felix@1931: range = Range.combine(old, range);
felix@1931: }
felix@1931:
felix@1931: xRanges.put(index, range);
ingo@1684: }
ingo@1684:
ingo@1684:
felix@1930: /**
felix@1711: * Adds annotations to list (if visible is true).
felix@1711: */
ingo@1684: public void addAnnotations(FLYSAnnotation annotation, boolean visible) {
ingo@1684: if (!visible) {
ingo@1684: return;
ingo@1684: }
ingo@1684:
ingo@1679: if (annotations == null) {
ingo@1679: annotations = new ArrayList();
ingo@1679: }
ingo@1679:
ingo@1679: annotations.add(annotation);
ingo@1679: }
ingo@1679:
ingo@1679:
felix@1931: /**
felix@1935: * If no data is visible, draw at least empty axis.
felix@1935: */
felix@1935: private void recoverEmptyPlot(XYPlot plot) {
felix@1935: if (plot.getRangeAxis() == null) {
felix@1935: logger.debug("debug: No range axis");
felix@1935: plot.setRangeAxis(createYAxis(0));
felix@1935: }
ingo@923: }
ingo@923:
ingo@923:
felix@1931: /**
felix@1940: * Expands X axes if only a point is shown.
felix@1931: */
ingo@1686: private void preparePointRanges(XYPlot plot) {
ingo@1686: for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) {
felix@1940: logger.debug("Check whether to expand a x axis.");
sascha@1698: Integer key = Integer.valueOf(i);
ingo@1686:
ingo@1686: Range r = xRanges.get(key);
ingo@1687: if (r != null && r.getLowerBound() == r.getUpperBound()) {
ingo@2242: setXRange(key, ChartHelper.expandRange(r, 5));
ingo@1686: }
ingo@1686: }
ingo@1686: }
ingo@1686:
ingo@1686:
felix@1931: /**
ingo@654: * This method zooms the plot to the specified ranges in the attribute
ingo@654: * document or to the ranges specified by the min/max values in the
ingo@654: * datasets. Note: We determine the range manually if no zoom ranges
ingo@654: * are given, because JFreeCharts auto-zoom adds a margin to the left and
ingo@654: * right of the data area.
ingo@653: *
ingo@653: * @param plot The XYPlot.
ingo@653: */
ingo@673: protected void autoZoom(XYPlot plot) {
ingo@652: logger.debug("Zoom to specified ranges.");
ingo@654:
ingo@673: Range xrange = getDomainAxisRange();
ingo@673: Range yrange = getValueAxisRange();
ingo@654:
ingo@2050: ValueAxis xAxis = plot.getDomainAxis();
ingo@2050:
ingo@2050: Range fixedXRange = getRangeForAxisFromSettings("X");
ingo@2050: if (fixedXRange != null) {
ingo@2050: xAxis.setRange(fixedXRange);
ingo@2050: }
ingo@2050: else {
ingo@2050: zoomX(plot, xAxis, xRanges.get(0), xrange);
ingo@2050: }
ingo@923:
ingo@1699: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) {
ingo@673: ValueAxis yaxis = plot.getRangeAxis(i);
ingo@654:
ingo@2049: if (yaxis instanceof IdentifiableNumberAxis) {
ingo@2050: IdentifiableNumberAxis idAxis = (IdentifiableNumberAxis) yaxis;
ingo@2050:
ingo@2050: Range fixedRange = getRangeForAxisFromSettings(idAxis.getId());
ingo@2050: if (fixedRange != null) {
ingo@2050: yaxis.setRange(fixedRange);
ingo@2050: continue;
ingo@2050: }
ingo@2049: }
ingo@2049:
ingo@673: if (yaxis == null) {
ingo@1699: logger.debug("Zoom problem: no Y Axis for index: " + i);
ingo@673: continue;
ingo@673: }
ingo@673:
ingo@1699: logger.debug("Prepare zoom settings for y axis at index: " + i);
ingo@1699: zoomY(plot, yaxis, yRanges.get(Integer.valueOf(i)), yrange);
ingo@654: }
ingo@653: }
ingo@652:
ingo@653:
ingo@2330: protected Range getDomainAxisRange() {
ingo@2330: String[] ranges = getDomainAxisRangeFromRequest();
ingo@2330:
ingo@2330: if (ranges == null || ranges.length < 2) {
ingo@2330: logger.debug("No zoom range for domain axis specified.");
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330: if (ranges[0].length() > 0 && ranges[1].length() > 0) {
ingo@2330: try {
ingo@2330: double from = Double.parseDouble(ranges[0]);
ingo@2330: double to = Double.parseDouble(ranges[1]);
ingo@2330:
ingo@2330: if (from == 0 && to == 0) {
ingo@2330: logger.debug("No range specified. Lower and upper X == 0");
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330: if (from > to) {
ingo@2330: double tmp = to;
ingo@2330: to = from;
ingo@2330: from = tmp;
ingo@2330: }
ingo@2330:
ingo@2330: return new Range(from, to);
ingo@2330: }
ingo@2330: catch (NumberFormatException nfe) {
ingo@2330: logger.warn("Wrong values for domain axis range.");
ingo@2330: }
ingo@2330: }
ingo@2330:
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: protected Range getValueAxisRange() {
ingo@2330: String[] ranges = getValueAxisRangeFromRequest();
ingo@2330:
ingo@2330: if (ranges == null || ranges.length < 2) {
ingo@2330: logger.debug("No range specified. Lower and upper Y == 0");
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330: if (ranges[0].length() > 0 && ranges[1].length() > 0) {
ingo@2330: try {
ingo@2330: double from = Double.parseDouble(ranges[0]);
ingo@2330: double to = Double.parseDouble(ranges[1]);
ingo@2330:
ingo@2330: if (from == 0 && to == 0) {
ingo@2330: logger.debug("No range specified. Lower and upper Y == 0");
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330: return from > to
ingo@2330: ? new Range(to, from)
ingo@2330: : new Range(from, to);
ingo@2330: }
ingo@2330: catch (NumberFormatException nfe) {
ingo@2330: logger.warn("Wrong values for value axis range.");
ingo@2330: }
ingo@2330: }
ingo@2330:
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@718: protected boolean zoomX(XYPlot plot, ValueAxis axis, Range range, Range x) {
ingo@718: return zoom(plot, axis, range, x);
ingo@718: }
ingo@718:
ingo@718:
ingo@718: protected boolean zoomY(XYPlot plot, ValueAxis axis, Range range, Range x) {
ingo@718: return zoom(plot, axis, range, x);
ingo@718: }
ingo@718:
ingo@718:
ingo@653: /**
ingo@653: * Zooms the x axis to the range specified in the attribute document.
ingo@653: *
felix@1958: * @param plot The XYPlot.
felix@1958: * @param axis The axis the shoud be modified.
ingo@673: * @param range The whole range specified by a dataset.
felix@1958: * @param x A user defined range (null permitted).
ingo@654: *
ingo@654: * @return true, if a zoom range was specified, otherwise false.
ingo@653: */
ingo@673: protected boolean zoom(XYPlot plot, ValueAxis axis, Range range, Range x) {
sascha@1736:
sascha@1736: if (range == null) {
sascha@1736: return false;
sascha@1736: }
sascha@1736:
ingo@673: if (x != null) {
ingo@673: double min = range.getLowerBound();
ingo@673: double max = range.getUpperBound();
ingo@673: double diff = max > min ? max - min : min - max;
ingo@652:
ingo@673: Range computed = new Range(
ingo@673: min + x.getLowerBound() * diff,
ingo@673: min + x.getUpperBound() * diff);
ingo@673:
ingo@717: axis.setRangeWithMargins(computed);
ingo@673:
ingo@673: logger.debug("Zoom axis to: " + computed);
ingo@654:
ingo@654: return true;
ingo@652: }
ingo@654:
ingo@717: axis.setRangeWithMargins(range);
ingo@654: return false;
ingo@654: }
ingo@654:
ingo@654:
ingo@654: /**
ingo@1684: * This method extracts the minimum and maximum values for x and y axes
ingo@1684: * which are stored in xRanges and yRanges.
ingo@654: *
ingo@1684: * @param index The index of the y-Axis.
ingo@654: *
ingo@654: * @return a Range[] as follows: [x-Range, y-Range].
ingo@654: */
ingo@2261: @Override
felix@1944: public Range[] getRangesForAxis(int index) {
felix@1944: logger.debug("getRangesForAxis " + index);
raimund@2132:
raimund@2132: Range rx = xRanges.get(Integer.valueOf(0));
raimund@2132: Range ry = yRanges.get(Integer.valueOf(index));
raimund@2132:
raimund@2132: if (rx == null) {
raimund@2132: logger.warn("Range for x axis not set." +
raimund@2132: " Using default values: 0 - 1.");
raimund@2132: rx = new Range(0, 1);
raimund@2132: }
raimund@2132: if (ry == null) {
raimund@2132: logger.warn("Range for y" + index +
raimund@2132: " axis not set. Using default values: 0 - 1.");
raimund@2132: ry = new Range(0, 1);
raimund@2132: }
raimund@2132: return new Range[] {rx, ry};
ingo@652: }
ingo@652:
ingo@652:
ingo@2330: @Override
ingo@2330: public Bounds getXBounds(int axis) {
ingo@2330: // TODO IMPLEMENT ME
ingo@2330: throw new RuntimeException(
ingo@2330: "XYChartGenerator.getXBounds(int) not implemented");
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: @Override
ingo@2330: protected void setXBounds(int axis, Bounds bounds) {
ingo@2330: // TODO IMPLEMENT ME
ingo@2330: throw new RuntimeException(
ingo@2330: "XYChartGenerator.setXBounds(int,Bounds) not implemented");
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: @Override
ingo@2330: public Bounds getYBounds(int axis) {
ingo@2330: // TODO IMPLEMENT ME
ingo@2330: throw new RuntimeException(
ingo@2330: "XYChartGenerator.getYBounds(int) not implemented");
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: @Override
ingo@2330: protected void setYBounds(int axis, Bounds bounds) {
ingo@2330: // TODO IMPLEMENT ME
ingo@2330: throw new RuntimeException(
ingo@2330: "XYChartGenerator.setYBounds(int,Bounds) not implemented");
ingo@2330: }
ingo@2330:
ingo@2330:
felix@2138: /** Get color for hyk zones by their type (which is the name). */
felix@2138: public Paint colorForHYKZone(String zoneName) {
felix@2138: if (zoneName.startsWith("R")) {
felix@2138: // Brownish.
felix@2138: return new Color(153, 60, 0);
felix@2138: }
felix@2138: else if (zoneName.startsWith("V")) {
felix@2138: // Greenish.
felix@2138: return new Color(0, 255, 0);
felix@2138: }
felix@2138: else if (zoneName.startsWith("B")) {
felix@2138: // Grayish.
felix@2138: return new Color(128, 128, 128);
felix@2138: }
felix@2138: else if (zoneName.startsWith("H")) {
felix@2138: // Blueish.
felix@2138: return new Color(0, 0, 255);
felix@2138: }
felix@2138: else {
felix@2138: // Default.
felix@2138: logger.debug("Unknown zone type found.");
felix@2138: return new Color(255, 0, 0);
felix@2138: }
felix@2138: }
felix@2138:
felix@2138:
felix@2161: /**
felix@2161: * Add a text and a line annotation.
felix@2161: */
felix@2161: public void addStickyAnnotation(
felix@2161: StickyAxisAnnotation annotation,
felix@2161: XYPlot plot,
felix@2161: Area area,
felix@2161: ThemeAccess.LineStyle lineStyle,
felix@2161: ThemeAccess.TextStyle textStyle
felix@2161: ) {
felix@2161: // OPTIMIZE pre-calculate area-related values
felix@2161: final float TEXT_OFF = 0.03f;
felix@2161: final float LINE_OFF = 0.02f;
felix@2161:
felix@2161: XYLineAnnotation lineAnnotation = null;
felix@2161: XYTextAnnotation textAnnotation = null;
felix@2161:
felix@2163: int rendererIndex = 0;
felix@2163:
felix@2161: if (annotation.atX()) {
felix@2161: textAnnotation = new CollisionFreeXYTextAnnotation(
felix@2161: annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
felix@2161: // OPTIMIZE externalize the calculation involving PI.
felix@2161: textAnnotation.setRotationAngle(270f*Math.PI/180f);
felix@2161: // Style the line.
felix@2161: if (lineStyle != null) {
felix@2161: lineAnnotation = new XYLineAnnotation(annotation.getPos(),
felix@2161: area.atGround(), annotation.getPos(), area.ofGround(LINE_OFF),
felix@2161: new BasicStroke(lineStyle.getWidth()),lineStyle.getColor());
felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
felix@2161: }
felix@2161: else {
felix@2161: lineAnnotation = new XYLineAnnotation(annotation.getPos(),
felix@2161: area.atGround(), annotation.getPos(), area.ofGround(LINE_OFF));
felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
felix@2161: }
felix@2161: }
felix@2161: else {
felix@2163: // Do the more complicated case where we stick to the Y-Axis.
felix@2163: // There is one nasty case (duration curves, where annotations
felix@2163: // might stick to the second y-axis).
ingo@2238: XYAxisDataset dataset = (XYAxisDataset) getAxisDataset(
felix@2163: new Integer(annotation.getAxisSymbol()));
felix@2163: if (dataset == null) {
felix@2163: logger.warn("Annotation should stick to unfindable y-axis: "
felix@2163: + annotation.getAxisSymbol());
felix@2163: rendererIndex = 0;
felix@2161: }
felix@2161: else {
felix@2163: rendererIndex = dataset.getPlotAxisIndex();
felix@2163: }
felix@2163:
felix@2163: if (rendererIndex != 0) {
felix@2163: // OPTIMIZE: Pass a different area to this function,
felix@2163: // do the adding to renderer outside (let this
felix@2163: // function return the annotations).
felix@2163: // Note that this path is travelled rarely.
felix@2163: Area area2 = new Area(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex));
felix@2163: textAnnotation = new CollisionFreeXYTextAnnotation(
felix@2163: annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos());
felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
felix@2163: // Style the line.
felix@2163: if (lineStyle != null) {
felix@2163: lineAnnotation = new XYLineAnnotation(area2.ofRight(LINE_OFF),
felix@2163: annotation.getPos(), area2.atRight(),
felix@2163: annotation.getPos(), new BasicStroke(lineStyle.getWidth()),
felix@2163: lineStyle.getColor());
felix@2163: }
felix@2163: else {
felix@2163: lineAnnotation = new XYLineAnnotation(area2.atRight(),
felix@2163: annotation.getPos(), area2.ofRight(LINE_OFF), annotation.getPos());
felix@2163: }
felix@2163: }
felix@2163: else {
felix@2163: textAnnotation = new CollisionFreeXYTextAnnotation(
felix@2163: annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
felix@2163: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
felix@2163: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
felix@2163: // Style the line.
felix@2163: if (lineStyle != null) {
felix@2163: lineAnnotation = new XYLineAnnotation(area.atLeft(),
felix@2163: annotation.getPos(), area.ofLeft(LINE_OFF),
felix@2163: annotation.getPos(), new BasicStroke(lineStyle.getWidth()),
felix@2163: lineStyle.getColor());
felix@2163: }
felix@2163: else {
felix@2163: lineAnnotation = new XYLineAnnotation(area.atLeft(),
felix@2163: annotation.getPos(), area.ofLeft(LINE_OFF), annotation.getPos());
felix@2163: }
felix@2161: }
felix@2161: }
felix@2161:
felix@2161: // Style the text.
felix@2161: if (textStyle != null) {
felix@2161: textStyle.apply(textAnnotation);
felix@2161: }
felix@2161:
felix@2161: // Add the Annotations to renderer.
felix@2163: plot.getRenderer(rendererIndex).addAnnotation(textAnnotation,
ingo@2300: org.jfree.ui.Layer.FOREGROUND);
felix@2163: plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation,
ingo@2300: org.jfree.ui.Layer.FOREGROUND);
felix@2161: }
felix@2161:
felix@2161:
felix@2161: /** Add annotations (Sticky, Text and hyk zones). */
felix@2161: public void addAnnotationsToRenderer(XYPlot plot) {
felix@2161: logger.debug("XYChartGenerator.addAnnotationsToRenderer");
felix@2138:
felix@2143: if (annotations == null) {
felix@2143: logger.debug("XYChartGenerator.addBoxAnnotations: no annotations.");
felix@2143: return;
felix@2143: }
felix@2143:
felix@2152: // Paints for the boxes/lines.
felix@2138: Stroke basicStroke = new BasicStroke(1.0f);
felix@2138:
felix@2161: Paint linePaint = new Color(255, 0,0,60);
felix@2161: Paint fillPaint = new Color(0, 255,0,60);
felix@2161: Paint tranPaint = new Color(0, 0,0, 0);
felix@2138:
felix@2161: // OPTMIMIZE: Pre-calculate positions
felix@2161: Area area = new Area(
felix@2161: plot.getDomainAxis(0).getRange(),
felix@2161: plot.getRangeAxis().getRange());
felix@2138:
felix@2161: // Walk over all Annotation sets.
felix@2161: for (FLYSAnnotation fa: annotations) {
felix@2152:
felix@2152: // Access text styling, if any.
felix@2152: Document theme = fa.getTheme();
felix@2152: ThemeAccess.TextStyle textStyle = null;
felix@2161: ThemeAccess.LineStyle lineStyle = null;
felix@2161:
felix@2161: // Get Themeing information and add legend item.
felix@2152: if (theme != null) {
felix@2152: ThemeAccess themeAccess = new ThemeAccess(theme);
felix@2152: textStyle = themeAccess.parseTextStyle();
felix@2161: lineStyle = themeAccess.parseLineStyle();
felix@2184: if (fa.getLabel() != null) {
felix@2184: LegendItemCollection lic = new LegendItemCollection();
felix@2184: LegendItemCollection old = plot.getFixedLegendItems();
felix@2184: lic.add(createLegendItem(theme, fa.getLabel()));
felix@2184: // (Re-)Add prior legend entries.
felix@2184: if (old != null) {
felix@2184: old.addAll(lic);
felix@2184: }
felix@2184: else {
felix@2184: old = lic;
felix@2184: }
felix@2184: plot.setFixedLegendItems(old);
felix@2161: }
felix@2152: }
felix@2152:
felix@2161: // The 'Sticky' Annotations (at axis, with line and text).
felix@2161: for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) {
felix@2161: addStickyAnnotation(sta, plot, area, lineStyle, textStyle);
felix@2161: }
felix@2161:
felix@2183: // Other Text Annotations.
felix@2161: for (XYTextAnnotation ta: fa.getTextAnnotations()) {
felix@2193: // Style the text.
felix@2193: if (textStyle != null) {
felix@2193: textStyle.apply(ta);
felix@2193: }
felix@2193: ta.setY(area.above(0.05d, ta.getY()));
felix@2183: plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
felix@2161: }
felix@2161:
felix@2161: // Hyks.
felix@2138: for (HYKFactory.Zone zone: fa.getBoxes()) {
felix@2161: // For each zone, create a box to fill with color, a box to draw
felix@2161: // the lines and a text to display the type.
felix@2138: fillPaint = colorForHYKZone(zone.getName());
felix@2138:
felix@2161: XYBoxAnnotation boxA = new XYBoxAnnotation(zone.getFrom(), area.atGround(),
felix@2161: zone.getTo(), area.ofGround(0.03f), basicStroke, tranPaint, fillPaint);
felix@2161: XYBoxAnnotation boxB = new XYBoxAnnotation(zone.getFrom(), area.atGround(),
felix@2161: zone.getTo(), area.atTop(), basicStroke, fillPaint, tranPaint);
felix@2138:
felix@2138: XYTextAnnotation tex = new XYTextAnnotation(zone.getName(),
felix@2138: zone.getFrom() + (zone.getTo() - zone.getFrom()) / 2.0d,
felix@2161: area.ofGround(0.015f));
felix@2152: if (textStyle != null) {
felix@2152: textStyle.apply(tex);
felix@2152: }
felix@2138:
felix@2161: plot.getRenderer().addAnnotation(boxA, org.jfree.ui.Layer.BACKGROUND);
felix@2161: plot.getRenderer().addAnnotation(boxB, org.jfree.ui.Layer.BACKGROUND);
felix@2161: plot.getRenderer().addAnnotation(tex, org.jfree.ui.Layer.BACKGROUND);
felix@2138: }
felix@2138: }
felix@2138: }
ingo@1679:
felix@2152:
ingo@369: /**
ingo@2054: * Adjusts the axes of a plot. This method sets the labelFont of the
ingo@2054: * X axis.
ingo@2054: *
ingo@369: * @param plot The XYPlot of the chart.
ingo@369: */
ingo@369: protected void adjustAxes(XYPlot plot) {
ingo@2054: ValueAxis xaxis = plot.getDomainAxis();
ingo@2054:
ingo@2054: ChartSettings chartSettings = getChartSettings();
ingo@2054: if (chartSettings == null) {
ingo@2054: return;
felix@1931: }
ingo@2054:
ingo@2054: Font labelFont = new Font(
ingo@2054: DEFAULT_FONT_NAME,
ingo@2054: Font.BOLD,
ingo@2054: getXAxisLabelFontSize());
ingo@2054:
ingo@2054: xaxis.setLabelFont(labelFont);
ingo@369: }
ingo@414:
ingo@414:
felix@1940: /**
felix@1940: * Set some Stroke/Grid defaults.
felix@1940: */
ingo@419: protected void adjustPlot(XYPlot plot) {
ingo@419: Stroke gridStroke = new BasicStroke(
ingo@419: DEFAULT_GRID_LINE_WIDTH,
ingo@419: BasicStroke.CAP_BUTT,
ingo@419: BasicStroke.JOIN_MITER,
ingo@419: 3.0f,
ingo@419: new float[] { 3.0f },
ingo@419: 0.0f);
ingo@419:
ingo@2047: ChartSettings cs = getChartSettings();
ingo@2047: boolean isGridVisible = cs != null ? isGridVisible(cs) : true;
ingo@2047:
ingo@419: plot.setDomainGridlineStroke(gridStroke);
ingo@419: plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR);
ingo@2047: plot.setDomainGridlinesVisible(isGridVisible);
ingo@419:
ingo@419: plot.setRangeGridlineStroke(gridStroke);
ingo@419: plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR);
ingo@2047: plot.setRangeGridlinesVisible(isGridVisible);
ingo@654:
ingo@654: plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));
ingo@419: }
ingo@419:
ingo@419:
ingo@1645: /**
ingo@1645: * This method walks over all axes (domain and range) of plot and
ingo@1645: * calls localizeDomainAxis() for domain axes or localizeRangeAxis() for
ingo@1645: * range axes.
ingo@1645: *
ingo@1645: * @param plot The XYPlot.
ingo@1645: */
ingo@1645: private void localizeAxes(XYPlot plot) {
ingo@1645: for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) {
ingo@1645: ValueAxis axis = plot.getDomainAxis(i);
ingo@1645:
ingo@1645: if (axis != null) {
ingo@1645: localizeDomainAxis(axis);
ingo@1645: }
ingo@1645: else {
ingo@1645: logger.warn("Domain axis at " + i + " is null.");
ingo@1645: }
ingo@1645: }
ingo@1645:
ingo@1645: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) {
ingo@1645: ValueAxis axis = plot.getRangeAxis(i);
ingo@1645:
ingo@1645: if (axis != null) {
ingo@1645: localizeRangeAxis(axis);
ingo@1645: }
ingo@1645: else {
ingo@1645: logger.warn("Range axis at " + i + " is null.");
ingo@1645: }
ingo@1645: }
ingo@1645: }
ingo@1645:
ingo@1645:
ingo@1645: /**
ingo@1645: * Overrides the NumberFormat with the NumberFormat for the current locale
ingo@1645: * that is provided by getLocale().
ingo@1645: *
ingo@1645: * @param domainAxis The domain axis that needs localization.
ingo@1645: */
ingo@1645: protected void localizeDomainAxis(ValueAxis domainAxis) {
ingo@1645: NumberFormat nf = NumberFormat.getInstance(getLocale());
ingo@1645: ((NumberAxis) domainAxis).setNumberFormatOverride(nf);
ingo@1645: }
ingo@1645:
ingo@1645:
ingo@1645: /**
ingo@1645: * Overrides the NumberFormat with the NumberFormat for the current locale
ingo@1645: * that is provided by getLocale().
ingo@1645: *
ingo@1645: * @param domainAxis The domain axis that needs localization.
ingo@1645: */
ingo@1645: protected void localizeRangeAxis(ValueAxis rangeAxis) {
ingo@1645: NumberFormat nf = NumberFormat.getInstance(getLocale());
ingo@1645: ((NumberAxis) rangeAxis).setNumberFormatOverride(nf);
ingo@1645: }
ingo@1645:
ingo@1645:
felix@1932: /**
felix@1861: * Register annotations like MainValues for later plotting
felix@1861: *
felix@1861: * @param o list of annotations (data of facet).
felix@1861: * @param facet The facet. This facet does NOT support any data objects. Use
felix@1861: * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
felix@1861: * data.
felix@1861: * @param theme Theme document for given annotations.
felix@1861: * @param visible The visibility of the annotations.
felix@1849: */
felix@1849: protected void doAnnotations(
felix@1849: FLYSAnnotation annotations,
ingo@2325: ArtifactAndFacet aandf,
felix@1849: Document theme,
felix@1849: boolean visible
felix@1849: ){
felix@1849: logger.debug("doAnnotations");
ingo@2332: if (aandf == null) {
ingo@2332: logger.warn(
ingo@2332: "Facet for Annotations is null. " +
ingo@2332: "This should never happen!");
ingo@2332: return;
ingo@2332: }
ingo@2332:
ingo@2325: Facet facet = aandf.getFacet();
felix@1849:
felix@1861: // Add all annotations to our annotation pool.
felix@1849: annotations.setTheme(theme);
felix@2183: if (facet != null)
ingo@2325: annotations.setLabel(aandf.getFacetDescription());
felix@1849: addAnnotations(annotations, visible);
felix@1849: }
ingo@1986:
ingo@1986:
ingo@1986: /**
felix@2206: * Do Points out.
felix@2206: */
felix@2206: protected void doPoints(
felix@2206: Object o,
ingo@2325: ArtifactAndFacet aandf,
felix@2206: Document theme,
felix@2206: boolean visible,
felix@2206: int axisIndex
felix@2206: ) {
ingo@2325: String seriesName = aandf.getFacetDescription();
felix@2206: XYSeries series = new StyledXYSeries(seriesName, theme);
felix@2206:
felix@2206: // Add text annotations for single points.
felix@2206: List xy = new ArrayList();
felix@2206:
felix@2206: try {
felix@2206: JSONArray points = new JSONArray((String) o);
felix@2206: for (int i = 0; i < points.length(); i++) {
felix@2206: JSONArray array = points.getJSONArray(i);
felix@2206: double x = array.getDouble(0);
felix@2206: double y = array.getDouble(1);
felix@2206: String name = array.getString(2);
felix@2206: boolean act = array.getBoolean(3);
felix@2206: if (!act) {
felix@2206: continue;
felix@2206: }
felix@2206: //logger.debug(" x " + x + " y " + y );
felix@2206: series.add(x, y, false);
felix@2206: xy.add(new CollisionFreeXYTextAnnotation(name, x, y));
felix@2206: }
felix@2206: }
felix@2206: catch(JSONException e){
felix@2206: logger.error("Could not decode json.");
felix@2206: }
felix@2206:
felix@2206: FLYSAnnotation annotations = new FLYSAnnotation(null, null, null, theme);
felix@2206: annotations.setTextAnnotations(xy);
felix@2206:
felix@2206: doAnnotations(annotations, null, theme, visible);
felix@2206: addAxisSeries(series, axisIndex, visible);
felix@2206: }
felix@2206:
felix@2206:
felix@2161: /** Two Ranges that span a rectangular area. */
felix@2161: public static class Area {
felix@2161: protected Range xRange;
felix@2161: protected Range yRange;
felix@2161:
felix@2161: public Area(Range rangeX, Range rangeY) {
felix@2161: this.xRange = rangeX;
felix@2161: this.yRange = rangeY;
felix@2161: }
felix@2161:
felix@2163: public Area(ValueAxis axisX, ValueAxis axisY) {
felix@2163: this.xRange = axisX.getRange();
felix@2163: this.yRange = axisY.getRange();
felix@2163: }
felix@2163:
felix@2161: public double ofLeft(double percent) {
felix@2161: return xRange.getLowerBound()
felix@2161: + xRange.getLength() * percent;
felix@2161: }
felix@2161:
felix@2163: public double ofRight(double percent) {
felix@2163: return xRange.getUpperBound()
felix@2163: - xRange.getLength() * percent;
felix@2163: }
felix@2163:
felix@2161: public double ofGround(double percent) {
felix@2161: return yRange.getLowerBound()
felix@2161: + yRange.getLength() * percent;
felix@2161: }
felix@2161:
felix@2161: public double atTop() {
felix@2161: return yRange.getUpperBound();
felix@2161: }
felix@2161:
felix@2161: public double atGround() {
felix@2161: return yRange.getLowerBound();
felix@2161: }
felix@2161:
felix@2163: public double atRight() {
felix@2163: return xRange.getUpperBound();
felix@2163: }
felix@2163:
felix@2161: public double atLeft() {
felix@2161: return xRange.getLowerBound();
felix@2161: }
felix@2193:
felix@2193: public double above(double percent, double base) {
felix@2193: return base + yRange.getLength() * percent;
felix@2193: }
felix@2161: }
ingo@369: }
ingo@369: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :