gernotbelger@9123: /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde gernotbelger@9123: * Software engineering by gernotbelger@9123: * Björnsen Beratende Ingenieure GmbH gernotbelger@9123: * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt gernotbelger@9123: * gernotbelger@9123: * This file is Free Software under the GNU AGPL (>=v3) gernotbelger@9123: * and comes with ABSOLUTELY NO WARRANTY! Check out the gernotbelger@9123: * documentation coming with Dive4Elements River for details. gernotbelger@9123: */ gernotbelger@9123: package org.dive4elements.river.exports; gernotbelger@9123: gernotbelger@9123: import java.awt.BasicStroke; gernotbelger@9123: import java.awt.Color; gernotbelger@9123: import java.util.ArrayList; gernotbelger@9123: import java.util.List; gernotbelger@9123: import java.util.Map; gernotbelger@9123: import java.util.SortedMap; gernotbelger@9123: gernotbelger@9123: import org.apache.log4j.Logger; gernotbelger@9123: import org.dive4elements.river.jfree.AxisDataset; gernotbelger@9123: import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation; gernotbelger@9123: import org.dive4elements.river.jfree.RiverAnnotation; gernotbelger@9123: import org.dive4elements.river.jfree.StickyAxisAnnotation; gernotbelger@9123: import org.dive4elements.river.themes.LineStyle; gernotbelger@9123: import org.dive4elements.river.themes.TextStyle; gernotbelger@9123: import org.dive4elements.river.themes.ThemeDocument; gernotbelger@9123: import org.jfree.chart.LegendItem; gernotbelger@9123: import org.jfree.chart.annotations.XYLineAnnotation; gernotbelger@9123: import org.jfree.chart.annotations.XYTextAnnotation; gernotbelger@9123: import org.jfree.chart.plot.XYPlot; gernotbelger@9123: import org.jfree.chart.renderer.xy.XYItemRenderer; gernotbelger@9123: import org.jfree.ui.TextAnchor; gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * @author Gernot Belger gernotbelger@9123: */ gernotbelger@9123: public final class AnnotationRenderer { gernotbelger@9123: gernotbelger@9123: private static final Logger log = Logger.getLogger(AnnotationRenderer.class); gernotbelger@9123: gernotbelger@9123: private static float ANNOTATIONS_AXIS_OFFSET = 0.02f; gernotbelger@9123: gernotbelger@9123: private final Map datasets; gernotbelger@9123: gernotbelger@9555: public AnnotationRenderer(final Map datasets) { gernotbelger@9123: this.datasets = datasets; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Add annotations (Sticky, Text and hyk zones) to a plot. gernotbelger@9123: * gernotbelger@9123: * @param annotations gernotbelger@9123: * Annotations to add gernotbelger@9123: * @param plot gernotbelger@9123: * XYPlot to add annotations to. gernotbelger@9123: * @param settings gernotbelger@9123: * ChartSettings object for settings. gernotbelger@9123: * @param datasets gernotbelger@9123: * Map of axis index and datasets gernotbelger@9123: */ gernotbelger@9555: public final void addAnnotationsToRenderer(final XYPlot plot, final LegendAggregator legendBuilder, final List annotations) { gernotbelger@9123: if (annotations == null || annotations.isEmpty()) { gernotbelger@9123: log.debug("addAnnotationsToRenderer: no annotations."); gernotbelger@9123: return; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: // OPTMIMIZE: Pre-calculate positions gernotbelger@9123: final ChartArea area = new ChartArea(plot.getDomainAxis(0), plot.getRangeAxis()); gernotbelger@9123: gernotbelger@9123: // Walk over all Annotation sets. gernotbelger@9123: for (final RiverAnnotation fa : annotations) { gernotbelger@9123: gernotbelger@9123: // Access text styling, if any. gernotbelger@9123: final ThemeDocument theme = fa.getTheme(); gernotbelger@9123: TextStyle textStyle = null; gernotbelger@9123: LineStyle lineStyle = null; gernotbelger@9123: gernotbelger@9123: // Get Theming information and add legend item. gernotbelger@9123: if (theme != null) { gernotbelger@9123: textStyle = theme.parseComplexTextStyle(); gernotbelger@9123: lineStyle = theme.parseComplexLineStyle(); gernotbelger@9123: if (fa.getLabel() != null) { gernotbelger@9123: Color color = theme.parseLineColorField(); gernotbelger@9123: if (color == null) { gernotbelger@9123: color = Color.BLACK; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: Color textColor = theme.parseTextColor(); gernotbelger@9123: if (textColor == null) { gernotbelger@9123: textColor = Color.BLACK; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: final LegendItem newItem = new LegendItem(fa.getLabel(), color); gernotbelger@9123: gernotbelger@9123: newItem.setLabelPaint(textColor); gernotbelger@9123: gernotbelger@9555: // REMARK: connection to facet is lost at this point gernotbelger@9555: final String themeType = null; gernotbelger@9555: gernotbelger@9555: legendBuilder.addLegendItem(themeType, newItem); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: // The 'Sticky' Annotations (at axis, with line and text). gernotbelger@9123: for (final StickyAxisAnnotation sta : fa.getAxisTextAnnotations()) { gernotbelger@9123: addStickyAnnotation(sta, plot, area, lineStyle, textStyle, theme, this.datasets.get(new Integer(sta.getAxisSymbol()))); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: // Other Text Annotations (e.g. labels of (manual) points). gernotbelger@9123: for (final XYTextAnnotation ta : fa.getTextAnnotations()) { gernotbelger@9123: // Style the text. gernotbelger@9123: if (textStyle != null) { gernotbelger@9123: textStyle.apply(ta); gernotbelger@9123: } gernotbelger@9123: ta.setY(area.above(0.05d, ta.getY())); gernotbelger@9123: plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Add a text and a line annotation. gernotbelger@9123: * gernotbelger@9123: * @param area gernotbelger@9123: * convenience to determine positions in plot. gernotbelger@9123: * @param theme gernotbelger@9123: * (optional) theme document gernotbelger@9123: */ gernotbelger@9123: private void addStickyAnnotation(final StickyAxisAnnotation annotation, final XYPlot plot, final ChartArea area, final LineStyle lineStyle, gernotbelger@9123: final TextStyle textStyle, final ThemeDocument theme, final AxisDataset dataset) { gernotbelger@9123: // OPTIMIZE pre-calculate area-related values gernotbelger@9123: final float TEXT_OFF = 0.03f; gernotbelger@9123: gernotbelger@9123: XYLineAnnotation lineAnnotation = null; gernotbelger@9123: XYTextAnnotation textAnnotation = null; gernotbelger@9123: gernotbelger@9123: final int axisIndex = annotation.getAxisSymbol(); gernotbelger@9123: XYItemRenderer renderer = null; gernotbelger@9123: if (dataset != null && dataset.getDatasets().length > 0) { gernotbelger@9123: renderer = plot.getRendererForDataset(dataset.getDatasets()[0]); gernotbelger@9123: } else { gernotbelger@9123: renderer = plot.getRenderer(); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: if (annotation.atX()) { gernotbelger@9123: textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF)); gernotbelger@9123: // OPTIMIZE externalize the calculation involving PI. gernotbelger@9123: // textAnnotation.setRotationAngle(270f*Math.PI/180f); gernotbelger@9123: lineAnnotation = createGroundStickAnnotation(area, annotation.getPos(), lineStyle); gernotbelger@9123: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); gernotbelger@9123: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); gernotbelger@9123: } else { gernotbelger@9123: // Stick to the "right" (opposed to left) Y-Axis. gernotbelger@9123: if (axisIndex != 0 && plot.getRangeAxis(axisIndex) != null) { gernotbelger@9123: // OPTIMIZE: Pass a different area to this function, gernotbelger@9123: // do the adding to renderer outside (let this gernotbelger@9123: // function return the annotations). gernotbelger@9123: // Note that this path is travelled rarely. gernotbelger@9123: textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), area.ofRight(TEXT_OFF), annotation.getPos()); gernotbelger@9123: textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT); gernotbelger@9123: textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT); gernotbelger@9123: lineAnnotation = createRightStickAnnotation(area, annotation.getPos(), lineStyle); gernotbelger@9123: gernotbelger@9123: // hit-lines for duration curve gernotbelger@9123: final ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(axisIndex)); gernotbelger@9123: if (!Float.isNaN(annotation.getHitPoint()) && theme != null) { gernotbelger@9123: // New line annotation to hit curve. gernotbelger@9123: if (theme.parseShowVerticalLine()) { gernotbelger@9123: final XYLineAnnotation hitLineAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.X_AXIS, annotation.getHitPoint(), gernotbelger@9123: annotation.getPos(), gernotbelger@9123: // annotation.getHitPoint(), gernotbelger@9123: area2, lineStyle); gernotbelger@9123: renderer.addAnnotation(hitLineAnnotation, org.jfree.ui.Layer.BACKGROUND); gernotbelger@9123: } gernotbelger@9123: if (theme.parseShowHorizontalLine()) { gernotbelger@9123: final XYLineAnnotation lineBackAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.Y_AXIS2, annotation.getPos(), gernotbelger@9123: annotation.getHitPoint(), area2, lineStyle); gernotbelger@9123: renderer.addAnnotation(lineBackAnnotation, org.jfree.ui.Layer.BACKGROUND); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: } else { // Stick to the left y-axis. gernotbelger@9123: textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos()); gernotbelger@9123: textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); gernotbelger@9123: textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); gernotbelger@9123: lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle); gernotbelger@9123: if (!Float.isNaN(annotation.getHitPoint()) && theme != null) { gernotbelger@9123: // New line annotation to hit curve. gernotbelger@9123: if (theme.parseShowHorizontalLine()) { gernotbelger@9123: final XYLineAnnotation hitLineAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.Y_AXIS, annotation.getPos(), gernotbelger@9123: annotation.getHitPoint(), area, lineStyle); gernotbelger@9123: renderer.addAnnotation(hitLineAnnotation, org.jfree.ui.Layer.BACKGROUND); gernotbelger@9123: } gernotbelger@9123: if (theme.parseShowVerticalLine()) { gernotbelger@9123: final XYLineAnnotation lineBackAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.X_AXIS, annotation.getHitPoint(), gernotbelger@9123: annotation.getPos(), area, lineStyle); gernotbelger@9123: renderer.addAnnotation(lineBackAnnotation, org.jfree.ui.Layer.BACKGROUND); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: // Style the text. gernotbelger@9123: if (textStyle != null) { gernotbelger@9123: textStyle.apply(textAnnotation); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: // Add the Annotations to renderer. gernotbelger@9123: renderer.addAnnotation(textAnnotation, org.jfree.ui.Layer.FOREGROUND); gernotbelger@9123: renderer.addAnnotation(lineAnnotation, org.jfree.ui.Layer.FOREGROUND); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9555: public final void addYAnnotationsToRenderer(final XYPlot plot, final LegendAggregator legendBuilder, gernotbelger@9555: final SortedMap yAnnotations) { gernotbelger@9123: final List annotations = new ArrayList<>(); gernotbelger@9123: gernotbelger@9123: for (final Map.Entry entry : yAnnotations.entrySet()) { gernotbelger@9123: final int axis = entry.getKey(); gernotbelger@9123: final AxisDataset dataset = this.datasets.get(new Integer(axis)); gernotbelger@9123: gernotbelger@9123: if (dataset == null || dataset.getRange() == null) { gernotbelger@9123: log.warn("No dataset available and active for axis " + axis); gernotbelger@9123: } else { gernotbelger@9123: final RiverAnnotation ya = entry.getValue(); gernotbelger@9123: for (final StickyAxisAnnotation sta : ya.getAxisTextAnnotations()) { gernotbelger@9123: sta.setAxisSymbol(axis); gernotbelger@9123: } gernotbelger@9123: annotations.add(ya); gernotbelger@9123: } gernotbelger@9123: } gernotbelger@9123: gernotbelger@9555: addAnnotationsToRenderer(plot, legendBuilder, annotations); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Create annotation that sticks to "ground" (X) axis. gernotbelger@9123: * gernotbelger@9123: * @param area gernotbelger@9123: * helper to calculate coordinates gernotbelger@9123: * @param pos gernotbelger@9123: * one-dimensional position (distance from axis) gernotbelger@9123: * @param lineStyle gernotbelger@9123: * the line style to use for the line. gernotbelger@9123: */ gernotbelger@9123: private XYLineAnnotation createGroundStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) { gernotbelger@9123: if (lineStyle != null) gernotbelger@9123: return new XYLineAnnotation(pos, area.atGround(), pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET), new BasicStroke(lineStyle.getWidth()), gernotbelger@9123: lineStyle.getColor()); gernotbelger@9123: gernotbelger@9123: return new XYLineAnnotation(pos, area.atGround(), pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET)); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Create annotation that sticks to the second Y axis ("right"). gernotbelger@9123: * gernotbelger@9123: * @param area gernotbelger@9123: * helper to calculate coordinates gernotbelger@9123: * @param pos gernotbelger@9123: * one-dimensional position (distance from axis) gernotbelger@9123: * @param lineStyle gernotbelger@9123: * the line style to use for the line. gernotbelger@9123: */ gernotbelger@9123: private XYLineAnnotation createRightStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) { gernotbelger@9123: if (lineStyle != null) gernotbelger@9123: return new XYLineAnnotation(area.atRight(), pos, area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos, new BasicStroke(lineStyle.getWidth()), gernotbelger@9123: lineStyle.getColor()); gernotbelger@9123: gernotbelger@9123: return new XYLineAnnotation(area.atRight(), pos, area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Create annotation that sticks to the first Y axis ("left"). gernotbelger@9123: * gernotbelger@9123: * @param area gernotbelger@9123: * helper to calculate coordinates gernotbelger@9123: * @param pos gernotbelger@9123: * one-dimensional position (distance from axis) gernotbelger@9123: * @param lineStyle gernotbelger@9123: * the line style to use for the line. gernotbelger@9123: */ gernotbelger@9123: private XYLineAnnotation createLeftStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) { gernotbelger@9123: if (lineStyle != null) gernotbelger@9123: return new XYLineAnnotation(area.atLeft(), pos, area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos, new BasicStroke(lineStyle.getWidth()), gernotbelger@9123: lineStyle.getColor()); gernotbelger@9123: gernotbelger@9123: return new XYLineAnnotation(area.atLeft(), pos, area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos); gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: /** gernotbelger@9123: * Create a line from a axis to a given point. gernotbelger@9123: * gernotbelger@9123: * @param axis gernotbelger@9123: * The "simple" axis. gernotbelger@9123: * @param fromD1 gernotbelger@9123: * from-location in first dimension. gernotbelger@9123: * @param toD2 gernotbelger@9123: * to-location in second dimension. gernotbelger@9123: * @param area gernotbelger@9123: * helper to calculate offsets. gernotbelger@9123: * @param lineStyle gernotbelger@9123: * optional line style. gernotbelger@9123: */ gernotbelger@9123: public static XYLineAnnotation createStickyLineAnnotation(final StickyAxisAnnotation.SimpleAxis axis, final float fromD1, final float toD2, gernotbelger@9123: final ChartArea area, final LineStyle lineStyle) { gernotbelger@9123: double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d; gernotbelger@9123: switch (axis) { gernotbelger@9123: case X_AXIS: gernotbelger@9123: anchorX1 = fromD1; gernotbelger@9123: anchorX2 = fromD1; gernotbelger@9123: anchorY1 = area.atGround(); gernotbelger@9123: anchorY2 = toD2; gernotbelger@9123: break; gernotbelger@9123: case Y_AXIS: gernotbelger@9123: anchorX1 = area.atLeft(); gernotbelger@9123: anchorX2 = toD2; gernotbelger@9123: anchorY1 = fromD1; gernotbelger@9123: anchorY2 = fromD1; gernotbelger@9123: break; gernotbelger@9123: case Y_AXIS2: gernotbelger@9123: anchorX1 = area.atRight(); gernotbelger@9123: anchorX2 = toD2; gernotbelger@9123: anchorY1 = fromD1; gernotbelger@9123: anchorY2 = fromD1; gernotbelger@9123: break; gernotbelger@9123: } gernotbelger@9123: gernotbelger@9123: if (lineStyle != null) gernotbelger@9123: return new XYLineAnnotation(anchorX1, anchorY1, anchorX2, anchorY2, new BasicStroke(lineStyle.getWidth()), lineStyle.getColor()); gernotbelger@9123: gernotbelger@9123: return new XYLineAnnotation(anchorX1, anchorY1, anchorX2, anchorY2); gernotbelger@9123: } gernotbelger@9123: }