ingo@2233: package de.intevation.flys.exports;
ingo@2233:
ingo@2238: import java.awt.Color;
ingo@2238:
ingo@2238: import java.util.ArrayList;
ingo@2238: import java.util.HashMap;
ingo@2238: import java.util.List;
ingo@2238: import java.util.Map;
ingo@2233:
ingo@2233: import org.apache.log4j.Logger;
ingo@2233:
ingo@2238: import org.jfree.chart.ChartFactory;
ingo@2233: import org.jfree.chart.JFreeChart;
ingo@2330: import org.jfree.chart.axis.ValueAxis;
ingo@2238: import org.jfree.chart.plot.XYPlot;
ingo@2238:
ingo@2238: import org.jfree.data.Range;
ingo@2238: import org.jfree.data.time.TimeSeriesCollection;
ingo@2242: import org.jfree.data.general.Series;
ingo@2238: import org.jfree.data.xy.XYDataset;
ingo@2233:
ingo@2330: import de.intevation.flys.jfree.Bounds;
ingo@2330: import de.intevation.flys.jfree.DoubleBounds;
ingo@2330: import de.intevation.flys.jfree.TimeBounds;
ingo@2330:
ingo@2233:
ingo@2233: /**
ingo@2233: * @author Ingo Weinzierl
ingo@2233: */
ingo@2233: public abstract class TimeseriesChartGenerator extends ChartGenerator {
ingo@2233:
ingo@2238:
ingo@2238: /**
ingo@2238: * Inner class TimeseriesAxisDataset stores TimeSeriesCollection.
ingo@2238: */
ingo@2238: public class TimeseriesAxisDataset implements AxisDataset {
ingo@2238:
ingo@2238: protected int axisSymbol;
ingo@2238:
ingo@2238: protected List datasets;
ingo@2238:
ingo@2238: protected Range range;
ingo@2238:
ingo@2238: protected int plotAxisIndex;
ingo@2238:
ingo@2238:
ingo@2238: public TimeseriesAxisDataset(int axisSymbol) {
ingo@2238: this.axisSymbol = axisSymbol;
ingo@2238: this.datasets = new ArrayList();
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2238: @Override
ingo@2238: public void addDataset(XYDataset dataset) {
ingo@2238: if (!(dataset instanceof TimeSeriesCollection)) {
ingo@2238: logger.warn("Skip non TimeSeriesCollection dataset.");
ingo@2238: return;
ingo@2238: }
ingo@2238:
ingo@2238: TimeSeriesCollection tsc = (TimeSeriesCollection) dataset;
ingo@2238:
ingo@2238: datasets.add(tsc);
ingo@2238: mergeRanges(tsc);
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2238: @Override
ingo@2242: public XYDataset[] getDatasets() {
ingo@2242: return (XYDataset[])
ingo@2242: datasets.toArray(new XYDataset[datasets.size()]);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2238: public boolean isEmpty() {
ingo@2238: return datasets.isEmpty();
ingo@2238: }
ingo@2238:
ingo@2238:
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 void setPlotAxisIndex(int plotAxisIndex) {
ingo@2242: this.plotAxisIndex = plotAxisIndex;
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2242: public int getPlotAxisIndex() {
ingo@2242: return plotAxisIndex;
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2242: public boolean isArea(XYDataset dataset) {
ingo@2242: logger.warn("This AxisDataset doesn't support Areas yet!");
ingo@2242: return false;
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2238: protected void mergeRanges(TimeSeriesCollection dataset) {
ingo@2238: logger.debug("Range after merging: " + range);
ingo@2238:
ingo@2330: Bounds[] xyRanges = ChartHelper.getBounds(dataset);
ingo@2330:
ingo@2330: // TODO COMBINE BOUNDS!
ingo@2238:
ingo@2238: logger.debug("Range after merging: " + range);
ingo@2238: }
ingo@2238:
ingo@2238: } // end of TimeseriesAxisDataset class
ingo@2238:
ingo@2238:
ingo@2238:
ingo@2233: private static final Logger logger =
ingo@2233: Logger.getLogger(TimeseriesChartGenerator.class);
ingo@2233:
ingo@2233:
ingo@2330: public static final int AXIS_SPACE = 5;
ingo@2238:
ingo@2330:
ingo@2330: protected Map xRanges;
ingo@2330:
ingo@2330: protected Map yRanges;
ingo@2242:
ingo@2238:
ingo@2238:
ingo@2238: /**
ingo@2238: * The default constructor that initializes internal datastructures.
ingo@2238: */
ingo@2238: public TimeseriesChartGenerator() {
ingo@2238: super();
ingo@2238:
ingo@2330: xRanges = new HashMap();
ingo@2330: yRanges = new HashMap();
ingo@2238: }
ingo@2238:
ingo@2233:
ingo@2233:
ingo@2233: @Override
ingo@2234: public JFreeChart generateChart() {
ingo@2233: logger.info("Generate Timeseries Chart.");
ingo@2233:
ingo@2238: JFreeChart chart = ChartFactory.createTimeSeriesChart(
ingo@2238: getChartTitle(),
ingo@2238: getXAxisLabel(),
ingo@2238: getYAxisLabel(0),
ingo@2238: null,
ingo@2238: true,
ingo@2238: false,
ingo@2238: false);
ingo@2238:
ingo@2233: logger.warn("TODO: IMPLEMENT ME!");
ingo@2234:
ingo@2238: XYPlot plot = (XYPlot) chart.getPlot();
ingo@2238:
ingo@2238: chart.setBackgroundPaint(Color.WHITE);
ingo@2238: plot.setBackgroundPaint(Color.WHITE);
ingo@2238:
ingo@2238: addSubtitles(chart);
ingo@2238: addDatasets(plot);
ingo@2238:
ingo@2330: adaptZoom(plot);
ingo@2330:
ingo@2238: return chart;
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2242: @Override
ingo@2242: protected Series getSeriesOf(XYDataset dataset, int idx) {
ingo@2242: return ((TimeSeriesCollection) dataset).getSeries(idx);
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2330: // TODO DECLARE IN UPPER CLASS AND ADD OVERRIDE ANNOTATION
ingo@2330: protected Bounds getXRange(int axis) {
ingo@2330: return xRanges.get(Integer.valueOf(axis));
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2242: @Override
ingo@2330: // TODO setXRange should always await a Bounds instance!
ingo@2330: // TODO SHOULD BE REMOVED WHEN DEFINED IN UPPER CLASS
ingo@2330: protected void setXRange(int axis, Range range) {
ingo@2330: // do nothing here, we will use setXRange(int, Bounds) now
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: @Override
ingo@2330: // TODO setYRange should always await a Bounds instance!
ingo@2242: protected void setYRange(int axis, Range range) {
ingo@2330: if (range == null) {
ingo@2330: logger.warn("Range is null!");
ingo@2330: return;
ingo@2330: }
ingo@2330:
ingo@2330: setYBounds(Integer.valueOf(axis), new DoubleBounds(
ingo@2330: range.getLowerBound(),
ingo@2330: range.getUpperBound()));
ingo@2242: }
ingo@2242:
ingo@2242:
ingo@2238: /**
ingo@2238: * This method creates new instances of TimeseriesAxisDataset.
ingo@2238: *
ingo@2238: * @param idx The symbol for the new TimeseriesAxisDataset.
ingo@2238: */
ingo@2238: @Override
ingo@2238: protected AxisDataset createAxisDataset(int idx) {
ingo@2238: logger.debug("Create a new AxisDataset for index: " + idx);
ingo@2238: return new TimeseriesAxisDataset(idx);
ingo@2238: }
ingo@2238:
ingo@2238:
ingo@2330: // TODO THIS SHOULD BE DONE IN AN UPPER CLASS!
ingo@2330: @Override
ingo@2330: public void addAxisDataset(XYDataset dataset, int idx, boolean visible) {
ingo@2330: if (dataset == null || idx < 0) {
ingo@2330: return;
ingo@2330: }
ingo@2330:
ingo@2330: AxisDataset axisDataset = getAxisDataset(idx);
ingo@2330:
ingo@2330: Bounds[] bounds = ChartHelper.getBounds((TimeSeriesCollection)dataset);
ingo@2330:
ingo@2330: if (bounds == null) {
ingo@2330: logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
ingo@2330: return;
ingo@2330: }
ingo@2330:
ingo@2330: if (visible) {
ingo@2330: if (logger.isDebugEnabled()) {
ingo@2330: logger.debug("Add new AxisDataset at index: " + idx);
ingo@2330: logger.debug("X extent: " + bounds[0]);
ingo@2330: logger.debug("Y extent: " + bounds[1]);
ingo@2330: }
ingo@2330:
ingo@2330: axisDataset.addDataset(dataset);
ingo@2330: }
ingo@2330:
ingo@2330: combineXRanges(bounds[0], 0);
ingo@2330: combineYRanges(bounds[1], idx);
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2238: /**
ingo@2238: * Effect: extend range of x axis to include given limits.
ingo@2238: * @param range the given ("minimal") range.
ingo@2238: * @param index index of axis to be merged.
ingo@2238: */
ingo@2238: @Override
ingo@2238: protected void combineXRanges(Range range, int index) {
ingo@2330: throw new RuntimeException(
ingo@2330: "TimeseriesChartGenerator.combineXRanges is not implemented!");
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: protected void combineXRanges(Bounds bounds, int index) {
ingo@2330: if (bounds != null) {
ingo@2330: Bounds old = getXRange(index);
ingo@2238:
ingo@2238: if (old != null) {
ingo@2330: bounds = bounds.combine(old);
ingo@2238: }
ingo@2238:
ingo@2330: setXBounds(index, bounds);
ingo@2238: }
ingo@2238: }
ingo@2261:
ingo@2261:
ingo@2330: protected void combineYRanges(Bounds bounds, int index) {
ingo@2330: if (bounds != null) {
ingo@2330: Bounds old = getYBounds(index);
ingo@2330:
ingo@2330: if (old != null) {
ingo@2330: bounds = bounds.combine(old);
ingo@2330: }
ingo@2330:
ingo@2330: setYBounds(index, bounds);
ingo@2330: }
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: // TODO REPLACE THIS METHOD WITH getBoundsForAxis(index)
ingo@2330: @Override
ingo@2330: public Range[] getRangesForAxis(int index) {
ingo@2330: // TODO
ingo@2330: Bounds[] bounds = getBoundsForAxis(index);
ingo@2330:
ingo@2330: return new Range[] {
ingo@2330: new Range(
ingo@2330: bounds[0].getLower().doubleValue(),
ingo@2330: bounds[0].getUpper().doubleValue()),
ingo@2330: new Range(
ingo@2330: bounds[1].getLower().doubleValue(),
ingo@2330: bounds[1].getUpper().doubleValue())
ingo@2330: };
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2261: @Override
ingo@2330: public Bounds getXBounds(int axis) {
ingo@2330: return xRanges.get(axis);
ingo@2330: }
ingo@2261:
ingo@2330:
ingo@2330: @Override
ingo@2330: protected void setXBounds(int axis, Bounds bounds) {
ingo@2330: xRanges.put(axis, bounds);
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: @Override
ingo@2330: public Bounds getYBounds(int axis) {
ingo@2330: return yRanges.get(axis);
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: @Override
ingo@2330: protected void setYBounds(int axis, Bounds bounds) {
ingo@2330: yRanges.put(axis, bounds);
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: public Bounds[] getBoundsForAxis(int index) {
ingo@2330: logger.debug("Return x and y bounds for axis at: " + index);
ingo@2330:
ingo@2330: Bounds rx = getXBounds(Integer.valueOf(index));
ingo@2330: Bounds ry = getYBounds(Integer.valueOf(index));
ingo@2261:
ingo@2261: if (rx == null) {
ingo@2261: logger.warn("Range for x axis not set." +
ingo@2261: " Using default values: 0 - 1.");
ingo@2330: rx = new TimeBounds(0l, 1l);
ingo@2261: }
ingo@2261:
ingo@2330: if (ry == null) {
ingo@2330: logger.warn("Range for y axis not set." +
ingo@2330: " Using default values: 0 - 1.");
ingo@2330: ry = new DoubleBounds(0l, 1l);
ingo@2330: }
ingo@2261:
ingo@2330: logger.debug("X Bounds at index " + index + " is: " + rx);
ingo@2330: logger.debug("Y Bounds at index " + index + " is: " + ry);
ingo@2330:
ingo@2330: return new Bounds[] {rx, ry};
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: public Bounds 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] == null || ranges[1] == null) {
ingo@2330: logger.warn("Invalid ranges for domain axis specified!");
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330: try {
ingo@2330: double lower = Double.parseDouble(ranges[0]);
ingo@2330: double upper = Double.parseDouble(ranges[1]);
ingo@2330:
ingo@2330: return new DoubleBounds(lower, upper);
ingo@2330: }
ingo@2330: catch (NumberFormatException nfe) {
ingo@2330: logger.warn("Invalid ranges for domain axis specified: " + nfe);
ingo@2330: }
ingo@2330:
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: public Bounds getValueAxisRange() {
ingo@2330: String[] ranges = getValueAxisRangeFromRequest();
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] == null || ranges[1] == null) {
ingo@2330: logger.warn("Invalid ranges for domain axis specified!");
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330: try {
ingo@2330: double lower = Double.parseDouble(ranges[0]);
ingo@2330: double upper = Double.parseDouble(ranges[1]);
ingo@2330:
ingo@2330: return new DoubleBounds(lower, upper);
ingo@2330: }
ingo@2330: catch (NumberFormatException nfe) {
ingo@2330: logger.warn("Invalid ranges for domain axis specified: " + nfe);
ingo@2330: }
ingo@2330:
ingo@2330: return null;
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: protected void adaptZoom(XYPlot plot) {
ingo@2330: logger.debug("Adapt zoom of Timeseries chart.");
ingo@2330:
ingo@2330: zoomX(plot, plot.getDomainAxis(), getXRange(0), getDomainAxisRange());
ingo@2330:
ingo@2330: Bounds valueAxisBounds = getValueAxisRange();
ingo@2330:
ingo@2330: for (int j = 0, n = plot.getRangeAxisCount(); j < n; j++) {
ingo@2330: zoomY(
ingo@2330: plot,
ingo@2330: plot.getRangeAxis(j),
ingo@2330: getYBounds(j),
ingo@2330: valueAxisBounds);
ingo@2330: }
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: protected void zoomX(
ingo@2330: XYPlot plot,
ingo@2330: ValueAxis axis,
ingo@2330: Bounds total,
ingo@2330: Bounds user
ingo@2330: ) {
ingo@2330: if (logger.isDebugEnabled()) {
ingo@2330: logger.debug("== Zoom X axis ==");
ingo@2330: logger.debug(" Total axis range : " + total);
ingo@2330: logger.debug(" User defined range: " + user);
ingo@2330: }
ingo@2330:
ingo@2330: if (user != null) {
ingo@2330: long min = total.getLower().longValue();
ingo@2330: long max = total.getUpper().longValue();
ingo@2330: long diff = max > min ? max - min : min - max;
ingo@2330:
ingo@2330: long newMin = (long) Math.round(min + user.getLower().doubleValue() * diff);
ingo@2330: long newMax = (long) Math.round(min + user.getUpper().doubleValue() * diff);
ingo@2330:
ingo@2330: TimeBounds newBounds = new TimeBounds(newMin, newMax);
ingo@2330:
ingo@2330: logger.debug(" Zoom axis to: " + newBounds);
ingo@2330:
ingo@2330: newBounds.applyBounds(axis, AXIS_SPACE);
ingo@2330: }
ingo@2330: else {
ingo@2330: logger.debug("No user specified zoom values found!");
ingo@2330: total.applyBounds(axis, AXIS_SPACE);
ingo@2330: }
ingo@2330: }
ingo@2330:
ingo@2330:
ingo@2330: protected void zoomY(
ingo@2330: XYPlot plot,
ingo@2330: ValueAxis axis,
ingo@2330: Bounds total,
ingo@2330: Bounds user
ingo@2330: ) {
ingo@2330: if (logger.isDebugEnabled()) {
ingo@2330: logger.debug("== Zoom Y axis ==");
ingo@2330: logger.debug(" Total axis range : " + total);
ingo@2330: logger.debug(" User defined range: " + user);
ingo@2330: }
ingo@2330:
ingo@2330: if (user != null) {
ingo@2330: double min = total.getLower().doubleValue();
ingo@2330: double max = total.getUpper().doubleValue();
ingo@2330: double diff = max > min ? max - min : min - max;
ingo@2330:
ingo@2330: double newMin = min + user.getLower().doubleValue() * diff;
ingo@2330: double newMax = min + user.getUpper().doubleValue() * diff;
ingo@2330:
ingo@2330: DoubleBounds newBounds = new DoubleBounds(newMin, newMax);
ingo@2330:
ingo@2330: logger.debug(" Zoom axis to: " + newBounds);
ingo@2330:
ingo@2330: newBounds.applyBounds(axis, AXIS_SPACE);
ingo@2330: }
ingo@2330: else {
ingo@2330: logger.debug("No user specified zoom values found!");
ingo@2330: total.applyBounds(axis, AXIS_SPACE);
ingo@2330: }
ingo@2261: }
ingo@2233: }
ingo@2233: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :