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 :