changeset 7043:06a9a241faac generator-refactoring

Factor out annotation handling code
author Andre Heinecke <aheinecke@intevation.de>
date Wed, 18 Sep 2013 17:12:13 +0200
parents 599d3c48474c
children 6ab1464021ae
files artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java artifacts/src/main/java/org/dive4elements/river/exports/LongitudinalSectionGenerator.java artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java artifacts/src/main/java/org/dive4elements/river/jfree/AnnotationHelper.java
diffstat 4 files changed, 377 insertions(+), 364 deletions(-) [+]
line wrap: on
line diff
--- a/artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java	Wed Sep 18 16:26:12 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java	Wed Sep 18 17:12:13 2013 +0200
@@ -21,12 +21,10 @@
 import org.dive4elements.river.artifacts.resources.Resources;
 import org.dive4elements.river.collections.D4EArtifactCollection;
 import org.dive4elements.river.jfree.Bounds;
-import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
 import org.dive4elements.river.jfree.DoubleBounds;
 import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer;
 import org.dive4elements.river.jfree.RiverAnnotation;
 import org.dive4elements.river.jfree.StableXYDifferenceRenderer;
-import org.dive4elements.river.jfree.StickyAxisAnnotation;
 import org.dive4elements.river.jfree.Style;
 import org.dive4elements.river.jfree.StyledAreaSeriesCollection;
 import org.dive4elements.river.jfree.StyledSeries;
@@ -60,8 +58,6 @@
 import org.jfree.chart.JFreeChart;
 import org.jfree.chart.LegendItem;
 import org.jfree.chart.LegendItemCollection;
-import org.jfree.chart.annotations.XYLineAnnotation;
-import org.jfree.chart.annotations.XYTextAnnotation;
 import org.jfree.chart.axis.NumberAxis;
 import org.jfree.chart.plot.XYPlot;
 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
@@ -77,15 +73,9 @@
 import org.dive4elements.river.utils.Formatter;
 
 /**
- * The base class for chart creation. It should provide some basic things that
- * equal in all chart types.
+ * Implementation of the OutGenerator interface for charts.
+ * It should provide some basic things that equal in all chart types.
  *
- * Annotations are added as RiverAnnotations and come in mutliple basic forms:
- * TextAnnotations are labels somewhere in data space, StickyAnnotations are
- * labels of a slice or line in one data dimension (i.e. visualized as label
- * on a single axis).
- *
- * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
  */
 public abstract class ChartGenerator2 implements OutGenerator {
 
@@ -99,7 +89,6 @@
     public static final int    DEFAULT_FONT_SIZE       = 12;
     public static final String DEFAULT_FONT_NAME       = "Tahoma";
 
-    protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
 
     public static final String XPATH_CHART_SIZE =
         "/art:action/art:attributes/art:size";
@@ -155,7 +144,6 @@
         datasets = new TreeMap<Integer, AxisDataset>();
     }
 
-
     /**
      * Adds annotations to list. The given annotation will be visible.
      */
@@ -164,313 +152,6 @@
     }
 
     /**
-     * Add a text and a line annotation.
-     * @param area convenience to determine positions in plot.
-     * @param theme (optional) theme document
-     */
-    protected void addStickyAnnotation(
-        StickyAxisAnnotation annotation,
-        XYPlot plot,
-        ChartArea area,
-        LineStyle lineStyle,
-        TextStyle textStyle,
-        ThemeDocument theme
-    ) {
-        // OPTIMIZE pre-calculate area-related values
-        final float TEXT_OFF = 0.03f;
-
-        XYLineAnnotation lineAnnotation = null;
-        XYTextAnnotation textAnnotation = null;
-
-        int rendererIndex = 0;
-
-        if (annotation.atX()) {
-            textAnnotation = new CollisionFreeXYTextAnnotation(
-                annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
-            // OPTIMIZE externalize the calculation involving PI.
-            //textAnnotation.setRotationAngle(270f*Math.PI/180f);
-            lineAnnotation = createGroundStickAnnotation(
-                area, annotation.getPos(), lineStyle);
-            textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
-            textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
-        }
-        else {
-            // Do the more complicated case where we stick to the Y-Axis.
-            // There is one nasty case (duration curves, where annotations
-            // might stick to the second y-axis).
-            // FIXME: Remove dependency to XYChartGenerator2 here
-            AxisDataset dataset = getAxisDataset(
-                new Integer(annotation.getAxisSymbol()));
-            if (dataset == null) {
-                logger.warn("Annotation should stick to unfindable y-axis: "
-                    + annotation.getAxisSymbol());
-                rendererIndex = 0;
-            }
-            else {
-                rendererIndex = dataset.getPlotAxisIndex();
-            }
-
-            // Stick to the "right" (opposed to left) Y-Axis.
-            if (rendererIndex != 0) {
-                // OPTIMIZE: Pass a different area to this function,
-                //           do the adding to renderer outside (let this
-                //           function return the annotations).
-                //           Note that this path is travelled rarely.
-                ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex));
-                textAnnotation = new CollisionFreeXYTextAnnotation(
-                    annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos());
-                textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
-                textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
-                lineAnnotation = createRightStickAnnotation(
-                    area2, annotation.getPos(), lineStyle);
-                if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
-                    // New line annotation to hit curve.
-                    if (theme.parseShowVerticalLine()) {
-                        XYLineAnnotation hitLineAnnotation =
-                            createStickyLineAnnotation(
-                                StickyAxisAnnotation.SimpleAxis.X_AXIS,
-                                annotation.getHitPoint(), annotation.getPos(),// annotation.getHitPoint(),
-                                area2, lineStyle);
-                        plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
-                            org.jfree.ui.Layer.BACKGROUND);
-                    }
-                    if (theme.parseShowHorizontalLine()) {
-                        XYLineAnnotation lineBackAnnotation =
-                            createStickyLineAnnotation(
-                                StickyAxisAnnotation.SimpleAxis.Y_AXIS2,
-                                annotation.getPos(), annotation.getHitPoint(),
-                                area2, lineStyle);
-                        plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
-                            org.jfree.ui.Layer.BACKGROUND);
-                    }
-                }
-            }
-            else { // Stick to the left y-axis.
-                textAnnotation = new CollisionFreeXYTextAnnotation(
-                    annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
-                textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
-                textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
-                lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle);
-                if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
-                    // New line annotation to hit curve.
-                    if (theme.parseShowHorizontalLine()) {
-                        XYLineAnnotation hitLineAnnotation =
-                            createStickyLineAnnotation(
-                                StickyAxisAnnotation.SimpleAxis.Y_AXIS,
-                                annotation.getPos(), annotation.getHitPoint(),
-                                area, lineStyle);
-                        plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
-                            org.jfree.ui.Layer.BACKGROUND);
-                    }
-                    if (theme.parseShowVerticalLine()) {
-                        XYLineAnnotation lineBackAnnotation =
-                            createStickyLineAnnotation(
-                                StickyAxisAnnotation.SimpleAxis.X_AXIS,
-                                annotation.getHitPoint(), annotation.getPos(),
-                                area, lineStyle);
-                        plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
-                            org.jfree.ui.Layer.BACKGROUND);
-                    }
-                }
-            }
-        }
-
-        // Style the text.
-        if (textStyle != null) {
-            textStyle.apply(textAnnotation);
-        }
-
-        // Add the Annotations to renderer.
-        plot.getRenderer(rendererIndex).addAnnotation(textAnnotation,
-            org.jfree.ui.Layer.FOREGROUND);
-        plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation,
-            org.jfree.ui.Layer.FOREGROUND);
-    }
-
-   /**
-     * Create annotation that sticks to "ground" (X) axis.
-     * @param area helper to calculate coordinates
-     * @param pos one-dimensional position (distance from axis)
-     * @param lineStyle the line style to use for the line.
-     */
-    protected static XYLineAnnotation createGroundStickAnnotation(
-        ChartArea area, float pos, LineStyle lineStyle
-    ) {
-        // Style the line.
-        if (lineStyle != null) {
-            return new XYLineAnnotation(
-                pos, area.atGround(),
-                pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET),
-                new BasicStroke(lineStyle.getWidth()),lineStyle.getColor());
-        }
-        else {
-            return new XYLineAnnotation(
-                pos, area.atGround(),
-                pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET));
-        }
-    }
-
-
-    /**
-     * Create annotation that sticks to the second Y axis ("right").
-     * @param area helper to calculate coordinates
-     * @param pos one-dimensional position (distance from axis)
-     * @param lineStyle the line style to use for the line.
-     */
-    protected static XYLineAnnotation createRightStickAnnotation(
-        ChartArea area, float pos, LineStyle lineStyle
-    ) {
-        // Style the line.
-        if (lineStyle != null) {
-            return new XYLineAnnotation(
-                area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos,
-                area.atRight(), pos,
-                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
-        }
-        else {
-            return new XYLineAnnotation(
-                area.atRight(), pos,
-                area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos);
-        }
-    }
-
-
-    /**
-     * Create annotation that sticks to the first Y axis ("left").
-     * @param area helper to calculate coordinates
-     * @param pos one-dimensional position (distance from axis)
-     * @param lineStyle the line style to use for the line.
-     */
-    protected static XYLineAnnotation createLeftStickAnnotation(
-        ChartArea area, float pos, LineStyle lineStyle
-    ) {
-        // Style the line.
-        if (lineStyle != null) {
-            return new XYLineAnnotation(
-                area.atLeft(), pos,
-                area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos,
-                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
-        }
-        else {
-            return new XYLineAnnotation(
-                area.atLeft(), pos,
-                area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos);
-        }
-    }
-
-
-    /**
-     * Create a line from a axis to a given point.
-     * @param axis   The "simple" axis.
-     * @param fromD1 from-location in first dimension.
-     * @param toD2   to-location in second dimension.
-     * @param area   helper to calculate offsets.
-     * @param lineStyle optional line style.
-     */
-    protected static XYLineAnnotation createStickyLineAnnotation(
-        StickyAxisAnnotation.SimpleAxis axis, float fromD1, float toD2,
-        ChartArea area, LineStyle lineStyle
-    ) {
-        double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d;
-        switch(axis) {
-            case X_AXIS:
-                anchorX1 = fromD1;
-                anchorX2 = fromD1;
-                anchorY1 = area.atGround();
-                anchorY2 = toD2;
-                break;
-            case Y_AXIS:
-                anchorX1 = area.atLeft();
-                anchorX2 = toD2;
-                anchorY1 = fromD1;
-                anchorY2 = fromD1;
-                break;
-            case Y_AXIS2:
-                anchorX1 = area.atRight();
-                anchorX2 = toD2;
-                anchorY1 = fromD1;
-                anchorY2 = fromD1;
-                break;
-        }
-        // Style the line.
-        if (lineStyle != null) {
-            return new XYLineAnnotation(
-                anchorX1, anchorY1,
-                anchorX2, anchorY2,
-                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
-        }
-        else {
-            return new XYLineAnnotation(
-                anchorX1, anchorY1,
-                anchorX2, anchorY2);
-        }
-    }
-
-    /**
-     * Add the annotations (Sticky, Text and hyk zones) stored
-     * in the annotations field.
-     * @param plot Plot to add annotations to.
-     */
-    protected void addAnnotationsToRenderer(XYPlot plot) {
-        logger.debug("addAnnotationsToRenderer");
-
-        if (annotations == null || annotations.isEmpty()) {
-            logger.debug("addAnnotationsToRenderer: no annotations.");
-            return;
-        }
-
-        // OPTMIMIZE: Pre-calculate positions
-        ChartArea area = new ChartArea(
-            plot.getDomainAxis(0).getRange(),
-            plot.getRangeAxis().getRange());
-
-        // Walk over all Annotation sets.
-        for (RiverAnnotation fa: annotations) {
-
-            // Access text styling, if any.
-            ThemeDocument theme = fa.getTheme();
-            TextStyle textStyle = null;
-            LineStyle lineStyle = null;
-
-            // Get Themeing information and add legend item.
-            if (theme != null) {
-                textStyle = theme.parseComplexTextStyle();
-                lineStyle = theme.parseComplexLineStyle();
-                if (fa.getLabel() != null) {
-                    LegendItemCollection lic = new LegendItemCollection();
-                    LegendItemCollection old = plot.getFixedLegendItems();
-                    lic.add(createLegendItem(theme, fa.getLabel()));
-                    // (Re-)Add prior legend entries.
-                    if (old != null) {
-                        old.addAll(lic);
-                    }
-                    else {
-                        old = lic;
-                    }
-                    plot.setFixedLegendItems(old);
-                }
-            }
-
-            // The 'Sticky' Annotations (at axis, with line and text).
-            for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) {
-                addStickyAnnotation(
-                    sta, plot, area, lineStyle, textStyle, theme);
-            }
-
-            // Other Text Annotations (e.g. labels of (manual) points).
-            for (XYTextAnnotation ta: fa.getTextAnnotations()) {
-                // Style the text.
-                if (textStyle != null) {
-                    textStyle.apply(ta);
-                }
-                ta.setY(area.above(0.05d, ta.getY()));
-                plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
-            }
-        }
-    }
-
-
-    /**
      * This method needs to be implemented by concrete subclasses to create new
      * instances of JFreeChart.
      *
@@ -585,40 +266,6 @@
         }
     }
 
-
-    /**
-     * Register annotations like MainValues for later plotting
-     *
-     * @param annotations list of annotations (data of facet).
-     * @param aandf   Artifact and the facet.
-     * @param theme   Theme document for given annotations.
-     * @param visible The visibility of the annotations.
-     */
-    public void doAnnotations(
-        RiverAnnotation annotations,
-        ArtifactAndFacet aandf,
-        ThemeDocument theme,
-        boolean visible
-    ){
-        logger.debug("doAnnotations");
-
-        // Add all annotations to our annotation pool.
-        annotations.setTheme(theme);
-        if (aandf != null) {
-            annotations.setLabel(aandf.getFacetDescription());
-        }
-        else {
-            logger.error(
-                "Art/Facet for Annotations is null. " +
-                "This should never happen!");
-        }
-
-        if (visible) {
-            addAnnotations(annotations);
-        }
-    }
-
-
     /**
      * Generate chart.
      */
--- a/artifacts/src/main/java/org/dive4elements/river/exports/LongitudinalSectionGenerator.java	Wed Sep 18 16:26:12 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/LongitudinalSectionGenerator.java	Wed Sep 18 17:12:13 2013 +0200
@@ -23,6 +23,7 @@
 import org.dive4elements.river.exports.process.BedheightProcessor;
 import org.dive4elements.river.exports.process.QOutProcessor;
 import org.dive4elements.river.exports.process.WOutProcessor;
+import org.dive4elements.river.exports.process.AnnotationProcessor;
 
 import org.dive4elements.river.jfree.RiverAnnotation;
 import org.dive4elements.river.jfree.StyledAreaSeriesCollection;
@@ -340,11 +341,12 @@
             return;
         }
 
-        WOutProcessor wProcessor = new WOutProcessor();
-        QOutProcessor qProcessor = new QOutProcessor();
+        Processor wProcessor = new WOutProcessor();
+        Processor qProcessor = new QOutProcessor();
         Processor bedp = new BedheightProcessor();
         Processor bdyProcessor = new BedDiffYearProcessor();
         Processor bdhyProcessor = new BedDiffHeightYearProcessor();
+        Processor annotationProcessor = new AnnotationProcessor();
 
         if (wProcessor.canHandle(name)) {
             wProcessor.doOut(this, artifactAndFacet, attr, visible, YAXIS.W.idx);
@@ -361,12 +363,8 @@
         else if (bdhyProcessor.canHandle(name)) {
            bdhyProcessor.doOut(this, artifactAndFacet, attr, visible, YAXIS.W.idx);
         }
-        else if (name.equals(LONGITUDINAL_ANNOTATION)) {
-            doAnnotations(
-                (RiverAnnotation) artifactAndFacet.getData(context),
-                 artifactAndFacet,
-                 attr,
-                 visible);
+        else if (annotationProcessor.canHandle(name)) {
+            annotationProcessor.doOut(this, artifactAndFacet, attr, visible, 0);
         }
         else if (name.equals(W_DIFFERENCES)) {
             doWDifferencesOut(
--- a/artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java	Wed Sep 18 16:26:12 2013 +0200
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java	Wed Sep 18 17:12:13 2013 +0200
@@ -47,6 +47,7 @@
 import org.dive4elements.river.jfree.StyledAreaSeriesCollection;
 import org.dive4elements.river.jfree.StyledXYSeries;
 import org.dive4elements.river.jfree.AxisDataset;
+import org.dive4elements.river.jfree.AnnotationHelper;
 import org.dive4elements.river.themes.ThemeDocument;
 
 
@@ -150,7 +151,8 @@
         //debugAxis(plot);
 
         // These have to go after the autozoom.
-        addAnnotationsToRenderer(plot);
+        AnnotationHelper.addAnnotationsToRenderer(annotations, plot,
+                getChartSettings(), datasets);
 
         // Add a logo (maybe).
         addLogo(plot);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/jfree/AnnotationHelper.java	Wed Sep 18 17:12:13 2013 +0200
@@ -0,0 +1,366 @@
+/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU AGPL (>=v3)
+ * and comes with ABSOLUTELY NO WARRANTY! Check out the
+ * documentation coming with Dive4Elements River for details.
+ */
+package org.dive4elements.river.jfree;
+
+import org.dive4elements.river.artifacts.model.HYKFactory;
+import org.dive4elements.river.themes.ThemeDocument;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+
+import org.jfree.ui.TextAnchor;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.LegendItem;
+import org.jfree.chart.LegendItemCollection;
+import org.jfree.chart.annotations.XYTextAnnotation;
+import org.jfree.chart.annotations.XYLineAnnotation;
+
+import org.dive4elements.river.themes.LineStyle;
+import org.dive4elements.river.themes.TextStyle;
+import org.dive4elements.river.exports.ChartSettings;
+import org.dive4elements.river.exports.LegendSection;
+import org.dive4elements.river.exports.ChartArea;
+
+import org.apache.log4j.Logger;
+
+/** Annotation helper class, handles plotting of annotations. */
+public class AnnotationHelper {
+    private static final Logger logger = Logger.getLogger(AnnotationHelper.class);
+
+    protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
+
+    /* arr this would be better in chartsettings */
+    public static final int    DEFAULT_FONT_SIZE       = 12;
+    public static final String DEFAULT_FONT_NAME       = "Tahoma";
+
+    /**
+     * Add annotations (Sticky, Text and hyk zones) to a plot.
+     * @param annotations Annotations to add
+     * @param plot Plot to add annotations to.
+     * @param settings ChartSettings object for settings.
+     */
+    public static void addAnnotationsToRenderer(List<RiverAnnotation> annotations,
+            XYPlot plot, ChartSettings settings, Map<Integer, AxisDataset> datasets) {
+        logger.debug("addAnnotationsToRenderer");
+
+        if (annotations == null || annotations.isEmpty()) {
+            logger.debug("addAnnotationsToRenderer: no annotations.");
+            return;
+        }
+
+        // OPTMIMIZE: Pre-calculate positions
+        ChartArea area = new ChartArea(
+            plot.getDomainAxis(0).getRange(),
+            plot.getRangeAxis().getRange());
+
+        // Walk over all Annotation sets.
+        for (RiverAnnotation fa: annotations) {
+
+            // Access text styling, if any.
+            ThemeDocument theme = fa.getTheme();
+            TextStyle textStyle = null;
+            LineStyle lineStyle = null;
+
+            // Get Themeing information and add legend item.
+            if (theme != null) {
+                textStyle = theme.parseComplexTextStyle();
+                lineStyle = theme.parseComplexLineStyle();
+                if (fa.getLabel() != null) {
+                    // Legend handling, maybe misplaced?
+                    LegendItemCollection lic = new LegendItemCollection();
+                    LegendItemCollection old = plot.getFixedLegendItems();
+
+                    Color color = theme.parseLineColorField();
+                    if (color == null) {
+                        color = Color.BLACK;
+                    }
+
+                    LegendItem newItem = new LegendItem(fa.getLabel(), color);
+                    LegendSection ls = settings.getLegendSection();
+                    newItem.setLabelFont (new Font(
+                        DEFAULT_FONT_NAME,
+                        Font.PLAIN,
+                        ls != null ? ls.getFontSize() : null)
+                    );
+
+                    lic.add(newItem);
+                    // (Re-)Add prior legend entries.
+                    if (old != null) {
+                        old.addAll(lic);
+                    }
+                    else {
+                        old = lic;
+                    }
+                    plot.setFixedLegendItems(old);
+                }
+            }
+
+            // The 'Sticky' Annotations (at axis, with line and text).
+            for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) {
+                addStickyAnnotation(
+                    sta, plot, area, lineStyle, textStyle, theme,
+                    datasets.get(new Integer(sta.getAxisSymbol())));
+            }
+
+            // Other Text Annotations (e.g. labels of (manual) points).
+            for (XYTextAnnotation ta: fa.getTextAnnotations()) {
+                // Style the text.
+                if (textStyle != null) {
+                    textStyle.apply(ta);
+                }
+                ta.setY(area.above(0.05d, ta.getY()));
+                plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
+            }
+        }
+    }
+
+    /**
+     * Add a text and a line annotation.
+     * @param area convenience to determine positions in plot.
+     * @param theme (optional) theme document
+     */
+    public static void addStickyAnnotation(
+        StickyAxisAnnotation annotation,
+        XYPlot plot,
+        ChartArea area,
+        LineStyle lineStyle,
+        TextStyle textStyle,
+        ThemeDocument theme,
+        AxisDataset dataset
+    ) {
+        // OPTIMIZE pre-calculate area-related values
+        final float TEXT_OFF = 0.03f;
+
+        XYLineAnnotation lineAnnotation = null;
+        XYTextAnnotation textAnnotation = null;
+
+        int rendererIndex = 0;
+
+        if (annotation.atX()) {
+            textAnnotation = new CollisionFreeXYTextAnnotation(
+                annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
+            // OPTIMIZE externalize the calculation involving PI.
+            //textAnnotation.setRotationAngle(270f*Math.PI/180f);
+            lineAnnotation = createGroundStickAnnotation(
+                area, annotation.getPos(), lineStyle);
+            textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
+            textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
+        }
+        else {
+            // Do the more complicated case where we stick to the Y-Axis.
+            // There is one nasty case (duration curves, where annotations
+            // might stick to the second y-axis).
+            if (dataset == null) {
+                logger.warn("Annotation should stick to unfindable y-axis: "
+                    + annotation.getAxisSymbol());
+                rendererIndex = 0;
+            }
+            else {
+                rendererIndex = dataset.getPlotAxisIndex();
+            }
+
+            // Stick to the "right" (opposed to left) Y-Axis.
+            if (rendererIndex != 0) {
+                // OPTIMIZE: Pass a different area to this function,
+                //           do the adding to renderer outside (let this
+                //           function return the annotations).
+                //           Note that this path is travelled rarely.
+                ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex));
+                textAnnotation = new CollisionFreeXYTextAnnotation(
+                    annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos());
+                textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
+                textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
+                lineAnnotation = createRightStickAnnotation(
+                    area2, annotation.getPos(), lineStyle);
+                if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
+                    // New line annotation to hit curve.
+                    if (theme.parseShowVerticalLine()) {
+                        XYLineAnnotation hitLineAnnotation =
+                            createStickyLineAnnotation(
+                                StickyAxisAnnotation.SimpleAxis.X_AXIS,
+                                annotation.getHitPoint(), annotation.getPos(),// annotation.getHitPoint(),
+                                area2, lineStyle);
+                        plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
+                            org.jfree.ui.Layer.BACKGROUND);
+                    }
+                    if (theme.parseShowHorizontalLine()) {
+                        XYLineAnnotation lineBackAnnotation =
+                            createStickyLineAnnotation(
+                                StickyAxisAnnotation.SimpleAxis.Y_AXIS2,
+                                annotation.getPos(), annotation.getHitPoint(),
+                                area2, lineStyle);
+                        plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
+                            org.jfree.ui.Layer.BACKGROUND);
+                    }
+                }
+            }
+            else { // Stick to the left y-axis.
+                textAnnotation = new CollisionFreeXYTextAnnotation(
+                    annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
+                textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
+                textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
+                lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle);
+                if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
+                    // New line annotation to hit curve.
+                    if (theme.parseShowHorizontalLine()) {
+                        XYLineAnnotation hitLineAnnotation =
+                            createStickyLineAnnotation(
+                                StickyAxisAnnotation.SimpleAxis.Y_AXIS,
+                                annotation.getPos(), annotation.getHitPoint(),
+                                area, lineStyle);
+                        plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
+                            org.jfree.ui.Layer.BACKGROUND);
+                    }
+                    if (theme.parseShowVerticalLine()) {
+                        XYLineAnnotation lineBackAnnotation =
+                            createStickyLineAnnotation(
+                                StickyAxisAnnotation.SimpleAxis.X_AXIS,
+                                annotation.getHitPoint(), annotation.getPos(),
+                                area, lineStyle);
+                        plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
+                            org.jfree.ui.Layer.BACKGROUND);
+                    }
+                }
+            }
+        }
+
+        // Style the text.
+        if (textStyle != null) {
+            textStyle.apply(textAnnotation);
+        }
+
+        // Add the Annotations to renderer.
+        plot.getRenderer(rendererIndex).addAnnotation(textAnnotation,
+            org.jfree.ui.Layer.FOREGROUND);
+        plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation,
+            org.jfree.ui.Layer.FOREGROUND);
+    }
+
+   /**
+     * Create annotation that sticks to "ground" (X) axis.
+     * @param area helper to calculate coordinates
+     * @param pos one-dimensional position (distance from axis)
+     * @param lineStyle the line style to use for the line.
+     */
+    public static XYLineAnnotation createGroundStickAnnotation(
+        ChartArea area, float pos, LineStyle lineStyle
+    ) {
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                pos, area.atGround(),
+                pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET),
+                new BasicStroke(lineStyle.getWidth()),lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                pos, area.atGround(),
+                pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET));
+        }
+    }
+
+
+    /**
+     * Create annotation that sticks to the second Y axis ("right").
+     * @param area helper to calculate coordinates
+     * @param pos one-dimensional position (distance from axis)
+     * @param lineStyle the line style to use for the line.
+     */
+    public static XYLineAnnotation createRightStickAnnotation(
+        ChartArea area, float pos, LineStyle lineStyle
+    ) {
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos,
+                area.atRight(), pos,
+                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                area.atRight(), pos,
+                area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos);
+        }
+    }
+    /**
+     * Create annotation that sticks to the first Y axis ("left").
+     * @param area helper to calculate coordinates
+     * @param pos one-dimensional position (distance from axis)
+     * @param lineStyle the line style to use for the line.
+     */
+    public static XYLineAnnotation createLeftStickAnnotation(
+        ChartArea area, float pos, LineStyle lineStyle
+    ) {
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                area.atLeft(), pos,
+                area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos,
+                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                area.atLeft(), pos,
+                area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos);
+        }
+    }
+
+
+    /**
+     * Create a line from a axis to a given point.
+     * @param axis   The "simple" axis.
+     * @param fromD1 from-location in first dimension.
+     * @param toD2   to-location in second dimension.
+     * @param area   helper to calculate offsets.
+     * @param lineStyle optional line style.
+     */
+    public static XYLineAnnotation createStickyLineAnnotation(
+        StickyAxisAnnotation.SimpleAxis axis, float fromD1, float toD2,
+        ChartArea area, LineStyle lineStyle
+    ) {
+        double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d;
+        switch(axis) {
+            case X_AXIS:
+                anchorX1 = fromD1;
+                anchorX2 = fromD1;
+                anchorY1 = area.atGround();
+                anchorY2 = toD2;
+                break;
+            case Y_AXIS:
+                anchorX1 = area.atLeft();
+                anchorX2 = toD2;
+                anchorY1 = fromD1;
+                anchorY2 = fromD1;
+                break;
+            case Y_AXIS2:
+                anchorX1 = area.atRight();
+                anchorX2 = toD2;
+                anchorY1 = fromD1;
+                anchorY2 = fromD1;
+                break;
+        }
+        // Style the line.
+        if (lineStyle != null) {
+            return new XYLineAnnotation(
+                anchorX1, anchorY1,
+                anchorX2, anchorY2,
+                new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
+        }
+        else {
+            return new XYLineAnnotation(
+                anchorX1, anchorY1,
+                anchorX2, anchorY2);
+        }
+    }
+
+};

http://dive4elements.wald.intevation.org