comparison 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
comparison
equal deleted inserted replaced
9122:b8e7f6becf78 9123:1cc7653ca84f
1 /** Copyright (C) 2017 by Bundesanstalt für Gewässerkunde
2 * Software engineering by
3 * Björnsen Beratende Ingenieure GmbH
4 * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt
5 *
6 * This file is Free Software under the GNU AGPL (>=v3)
7 * and comes with ABSOLUTELY NO WARRANTY! Check out the
8 * documentation coming with Dive4Elements River for details.
9 */
10 package org.dive4elements.river.exports;
11
12 import java.awt.BasicStroke;
13 import java.awt.Color;
14 import java.awt.Font;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.SortedMap;
19
20 import org.apache.log4j.Logger;
21 import org.dive4elements.river.jfree.AxisDataset;
22 import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
23 import org.dive4elements.river.jfree.RiverAnnotation;
24 import org.dive4elements.river.jfree.StickyAxisAnnotation;
25 import org.dive4elements.river.themes.LineStyle;
26 import org.dive4elements.river.themes.TextStyle;
27 import org.dive4elements.river.themes.ThemeDocument;
28 import org.jfree.chart.LegendItem;
29 import org.jfree.chart.LegendItemCollection;
30 import org.jfree.chart.annotations.XYLineAnnotation;
31 import org.jfree.chart.annotations.XYTextAnnotation;
32 import org.jfree.chart.plot.XYPlot;
33 import org.jfree.chart.renderer.xy.XYItemRenderer;
34 import org.jfree.ui.TextAnchor;
35
36 /**
37 * @author Gernot Belger
38 */
39 public final class AnnotationRenderer {
40
41 private static final Logger log = Logger.getLogger(AnnotationRenderer.class);
42
43 private static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
44
45 private final ChartSettings settings;
46
47 private final Map<Integer, AxisDataset> datasets;
48
49 private final String fontName;
50
51 public AnnotationRenderer(final ChartSettings settings, final Map<Integer, AxisDataset> datasets, final String fontName) {
52 this.settings = settings;
53 this.datasets = datasets;
54 this.fontName = fontName;
55 }
56
57 /**
58 * Add annotations (Sticky, Text and hyk zones) to a plot.
59 *
60 * @param annotations
61 * Annotations to add
62 * @param plot
63 * XYPlot to add annotations to.
64 * @param settings
65 * ChartSettings object for settings.
66 * @param datasets
67 * Map of axis index and datasets
68 */
69 public final void addAnnotationsToRenderer(final XYPlot plot, final List<RiverAnnotation> annotations) {
70 if (annotations == null || annotations.isEmpty()) {
71 log.debug("addAnnotationsToRenderer: no annotations.");
72 return;
73 }
74
75 // OPTMIMIZE: Pre-calculate positions
76 final ChartArea area = new ChartArea(plot.getDomainAxis(0), plot.getRangeAxis());
77
78 // Walk over all Annotation sets.
79 for (final RiverAnnotation fa : annotations) {
80
81 // Access text styling, if any.
82 final ThemeDocument theme = fa.getTheme();
83 TextStyle textStyle = null;
84 LineStyle lineStyle = null;
85
86 // Get Theming information and add legend item.
87 if (theme != null) {
88 textStyle = theme.parseComplexTextStyle();
89 lineStyle = theme.parseComplexLineStyle();
90 if (fa.getLabel() != null) {
91 // Legend handling, maybe misplaced?
92 final LegendItemCollection lic = new LegendItemCollection();
93 LegendItemCollection old = plot.getFixedLegendItems();
94
95 Color color = theme.parseLineColorField();
96 if (color == null) {
97 color = Color.BLACK;
98 }
99
100 Color textColor = theme.parseTextColor();
101 if (textColor == null) {
102 textColor = Color.BLACK;
103 }
104
105 final LegendItem newItem = new LegendItem(fa.getLabel(), color);
106
107 final LegendSection ls = this.settings != null ? this.settings.getLegendSection() : null;
108
109 final Integer size = ls != null ? ls.getFontSize() : null;
110
111 newItem.setLabelFont(new Font(this.fontName, Font.PLAIN, size));
112
113 newItem.setLabelPaint(textColor);
114
115 lic.add(newItem);
116 // (Re-)Add prior legend entries.
117 if (old != null) {
118 old.addAll(lic);
119 } else {
120 old = lic;
121 }
122 plot.setFixedLegendItems(old);
123 }
124 }
125
126 // The 'Sticky' Annotations (at axis, with line and text).
127 for (final StickyAxisAnnotation sta : fa.getAxisTextAnnotations()) {
128 addStickyAnnotation(sta, plot, area, lineStyle, textStyle, theme, this.datasets.get(new Integer(sta.getAxisSymbol())));
129 }
130
131 // Other Text Annotations (e.g. labels of (manual) points).
132 for (final XYTextAnnotation ta : fa.getTextAnnotations()) {
133 // Style the text.
134 if (textStyle != null) {
135 textStyle.apply(ta);
136 }
137 ta.setY(area.above(0.05d, ta.getY()));
138 plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
139 }
140 }
141 }
142
143 /**
144 * Add a text and a line annotation.
145 *
146 * @param area
147 * convenience to determine positions in plot.
148 * @param theme
149 * (optional) theme document
150 */
151 private void addStickyAnnotation(final StickyAxisAnnotation annotation, final XYPlot plot, final ChartArea area, final LineStyle lineStyle,
152 final TextStyle textStyle, final ThemeDocument theme, final AxisDataset dataset) {
153 // OPTIMIZE pre-calculate area-related values
154 final float TEXT_OFF = 0.03f;
155
156 XYLineAnnotation lineAnnotation = null;
157 XYTextAnnotation textAnnotation = null;
158
159 final int axisIndex = annotation.getAxisSymbol();
160 XYItemRenderer renderer = null;
161 if (dataset != null && dataset.getDatasets().length > 0) {
162 renderer = plot.getRendererForDataset(dataset.getDatasets()[0]);
163 } else {
164 renderer = plot.getRenderer();
165 }
166
167 if (annotation.atX()) {
168 textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
169 // OPTIMIZE externalize the calculation involving PI.
170 // textAnnotation.setRotationAngle(270f*Math.PI/180f);
171 lineAnnotation = createGroundStickAnnotation(area, annotation.getPos(), lineStyle);
172 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
173 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
174 } else {
175 // Stick to the "right" (opposed to left) Y-Axis.
176 if (axisIndex != 0 && plot.getRangeAxis(axisIndex) != null) {
177 // OPTIMIZE: Pass a different area to this function,
178 // do the adding to renderer outside (let this
179 // function return the annotations).
180 // Note that this path is travelled rarely.
181 textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), area.ofRight(TEXT_OFF), annotation.getPos());
182 textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
183 textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
184 lineAnnotation = createRightStickAnnotation(area, annotation.getPos(), lineStyle);
185
186 // hit-lines for duration curve
187 final ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(axisIndex));
188 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
189 // New line annotation to hit curve.
190 if (theme.parseShowVerticalLine()) {
191 final XYLineAnnotation hitLineAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.X_AXIS, annotation.getHitPoint(),
192 annotation.getPos(),
193 // annotation.getHitPoint(),
194 area2, lineStyle);
195 renderer.addAnnotation(hitLineAnnotation, org.jfree.ui.Layer.BACKGROUND);
196 }
197 if (theme.parseShowHorizontalLine()) {
198 final XYLineAnnotation lineBackAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.Y_AXIS2, annotation.getPos(),
199 annotation.getHitPoint(), area2, lineStyle);
200 renderer.addAnnotation(lineBackAnnotation, org.jfree.ui.Layer.BACKGROUND);
201 }
202 }
203 } else { // Stick to the left y-axis.
204 textAnnotation = new CollisionFreeXYTextAnnotation(annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
205 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
206 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
207 lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle);
208 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
209 // New line annotation to hit curve.
210 if (theme.parseShowHorizontalLine()) {
211 final XYLineAnnotation hitLineAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.Y_AXIS, annotation.getPos(),
212 annotation.getHitPoint(), area, lineStyle);
213 renderer.addAnnotation(hitLineAnnotation, org.jfree.ui.Layer.BACKGROUND);
214 }
215 if (theme.parseShowVerticalLine()) {
216 final XYLineAnnotation lineBackAnnotation = createStickyLineAnnotation(StickyAxisAnnotation.SimpleAxis.X_AXIS, annotation.getHitPoint(),
217 annotation.getPos(), area, lineStyle);
218 renderer.addAnnotation(lineBackAnnotation, org.jfree.ui.Layer.BACKGROUND);
219 }
220 }
221 }
222 }
223
224 // Style the text.
225 if (textStyle != null) {
226 textStyle.apply(textAnnotation);
227 }
228
229 // Add the Annotations to renderer.
230 renderer.addAnnotation(textAnnotation, org.jfree.ui.Layer.FOREGROUND);
231 renderer.addAnnotation(lineAnnotation, org.jfree.ui.Layer.FOREGROUND);
232 }
233
234 public final void addYAnnotationsToRenderer(final XYPlot plot, final SortedMap<Integer, RiverAnnotation> yAnnotations) {
235 final List<RiverAnnotation> annotations = new ArrayList<>();
236
237 for (final Map.Entry<Integer, RiverAnnotation> entry : yAnnotations.entrySet()) {
238 final int axis = entry.getKey();
239 final AxisDataset dataset = this.datasets.get(new Integer(axis));
240
241 if (dataset == null || dataset.getRange() == null) {
242 log.warn("No dataset available and active for axis " + axis);
243 } else {
244 final RiverAnnotation ya = entry.getValue();
245 for (final StickyAxisAnnotation sta : ya.getAxisTextAnnotations()) {
246 sta.setAxisSymbol(axis);
247 }
248 annotations.add(ya);
249 }
250 }
251
252 addAnnotationsToRenderer(plot, annotations);
253 }
254
255 /**
256 * Create annotation that sticks to "ground" (X) axis.
257 *
258 * @param area
259 * helper to calculate coordinates
260 * @param pos
261 * one-dimensional position (distance from axis)
262 * @param lineStyle
263 * the line style to use for the line.
264 */
265 private XYLineAnnotation createGroundStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) {
266 if (lineStyle != null)
267 return new XYLineAnnotation(pos, area.atGround(), pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET), new BasicStroke(lineStyle.getWidth()),
268 lineStyle.getColor());
269
270 return new XYLineAnnotation(pos, area.atGround(), pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET));
271 }
272
273 /**
274 * Create annotation that sticks to the second Y axis ("right").
275 *
276 * @param area
277 * helper to calculate coordinates
278 * @param pos
279 * one-dimensional position (distance from axis)
280 * @param lineStyle
281 * the line style to use for the line.
282 */
283 private XYLineAnnotation createRightStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) {
284 if (lineStyle != null)
285 return new XYLineAnnotation(area.atRight(), pos, area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos, new BasicStroke(lineStyle.getWidth()),
286 lineStyle.getColor());
287
288 return new XYLineAnnotation(area.atRight(), pos, area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos);
289 }
290
291 /**
292 * Create annotation that sticks to the first Y axis ("left").
293 *
294 * @param area
295 * helper to calculate coordinates
296 * @param pos
297 * one-dimensional position (distance from axis)
298 * @param lineStyle
299 * the line style to use for the line.
300 */
301 private XYLineAnnotation createLeftStickAnnotation(final ChartArea area, final float pos, final LineStyle lineStyle) {
302 if (lineStyle != null)
303 return new XYLineAnnotation(area.atLeft(), pos, area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos, new BasicStroke(lineStyle.getWidth()),
304 lineStyle.getColor());
305
306 return new XYLineAnnotation(area.atLeft(), pos, area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos);
307 }
308
309 /**
310 * Create a line from a axis to a given point.
311 *
312 * @param axis
313 * The "simple" axis.
314 * @param fromD1
315 * from-location in first dimension.
316 * @param toD2
317 * to-location in second dimension.
318 * @param area
319 * helper to calculate offsets.
320 * @param lineStyle
321 * optional line style.
322 */
323 public static XYLineAnnotation createStickyLineAnnotation(final StickyAxisAnnotation.SimpleAxis axis, final float fromD1, final float toD2,
324 final ChartArea area, final LineStyle lineStyle) {
325 double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d;
326 switch (axis) {
327 case X_AXIS:
328 anchorX1 = fromD1;
329 anchorX2 = fromD1;
330 anchorY1 = area.atGround();
331 anchorY2 = toD2;
332 break;
333 case Y_AXIS:
334 anchorX1 = area.atLeft();
335 anchorX2 = toD2;
336 anchorY1 = fromD1;
337 anchorY2 = fromD1;
338 break;
339 case Y_AXIS2:
340 anchorX1 = area.atRight();
341 anchorX2 = toD2;
342 anchorY1 = fromD1;
343 anchorY2 = fromD1;
344 break;
345 }
346
347 if (lineStyle != null)
348 return new XYLineAnnotation(anchorX1, anchorY1, anchorX2, anchorY2, new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
349
350 return new XYLineAnnotation(anchorX1, anchorY1, anchorX2, anchorY2);
351 }
352 }

http://dive4elements.wald.intevation.org