changeset 2330:594885703687

Picked changes r4015:4026 from trunk. flys-artifacts/tags/2.6@4028 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Fri, 10 Feb 2012 11:18:27 +0000
parents d999062c20e6
children 2c96222dd773
files flys-artifacts/ChangeLog flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java flys-artifacts/src/main/java/de/intevation/flys/exports/ChartHelper.java flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java flys-artifacts/src/main/java/de/intevation/flys/jfree/Bounds.java flys-artifacts/src/main/java/de/intevation/flys/jfree/DoubleBounds.java flys-artifacts/src/main/java/de/intevation/flys/jfree/TimeBounds.java flys-artifacts/src/main/resources/messages.properties flys-artifacts/src/main/resources/messages_de.properties flys-artifacts/src/main/resources/messages_de_DE.properties flys-artifacts/src/main/resources/messages_en.properties
diffstat 14 files changed, 665 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/flys-artifacts/ChangeLog	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/ChangeLog	Fri Feb 10 11:18:27 2012 +0000
@@ -1,3 +1,78 @@
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java: Write
+	  correct min and max values for date axes into the info document.
+
+	* src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java:
+	  Enabled zooming for timeseries charts.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/Bounds.java,
+	  src/main/java/de/intevation/flys/jfree/TimeBounds.java,
+	  src/main/java/de/intevation/flys/jfree/DoubleBounds.java: Added a method
+	  applyBounds(ValueAxis, int) that might be used to adapt the range of the
+	  axis to the bounds adding a space to the left and right.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Defined
+	  new abstract methods for setting and getting Bounds. Modified and
+	  renamed getValueAxisRange(). This method is now called
+	  getValueAxisRangeFromRequest() and returns no longer a Range object but
+	  a String array that consists of the raw string values speicified in the
+	  request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented the missing method getDomainAxisRange(). This method returns
+	  a Range object based on the String array returned from
+	  getValueAxisRangeFromRequest().
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/TimeBounds.java: Added new
+	  methods getLowerAsDate() and getUpperAsDate(). The toString() method
+	  will now return a string that contains a human readable date string.
+
+	* src/main/java/de/intevation/flys/jfree/DoubleBounds.java: Made 'lower'
+	  always be smaller than 'upper' in the default constructor.
+
+2012-02-10  Sascha L. Teichmann	<sascha.teichmann@intevation.de>
+
+	* src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java:
+	  Give more precise message when an error occurs in W~W relation.
+
+	* src/main/resources/messages.properties,
+	  src/main/resources/messages_de_DE.properties,
+	  src/main/resources/messages_en.properties,
+	  src/main/resources/messages_de.properties:
+	  Improved error messages.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartGenerator.java: Modified
+	  and renamend getDomainAxisRange(). This method is now called
+	  getDomainAxisRangeFromRequest() and returns no longer a Range object but
+	  a String array that consists of the raw string values specified in the
+	  request document.
+
+	* src/main/java/de/intevation/flys/exports/XYChartGenerator.java:
+	  Implemented the missing method getDomainAxisRange(). This method returns
+	  a Range object based on the String array returned from
+	  getDomainAxisRangeFromRequest().
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/exports/ChartHelper.java: Added a
+	  helper function to determine the min and max bounds (x and y) for
+	  TimeSeriesCollections.
+
+2012-02-10  Ingo Weinzierl <ingo@intevation.de>
+
+	* src/main/java/de/intevation/flys/jfree/TimeBounds.java,
+	  src/main/java/de/intevation/flys/jfree/DoubleBounds.java: Removed
+	  useless imports.
+
 2012-02-10  Felix Wolfsteller	<felix.wolfsteller@intevation.de>
 
 	* src/main/java/de/intevation/flys/artifacts/model/WW.java:
--- a/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java	Fri Feb 10 11:18:27 2012 +0000
@@ -903,11 +903,28 @@
         return relateWs(km1, km2, RELATE_WS_SAMPLES, errors);
     }
 
+    private static class ErrorHandler {
+
+        boolean     hasErrors;
+        Calculation errors;
+
+        ErrorHandler(Calculation errors) {
+            this.errors = errors;
+        }
+
+        void error(double km, String key, Object ... args) {
+            if (errors != null && !hasErrors) {
+                hasErrors = true;
+                errors.addProblem(km, key, args);
+            }
+        }
+    } // class ErrorHandler
+
+
     /* TODO: Add optimized methods of relateWs to relate one
      *       start km to many end kms. The index generation/spline stuff for 
      *       the start km is always the same.
      */
-
     public double [][] relateWs(
         double      km1, 
         double      km2,
@@ -951,31 +968,51 @@
         TDoubleArrayList qs1 = new TDoubleArrayList(numSamples);
         TDoubleArrayList qs2 = new TDoubleArrayList(numSamples);
 
-        boolean hadErrors = false;
+        ErrorHandler err = new ErrorHandler(errors);
 
         int i = 0;
         for (double p = 0d; p <= N-1; p += stepWidth, ++i) {
+
+            double q1;
             try {
-                double q1 = iQ1.value(p);
-                double w1 = qW1.value(q1);
-                double q2 = iQ2.value(p);
-                double w2 = qW2.value(q2);
-                ws1.add(w1);
-                ws2.add(w2);
-                qs1.add(q1);
-                qs2.add(q2);
+                q1 = iQ1.value(p);
             }
             catch (ArgumentOutsideDomainException aode) {
-                if (!hadErrors) {
-                    // XXX: I'm not sure if this really can happen
-                    //      and if we should report this more than once.
-                    hadErrors = true;
-                    if (errors != null) {
-                        errors.addProblem("relating.w.w.failed");
-                        log.warn("W~W failed", aode);
-                    }
-                }
+                err.error(km1, "w.w.qkm1.failed", p);
+                continue;
             }
+
+            double w1;
+            try {
+                w1 = qW1.value(q1);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km1, "w.w.wkm1.failed", p);
+                continue;
+            }
+
+            double q2;
+            try {
+                q2 = iQ2.value(p);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km2, "w.w.qkm2.failed", p);
+                continue;
+            }
+
+            double w2;
+            try {
+                w2 = qW2.value(q2);
+            }
+            catch (ArgumentOutsideDomainException aode) {
+                err.error(km2, "w.w.wkm2.failed", p);
+                continue;
+            }
+
+            ws1.add(w1);
+            ws2.add(w2);
+            qs1.add(q1);
+            qs2.add(q2);
         }
 
         return new double [][] {
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.java	Fri Feb 10 11:18:27 2012 +0000
@@ -49,6 +49,7 @@
 
 import de.intevation.flys.artifacts.FLYSArtifact;
 import de.intevation.flys.artifacts.resources.Resources;
+import de.intevation.flys.jfree.Bounds;
 import de.intevation.flys.jfree.EnhancedLineAndShapeRenderer;
 import de.intevation.flys.jfree.StableXYDifferenceRenderer;
 import de.intevation.flys.jfree.StyledAreaSeriesCollection;
@@ -248,6 +249,14 @@
      */
     public abstract Range[] getRangesForAxis(int index);
 
+    public abstract Bounds getXBounds(int axis);
+
+    protected abstract void setXBounds(int axis, Bounds bounds);
+
+    public abstract Bounds getYBounds(int axis);
+
+    protected abstract void setYBounds(int axis, Bounds bounds);
+
 
     /**
      * This method should be used by concrete subclasses to add subtitle to
@@ -1003,9 +1012,11 @@
 
 
     /**
-     * Get Range of Domain ("X"-) Axis from request.
+     * Returns the X-Axis range as String array from request document.
+     *
+     * @return a String array with [lower, upper].
      */
-    protected Range getDomainAxisRange() {
+    protected String[] getDomainAxisRangeFromRequest() {
         Element xrange = (Element)XMLUtils.xpath(
             request,
             XPATH_CHART_X_RANGE,
@@ -1021,34 +1032,11 @@
         String lower = xrange.getAttributeNS(uri, "from");
         String upper = xrange.getAttributeNS(uri, "to");
 
-        if (lower.length() > 0 && upper.length() > 0) {
-            try {
-                double from = Double.parseDouble(lower);
-                double to   = Double.parseDouble(upper);
-
-                if (from == 0 && to == 0) {
-                    logger.debug("No range specified. Lower and upper X == 0");
-                    return null;
-                }
-
-                if (from > to) {
-                    double tmp = to;
-                    to         = from;
-                    from       = tmp;
-                }
-
-                return new Range(from, to);
-            }
-            catch (NumberFormatException nfe) {
-                logger.warn("Wrong values for domain axis range.");
-            }
-        }
-
-        return null;
+        return new String[] { lower, upper };
     }
 
 
-    protected Range getValueAxisRange() {
+    protected String[] getValueAxisRangeFromRequest() {
         Element yrange = (Element)XMLUtils.xpath(
             request,
             XPATH_CHART_Y_RANGE,
@@ -1062,30 +1050,10 @@
 
         String uri = ArtifactNamespaceContext.NAMESPACE_URI;
 
-
         String lower = yrange.getAttributeNS(uri, "from");
         String upper = yrange.getAttributeNS(uri, "to");
 
-        if (lower.length() > 0 && upper.length() > 0) {
-            try {
-                double from = Double.parseDouble(lower);
-                double to   = Double.parseDouble(upper);
-
-                if (from == 0 && to == 0) {
-                    logger.debug("No range specified. Lower and upper Y == 0");
-                    return null;
-                }
-
-                return from > to
-                       ? new Range(to, from)
-                       : new Range(from, to);
-            }
-            catch (NumberFormatException nfe) {
-                logger.warn("Wrong values for value axis range.");
-            }
-        }
-
-        return null;
+        return new String[] { lower, upper };
     }
 
 
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartHelper.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/ChartHelper.java	Fri Feb 10 11:18:27 2012 +0000
@@ -2,9 +2,16 @@
 
 import org.jfree.data.Range;
 import org.jfree.data.xy.XYDataset;
+import org.jfree.data.time.RegularTimePeriod;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.data.time.TimeSeries;
 
 import org.apache.log4j.Logger;
 
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.DoubleBounds;
+import de.intevation.flys.jfree.TimeBounds;
+
 
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
@@ -73,6 +80,63 @@
     }
 
 
+    public static Bounds[] getBounds(TimeSeriesCollection collection) {
+        int seriesCount = collection != null ? collection.getSeriesCount() : 0;
+
+        if (seriesCount == 0) {
+            logger.warn("TimeSeriesCollection is empty or has no Series set.");
+            return null;
+        }
+
+        boolean foundValue = false;
+
+        long lowerX = Long.MAX_VALUE;
+        long upperX = -Long.MAX_VALUE;
+
+        double lowerY = Double.MAX_VALUE;
+        double upperY = -Double.MAX_VALUE;
+
+        for (int i = 0, m = seriesCount; i < m; i++) {
+            TimeSeries series = collection.getSeries(i);
+
+            for (int j = 0, n = collection.getItemCount(i); j < n; j++) {
+                RegularTimePeriod rtp = series.getTimePeriod(j);
+
+                if (rtp == null) {
+                    continue;
+                }
+
+                foundValue = true;
+
+                long start = rtp.getFirstMillisecond();
+                long end   = rtp.getLastMillisecond();
+
+                if (start < lowerX) {
+                    lowerX = start;
+                }
+
+                if (end > upperX) {
+                    upperX = end;
+                }
+
+                double y = series.getValue(j).doubleValue();
+
+                lowerY = Math.min(lowerY, y);
+                upperY = Math.max(upperY, y);
+            }
+        }
+
+        if (foundValue) {
+            return new Bounds[] {
+                new TimeBounds(lowerX, upperX),
+                new DoubleBounds(lowerY, upperY)
+            };
+        }
+
+        return null;
+    }
+
+
     /**
      * Expand range by percent.
      *
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/InfoGeneratorHelper.java	Fri Feb 10 11:18:27 2012 +0000
@@ -24,6 +24,8 @@
 import de.intevation.artifacts.common.utils.XMLUtils;
 import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
 
+import de.intevation.flys.jfree.Bounds;
+
 
 /**
  * This class helps generating chart info documents.
@@ -204,13 +206,20 @@
         Date from = axis.getMinimumDate();
         Date to   = axis.getMaximumDate();
 
+        Bounds bounds = null;
+        if (type.equals("range")) {
+            bounds = generator.getYBounds(pos);
+        }
+        else {
+            bounds = generator.getXBounds(pos);
+        }
+
         cr.addAttr(e, "axistype", "date", true);
         cr.addAttr(e, "from", String.valueOf(from.getTime()), true);
         cr.addAttr(e, "to", String.valueOf(to.getTime()), true);
 
-        // TODO Get correct min/max
-        cr.addAttr(e, "min", String.valueOf(from.getTime()), true);
-        cr.addAttr(e, "max", String.valueOf(to.getTime()), true);
+        cr.addAttr(e, "min", bounds.getLower().toString(), true);
+        cr.addAttr(e, "max", bounds.getUpper().toString(), true);
 
         return e;
     }
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/TimeseriesChartGenerator.java	Fri Feb 10 11:18:27 2012 +0000
@@ -11,6 +11,7 @@
 
 import org.jfree.chart.ChartFactory;
 import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.ValueAxis;
 import org.jfree.chart.plot.XYPlot;
 
 import org.jfree.data.Range;
@@ -18,6 +19,10 @@
 import org.jfree.data.general.Series;
 import org.jfree.data.xy.XYDataset;
 
+import de.intevation.flys.jfree.Bounds;
+import de.intevation.flys.jfree.DoubleBounds;
+import de.intevation.flys.jfree.TimeBounds;
+
 
 /**
  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
@@ -106,8 +111,9 @@
         protected void mergeRanges(TimeSeriesCollection dataset) {
             logger.debug("Range after merging: " + range);
 
-            Range[] xyRanges = ChartHelper.getRanges(dataset);
-            range = Range.combine(range, xyRanges[1]);
+            Bounds[] xyRanges = ChartHelper.getBounds(dataset);
+
+            // TODO COMBINE BOUNDS!
 
             logger.debug("Range after merging: " + range);
         }
@@ -120,9 +126,12 @@
         Logger.getLogger(TimeseriesChartGenerator.class);
 
 
-    protected Map<Integer, Range> xRanges;
+    public static final int AXIS_SPACE = 5;
 
-    protected Map<Integer, Range> yRanges;
+
+    protected Map<Integer, Bounds> xRanges;
+
+    protected Map<Integer, Bounds> yRanges;
 
 
 
@@ -132,8 +141,8 @@
     public TimeseriesChartGenerator() {
         super();
 
-        xRanges = new HashMap<Integer, Range>();
-        yRanges = new HashMap<Integer, Range>();
+        xRanges = new HashMap<Integer, Bounds>();
+        yRanges = new HashMap<Integer, Bounds>();
     }
 
 
@@ -161,6 +170,8 @@
         addSubtitles(chart);
         addDatasets(plot);
 
+        adaptZoom(plot);
+
         return chart;
     }
 
@@ -171,15 +182,31 @@
     }
 
 
-    @Override
-    protected void setXRange(int axis, Range range) {
-        xRanges.put(Integer.valueOf(axis), range);
+    // TODO DECLARE IN UPPER CLASS AND ADD OVERRIDE ANNOTATION
+    protected Bounds getXRange(int axis) {
+        return xRanges.get(Integer.valueOf(axis));
     }
 
 
     @Override
+    // TODO setXRange should always await a Bounds instance!
+    // TODO SHOULD BE REMOVED WHEN DEFINED IN UPPER CLASS
+    protected void setXRange(int axis, Range range) {
+        // do nothing here, we will use setXRange(int, Bounds) now
+    }
+
+
+    @Override
+    // TODO setYRange should always await a Bounds instance!
     protected void setYRange(int axis, Range range) {
-        yRanges.put(Integer.valueOf(axis), range);
+        if (range == null) {
+            logger.warn("Range is null!");
+            return;
+        }
+
+        setYBounds(Integer.valueOf(axis), new DoubleBounds(
+            range.getLowerBound(),
+            range.getUpperBound()));
     }
 
 
@@ -195,6 +222,37 @@
     }
 
 
+    // TODO THIS SHOULD BE DONE IN AN UPPER CLASS!
+    @Override
+    public void addAxisDataset(XYDataset dataset, int idx, boolean visible) {
+        if (dataset == null || idx < 0) {
+            return;
+        }
+
+        AxisDataset axisDataset = getAxisDataset(idx);
+
+        Bounds[] bounds = ChartHelper.getBounds((TimeSeriesCollection)dataset);
+
+        if (bounds == null) {
+            logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
+            return;
+        }
+
+        if (visible) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("Add new AxisDataset at index: " + idx);
+                logger.debug("X extent: " + bounds[0]);
+                logger.debug("Y extent: " + bounds[1]);
+            }
+
+            axisDataset.addDataset(dataset);
+        }
+
+        combineXRanges(bounds[0], 0);
+        combineYRanges(bounds[1], idx);
+    }
+
+
     /**
      * Effect: extend range of x axis to include given limits.
      * @param range the given ("minimal") range.
@@ -202,40 +260,237 @@
      */
     @Override
     protected void combineXRanges(Range range, int index) {
-        if (range != null) {
-            Range old = xRanges.get(index);
+        throw new RuntimeException(
+            "TimeseriesChartGenerator.combineXRanges is not implemented!");
+    }
+
+
+    protected void combineXRanges(Bounds bounds, int index) {
+        if (bounds != null) {
+            Bounds old = getXRange(index);
 
             if (old != null) {
-                range = Range.combine(old, range);
+                bounds = bounds.combine(old);
             }
 
-            xRanges.put(index, range);
+            setXBounds(index, bounds);
         }
     }
 
 
+    protected void combineYRanges(Bounds bounds, int index) {
+        if (bounds != null) {
+            Bounds old = getYBounds(index);
+
+            if (old != null) {
+                bounds = bounds.combine(old);
+            }
+
+            setYBounds(index, bounds);
+        }
+    }
+
+
+    // TODO REPLACE THIS METHOD WITH getBoundsForAxis(index)
+    @Override
+    public Range[] getRangesForAxis(int index) {
+        // TODO
+        Bounds[] bounds = getBoundsForAxis(index);
+
+        return new Range[] {
+            new Range(
+                bounds[0].getLower().doubleValue(),
+                bounds[0].getUpper().doubleValue()),
+            new Range(
+                bounds[1].getLower().doubleValue(),
+                bounds[1].getUpper().doubleValue())
+        };
+    }
+
+
     @Override
-    public Range[] getRangesForAxis(int index) {
-        logger.debug("Return ranges for axis at: " + index);
+    public Bounds getXBounds(int axis) {
+        return xRanges.get(axis);
+    }
 
-        Range rx = xRanges.get(Integer.valueOf(0));
-        Range ry = yRanges.get(Integer.valueOf(index));
+
+    @Override
+    protected void setXBounds(int axis, Bounds bounds) {
+        xRanges.put(axis, bounds);
+    }
+
+
+    @Override
+    public Bounds getYBounds(int axis) {
+        return yRanges.get(axis);
+    }
+
+
+    @Override
+    protected void setYBounds(int axis, Bounds bounds) {
+        yRanges.put(axis, bounds);
+    }
+
+
+    public Bounds[] getBoundsForAxis(int index) {
+        logger.debug("Return x and y bounds for axis at: " + index);
+
+        Bounds rx = getXBounds(Integer.valueOf(index));
+        Bounds ry = getYBounds(Integer.valueOf(index));
 
         if (rx == null) {
             logger.warn("Range for x axis not set." +
                         " Using default values: 0 - 1.");
-            rx = new Range(0, 1);
-        }
-        if (ry == null) {
-            logger.warn("Range for y" + index +
-                        " axis not set. Using default values: 0 - 1.");
-            ry = new Range(0, 1);
+            rx = new TimeBounds(0l, 1l);
         }
 
-        logger.debug("X Range is: " + rx);
-        logger.debug("Y Range is: " + ry);
+        if (ry == null) {
+            logger.warn("Range for y axis not set." +
+                        " Using default values: 0 - 1.");
+            ry = new DoubleBounds(0l, 1l);
+        }
 
-        return new Range[] {rx, ry};
+        logger.debug("X Bounds at index " + index + " is: " + rx);
+        logger.debug("Y Bounds at index " + index + " is: " + ry);
+
+        return new Bounds[] {rx, ry};
+    }
+
+
+    public Bounds getDomainAxisRange() {
+        String[] ranges = getDomainAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No zoom range for domain axis specified.");
+            return null;
+        }
+
+        if (ranges[0] == null || ranges[1] == null) {
+            logger.warn("Invalid ranges for domain axis specified!");
+            return null;
+        }
+
+        try {
+            double lower = Double.parseDouble(ranges[0]);
+            double upper = Double.parseDouble(ranges[1]);
+
+            return new DoubleBounds(lower, upper);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Invalid ranges for domain axis specified: " + nfe);
+        }
+
+        return null;
+    }
+
+
+    public Bounds getValueAxisRange() {
+        String[] ranges = getValueAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No zoom range for domain axis specified.");
+            return null;
+        }
+
+        if (ranges[0] == null || ranges[1] == null) {
+            logger.warn("Invalid ranges for domain axis specified!");
+            return null;
+        }
+
+        try {
+            double lower = Double.parseDouble(ranges[0]);
+            double upper = Double.parseDouble(ranges[1]);
+
+            return new DoubleBounds(lower, upper);
+        }
+        catch (NumberFormatException nfe) {
+            logger.warn("Invalid ranges for domain axis specified: " + nfe);
+        }
+
+        return null;
+    }
+
+
+    protected void adaptZoom(XYPlot plot) {
+        logger.debug("Adapt zoom of Timeseries chart.");
+
+        zoomX(plot, plot.getDomainAxis(), getXRange(0), getDomainAxisRange());
+
+        Bounds valueAxisBounds = getValueAxisRange();
+
+        for (int j = 0, n = plot.getRangeAxisCount(); j < n; j++) {
+            zoomY(
+                plot,
+                plot.getRangeAxis(j),
+                getYBounds(j),
+                valueAxisBounds);
+        }
+    }
+
+
+    protected void zoomX(
+        XYPlot    plot,
+        ValueAxis axis,
+        Bounds    total,
+        Bounds    user
+    ) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("== Zoom X axis ==");
+            logger.debug("    Total axis range  : " + total);
+            logger.debug("    User defined range: " + user);
+        }
+
+        if (user != null) {
+            long min  = total.getLower().longValue();
+            long max  = total.getUpper().longValue();
+            long diff = max > min ? max - min : min - max;
+
+            long newMin = (long) Math.round(min + user.getLower().doubleValue() * diff);
+            long newMax = (long) Math.round(min + user.getUpper().doubleValue() * diff);
+
+            TimeBounds newBounds = new TimeBounds(newMin, newMax);
+
+            logger.debug("    Zoom axis to: " + newBounds);
+
+            newBounds.applyBounds(axis, AXIS_SPACE);
+        }
+        else {
+            logger.debug("No user specified zoom values found!");
+            total.applyBounds(axis, AXIS_SPACE);
+        }
+    }
+
+
+    protected void zoomY(
+        XYPlot    plot,
+        ValueAxis axis,
+        Bounds    total,
+        Bounds    user
+    ) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("== Zoom Y axis ==");
+            logger.debug("    Total axis range  : " + total);
+            logger.debug("    User defined range: " + user);
+        }
+
+        if (user != null) {
+            double min  = total.getLower().doubleValue();
+            double max  = total.getUpper().doubleValue();
+            double diff = max > min ? max - min : min - max;
+
+            double newMin = min + user.getLower().doubleValue() * diff;
+            double newMax = min + user.getUpper().doubleValue() * diff;
+
+            DoubleBounds newBounds = new DoubleBounds(newMin, newMax);
+
+            logger.debug("    Zoom axis to: " + newBounds);
+
+            newBounds.applyBounds(axis, AXIS_SPACE);
+        }
+        else {
+            logger.debug("No user specified zoom values found!");
+            total.applyBounds(axis, AXIS_SPACE);
+        }
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java	Fri Feb 10 11:18:27 2012 +0000
@@ -39,6 +39,7 @@
 import de.intevation.artifactdatabase.state.ArtifactAndFacet;
 import de.intevation.artifactdatabase.state.Facet;
 
+import de.intevation.flys.jfree.Bounds;
 import de.intevation.flys.jfree.FLYSAnnotation;
 import de.intevation.flys.jfree.StickyAxisAnnotation;
 import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
@@ -465,6 +466,72 @@
     }
 
 
+    protected Range getDomainAxisRange() {
+        String[] ranges = getDomainAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No zoom range for domain axis specified.");
+            return null;
+        }
+
+        if (ranges[0].length() > 0 && ranges[1].length() > 0) {
+            try {
+                double from = Double.parseDouble(ranges[0]);
+                double to   = Double.parseDouble(ranges[1]);
+
+                if (from == 0 && to == 0) {
+                    logger.debug("No range specified. Lower and upper X == 0");
+                    return null;
+                }
+
+                if (from > to) {
+                    double tmp = to;
+                    to         = from;
+                    from       = tmp;
+                }
+
+                return new Range(from, to);
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Wrong values for domain axis range.");
+            }
+        }
+
+        return null;
+    }
+
+
+    protected Range getValueAxisRange() {
+        String[] ranges = getValueAxisRangeFromRequest();
+
+        if (ranges == null || ranges.length < 2) {
+            logger.debug("No range specified. Lower and upper Y == 0");
+            return null;
+        }
+
+        if (ranges[0].length() > 0 && ranges[1].length() > 0) {
+            try {
+                double from = Double.parseDouble(ranges[0]);
+                double to   = Double.parseDouble(ranges[1]);
+
+                if (from == 0 && to == 0) {
+                    logger.debug("No range specified. Lower and upper Y == 0");
+                    return null;
+                }
+
+                return from > to
+                       ? new Range(to, from)
+                       : new Range(from, to);
+            }
+            catch (NumberFormatException nfe) {
+                logger.warn("Wrong values for value axis range.");
+            }
+        }
+
+        return null;
+    }
+
+
     protected boolean zoomX(XYPlot plot, ValueAxis axis, Range range, Range x) {
         return zoom(plot, axis, range, x);
     }
@@ -541,6 +608,38 @@
     }
 
 
+    @Override
+    public Bounds getXBounds(int axis) {
+        // TODO IMPLEMENT ME
+        throw new RuntimeException(
+            "XYChartGenerator.getXBounds(int) not implemented");
+    }
+
+
+    @Override
+    protected void setXBounds(int axis, Bounds bounds) {
+        // TODO IMPLEMENT ME
+        throw new RuntimeException(
+            "XYChartGenerator.setXBounds(int,Bounds) not implemented");
+    }
+
+
+    @Override
+    public Bounds getYBounds(int axis) {
+        // TODO IMPLEMENT ME
+        throw new RuntimeException(
+            "XYChartGenerator.getYBounds(int) not implemented");
+    }
+
+
+    @Override
+    protected void setYBounds(int axis, Bounds bounds) {
+        // TODO IMPLEMENT ME
+        throw new RuntimeException(
+            "XYChartGenerator.setYBounds(int,Bounds) not implemented");
+    }
+
+
     /** Get color for hyk zones by their type (which is the name). */
     public Paint colorForHYKZone(String zoneName) {
         if (zoneName.startsWith("R")) {
--- a/flys-artifacts/src/main/java/de/intevation/flys/jfree/Bounds.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/Bounds.java	Fri Feb 10 11:18:27 2012 +0000
@@ -16,6 +16,8 @@
 
     void applyBounds(ValueAxis axis);
 
+    void applyBounds(ValueAxis axis, int percent);
+
     Bounds combine(Bounds bounds);
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/java/de/intevation/flys/jfree/DoubleBounds.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/DoubleBounds.java	Fri Feb 10 11:18:27 2012 +0000
@@ -15,9 +15,13 @@
     protected double upper;
 
 
+    /**
+     * Default constructor. <b>A DoubleBounds has always set lower &lt;
+     * upper!</b>
+     */
     public DoubleBounds(double lower, double upper) {
-        this.lower = lower;
-        this.upper = upper;
+        this.lower = Math.min(lower, upper);
+        this.upper = Math.max(lower, upper);
     }
 
 
@@ -40,6 +44,13 @@
 
 
     @Override
+    public void applyBounds(ValueAxis axis, int percent) {
+        double space = (upper - lower) / 100 * percent;
+        axis.setRange(new Range(lower-space, upper+space));
+    }
+
+
+    @Override
     public Bounds combine(Bounds bounds) {
         if (bounds == null) {
             return this;
--- a/flys-artifacts/src/main/java/de/intevation/flys/jfree/TimeBounds.java	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/java/de/intevation/flys/jfree/TimeBounds.java	Fri Feb 10 11:18:27 2012 +0000
@@ -2,8 +2,6 @@
 
 import java.util.Date;
 
-import org.apache.log4j.Logger;
-
 import org.jfree.chart.axis.DateAxis;
 import org.jfree.chart.axis.ValueAxis;
 
@@ -29,12 +27,22 @@
     }
 
 
+    public Date getLowerAsDate() {
+        return new Date(lower);
+    }
+
+
     @Override
     public Number getUpper() {
         return Long.valueOf(upper);
     }
 
 
+    public Date getUpperAsDate() {
+        return new Date(upper);
+    }
+
+
     @Override
     public void applyBounds(ValueAxis axis) {
         DateAxis dateAxis = (DateAxis) axis;
@@ -45,6 +53,17 @@
 
 
     @Override
+    public void applyBounds(ValueAxis axis, int percent) {
+        DateAxis dateAxis = (DateAxis) axis;
+
+        long space = (upper - lower) / 100 * percent;
+
+        dateAxis.setMinimumDate(new Date(lower-space));
+        dateAxis.setMaximumDate(new Date(upper+space));
+    }
+
+
+    @Override
     public Bounds combine(Bounds bounds) {
         if (bounds == null) {
             return this;
@@ -63,7 +82,7 @@
 
     @Override
     public String toString() {
-        return "TimeBounds=[" + lower + " ; " + upper + "]";
+        return "TimeBounds=["+ getLowerAsDate() + " ; " + getUpperAsDate() +"]";
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/flys-artifacts/src/main/resources/messages.properties	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/resources/messages.properties	Fri Feb 10 11:18:27 2012 +0000
@@ -178,7 +178,12 @@
 km.not.found = Cannot find km.
 cannot.create.wq.relation = Cannot create W/Q relation.
 cannot.create.index.q.relation = Cannot create index/Q relation.
-relating.w.w.failed = Relating W with W failed.
+
+w.w.qkm1.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm1.failed = Calculating W for WST index {0,number,#.##} failed.
+w.w.qkm2.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm2.failed = Calculating W for WST index {0,number,#.##} failed.
+
 cannot.find.hist.q.for.w = Cannot find Q for W = {0,number,#.##} in timerange {1, date} - {2, date}
 cannot.find.hist.q.tables = Cannot find Discharge Tables for given timerange.
 cannot.find.hist.q.reftable = Cannot find reference Discharge Table for specified Gauge.
--- a/flys-artifacts/src/main/resources/messages_de.properties	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/resources/messages_de.properties	Fri Feb 10 11:18:27 2012 +0000
@@ -178,7 +178,12 @@
 km.not.found = Passender Kilometer konnte nicht gefunden werden.
 cannot.create.wq.relation = W/Q-Beziehung konnte nicht ermittelt werden.
 cannot.create.index.q.relation = Spaltenindex/Q-Beziehung konnte nicht erstellt werden.
-relating.w.w.failed = W-zu-W-Zuordnung fehlgeschlagen.
+
+w.w.qkm1.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen.
+w.w.wkm1.failed = Berechnung von W f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen.
+w.w.qkm2.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen. 
+w.w.wkm2.failed = Berechnung von W f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen. 
+
 cannot.find.hist.q.for.w = Konnte zu W = {0,number,#.##} im Zeitraum ({1,date} - {2,date}) kein Abfluss ermitteln.
 cannot.find.hist.q.tables = Konnte f\u00fcr den angegebenen Zeitraum keine Abflusstafeln finden.
 cannot.find.hist.q.reftable = Konnte f\u00fcr den gew\u00e4hlten Pegel keine Bezugs-Abflusstafel ermitteln.
--- a/flys-artifacts/src/main/resources/messages_de_DE.properties	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/resources/messages_de_DE.properties	Fri Feb 10 11:18:27 2012 +0000
@@ -178,7 +178,12 @@
 km.not.found = Passender Kilometer konnte nicht gefunden werden.
 cannot.create.wq.relation = W/Q-Beziehung konnte nicht ermittelt werden.
 cannot.create.index.q.relation = Spaltenindex/Q-Beziehung konnte nicht erstellt werden.
-relating.w.w.failed = W-zu-W-Zuordnung fehlgeschlagen.
+
+w.w.qkm1.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen.
+w.w.wkm1.failed = Berechnung von W f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen.
+w.w.qkm2.failed = Berechnung von Q f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen. 
+w.w.wkm2.failed = Berechnung von W f\u00fcr WST-Index {0,number,#.##} fehlgeschlagen. 
+
 cannot.find.hist.q.for.w = Konnte zu W = {0,number,#.##} im Zeitraum ({1,date} - {2,date}) kein Abfluss ermitteln.
 cannot.find.hist.q.tables = Konnte f\u00fcr den angegebenen Zeitraum keine Abflusstafeln finden.
 cannot.find.hist.q.reftable = Konnte f\u00fcr den gew\u00e4hlten Pegel keine Bezugs-Abflusstafel ermitteln.
--- a/flys-artifacts/src/main/resources/messages_en.properties	Fri Feb 10 08:28:17 2012 +0000
+++ b/flys-artifacts/src/main/resources/messages_en.properties	Fri Feb 10 11:18:27 2012 +0000
@@ -174,7 +174,12 @@
 km.not.found = Cannot find km.
 cannot.create.wq.relation = Cannot create W/Q relation.
 cannot.create.index.q.relation = Cannot create index/Q relation.
-relating.w.w.failed = Relating W with W failed.
+
+w.w.qkm1.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm1.failed = Calculating W for WST index {0,number,#.##} failed.
+w.w.qkm2.failed = Calculating Q for WST index {0,number,#.##} failed.
+w.w.wkm2.failed = Calculating W for WST index {0,number,#.##} failed.
+
 cannot.find.hist.q.for.w = Cannot find Q for W = {0,number,#.##} in timerange {1, date} - {2, date}
 cannot.find.hist.q.tables = Cannot find Discharge Tables for given timerange.
 cannot.find.hist.q.reftable = Cannot find reference Discharge Table for specified Gauge.

http://dive4elements.wald.intevation.org