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