diff artifacts/src/main/java/org/dive4elements/river/exports/AnnotationRenderer.java @ 9123:1cc7653ca84f

Cleanup of ChartGenerator and ChartGenerator2 code. Put some of the copy/pasted code into a common abstraction.
author gernotbelger
date Tue, 05 Jun 2018 19:21:16 +0200
parents
children ef5754ba5573
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifacts/src/main/java/org/dive4elements/river/exports/AnnotationRenderer.java	Tue Jun 05 19:21:16 2018 +0200
@@ -0,0 +1,352 @@
+/** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
+ * Software engineering by
+ *  Björnsen Beratende Ingenieure GmbH
+ *  Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
+ *
+ * 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.exports;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+
+import org.apache.log4j.Logger;
+import org.dive4elements.river.jfree.AxisDataset;
+import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
+import org.dive4elements.river.jfree.RiverAnnotation;
+import org.dive4elements.river.jfree.StickyAxisAnnotation;
+import org.dive4elements.river.themes.LineStyle;
+import org.dive4elements.river.themes.TextStyle;
+import org.dive4elements.river.themes.ThemeDocument;
+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.plot.XYPlot;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.ui.TextAnchor;
+
+/**
+ * @author Gernot Belger
+ */
+public final class AnnotationRenderer {
+
+    private static final Logger log = Logger.getLogger(AnnotationRenderer.class);
+
+    private static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
+
+    private final ChartSettings settings;
+
+    private final Map<Integer, AxisDataset> datasets;
+
+    private final String fontName;
+
+    public AnnotationRenderer(final ChartSettings settings, final Map<Integer, AxisDataset> datasets, final String fontName) {
+        this.settings = settings;
+        this.datasets = datasets;
+        this.fontName = fontName;
+    }
+
+    /**
+     * Add annotations (Sticky, Text and hyk zones) to a plot.
+     *
+     * @param annotations
+     *            Annotations to add
+     * @param plot
+     *            XYPlot to add annotations to.
+     * @param settings
+     *            ChartSettings object for settings.
+     * @param datasets
+     *            Map of axis index and datasets
+     */
+    public final void addAnnotationsToRenderer(final XYPlot plot, final List<RiverAnnotation> annotations) {
+        if (annotations == null || annotations.isEmpty()) {
+            log.debug("addAnnotationsToRenderer: no annotations.");
+            return;
+        }
+
+        // OPTMIMIZE: Pre-calculate positions
+        final ChartArea area = new ChartArea(plot.getDomainAxis(0), plot.getRangeAxis());
+
+        // Walk over all Annotation sets.
+        for (final RiverAnnotation fa : annotations) {
+
+            // Access text styling, if any.
+            final ThemeDocument theme = fa.getTheme();
+            TextStyle textStyle = null;
+            LineStyle lineStyle = null;
+
+            // Get Theming information and add legend item.
+            if (theme != null) {
+                textStyle = theme.parseComplexTextStyle();
+                lineStyle = theme.parseComplexLineStyle();
+                if (fa.getLabel() != null) {
+                    // Legend handling, maybe misplaced?
+                    final LegendItemCollection lic = new LegendItemCollection();
+                    LegendItemCollection old = plot.getFixedLegendItems();
+
+                    Color color = theme.parseLineColorField();
+                    if (color == null) {
+                        color = Color.BLACK;
+                    }
+
+                    Color textColor = theme.parseTextColor();
+                    if (textColor == null) {
+                        textColor = Color.BLACK;
+                    }
+
+                    final LegendItem newItem = new LegendItem(fa.getLabel(), color);
+
+                    final LegendSection ls = this.settings != null ? this.settings.getLegendSection() : null;
+
+                    final Integer size = ls != null ? ls.getFontSize() : null;
+
+                    newItem.setLabelFont(new Font(this.fontName, Font.PLAIN, size));
+
+                    newItem.setLabelPaint(textColor);
+
+                    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 (final StickyAxisAnnotation sta : fa.getAxisTextAnnotations()) {
+                addStickyAnnotation(sta, plot, area, lineStyle, textStyle, theme, this.datasets.get(new Integer(sta.getAxisSymbol())));
+            }
+
+            // Other Text Annotations (e.g. labels of (manual) points).
+            for (final 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
+     */
+    private void addStickyAnnotation(final StickyAxisAnnotation annotation, final XYPlot plot, final ChartArea area, final LineStyle lineStyle,
+            final TextStyle textStyle, final ThemeDocument theme, final AxisDataset dataset) {
+        // OPTIMIZE pre-calculate area-related values
+        final float TEXT_OFF = 0.03f;
+
+        XYLineAnnotation lineAnnotation = null;
+        XYTextAnnotation textAnnotation = null;
+
+        final int axisIndex = annotation.getAxisSymbol();
+        XYItemRenderer renderer = null;
+        if (dataset != null && dataset.getDatasets().length > 0) {
+            renderer = plot.getRendererForDataset(dataset.getDatasets()[0]);
+        } else {
+            renderer = plot.getRenderer();
+        }
+
+        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 {
+            // Stick to the "right" (opposed to left) Y-Axis.
+            if (axisIndex != 0 && plot.getRangeAxis(axisIndex) != null) {
+                // 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.
+                textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), area.ofRight(TEXT_OFF), annotation.getPos());
+                textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
+                textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
+                lineAnnotation = createRightStickAnnotation(area, annotation.getPos(), lineStyle);
+
+                // hit-lines for duration curve
+                final ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(axisIndex));
+                if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
+                    // New line annotation to hit curve.
+                    if (theme.parseShowVerticalLine()) {
+                        final XYLineAnnotation hitLineAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.X_AXIS, annotation.getHitPoint(),
+                                annotation.getPos(),
+                                // annotation.getHitPoint(),
+                                area2, lineStyle);
+                        renderer.addAnnotation(hitLineAnnotation, org.jfree.ui.Layer.BACKGROUND);
+                    }
+                    if (theme.parseShowHorizontalLine()) {
+                        final XYLineAnnotation lineBackAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.Y_AXIS2, annotation.getPos(),
+                                annotation.getHitPoint(), area2, lineStyle);
+                        renderer.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()) {
+                        final XYLineAnnotation hitLineAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.Y_AXIS, annotation.getPos(),
+                                annotation.getHitPoint(), area, lineStyle);
+                        renderer.addAnnotation(hitLineAnnotation, org.jfree.ui.Layer.BACKGROUND);
+                    }
+                    if (theme.parseShowVerticalLine()) {
+                        final XYLineAnnotation lineBackAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.X_AXIS, annotation.getHitPoint(),
+                                annotation.getPos(), area, lineStyle);
+                        renderer.addAnnotation(lineBackAnnotation, org.jfree.ui.Layer.BACKGROUND);
+                    }
+                }
+            }
+        }
+
+        // Style the text.
+        if (textStyle != null) {
+            textStyle.apply(textAnnotation);
+        }
+
+        // Add the Annotations to renderer.
+        renderer.addAnnotation(textAnnotation, org.jfree.ui.Layer.FOREGROUND);
+        renderer.addAnnotation(lineAnnotation, org.jfree.ui.Layer.FOREGROUND);
+    }
+
+    public final void addYAnnotationsToRenderer(final XYPlot plot, final SortedMap<Integer, RiverAnnotation> yAnnotations) {
+        final List<RiverAnnotation> annotations = new ArrayList<>();
+
+        for (final Map.Entry<Integer, RiverAnnotation> entry : yAnnotations.entrySet()) {
+            final int axis = entry.getKey();
+            final AxisDataset dataset = this.datasets.get(new Integer(axis));
+
+            if (dataset == null || dataset.getRange() == null) {
+                log.warn("No dataset available and active for axis " + axis);
+            } else {
+                final RiverAnnotation ya = entry.getValue();
+                for (final StickyAxisAnnotation sta : ya.getAxisTextAnnotations()) {
+                    sta.setAxisSymbol(axis);
+                }
+                annotations.add(ya);
+            }
+        }
+
+        addAnnotationsToRenderer(plot, annotations);
+    }
+
+    /**
+     * 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.
+     */
+    private XYLineAnnotation createGroundStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) {
+        if (lineStyle != null)
+            return new XYLineAnnotation(pos, area.atGround(), pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET), new BasicStroke(lineStyle.getWidth()),
+                    lineStyle.getColor());
+
+        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.
+     */
+    private XYLineAnnotation createRightStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) {
+        if (lineStyle != null)
+            return new XYLineAnnotation(area.atRight(), pos, area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos, new BasicStroke(lineStyle.getWidth()),
+                    lineStyle.getColor());
+
+        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.
+     */
+    private XYLineAnnotation createLeftStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) {
+        if (lineStyle != null)
+            return new XYLineAnnotation(area.atLeft(), pos, area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos, new BasicStroke(lineStyle.getWidth()),
+                    lineStyle.getColor());
+
+        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(final StickyAxisAnnotation.SimpleAxis axis, final float fromD1, final float toD2,
+            final ChartArea area, final 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;
+        }
+
+        if (lineStyle != null)
+            return new XYLineAnnotation(anchorX1, anchorY1, anchorX2, anchorY2, new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
+
+        return new XYLineAnnotation(anchorX1, anchorY1, anchorX2, anchorY2);
+    }
+}
\ No newline at end of file

http://dive4elements.wald.intevation.org