Mercurial > dive4elements > river
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