Mercurial > dive4elements > river
diff artifacts/src/main/java/org/dive4elements/river/jfree/AnnotationHelper.java @ 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 | |
children | 35aabc86566d |
line wrap: on
line diff
--- /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); + } + } + +};