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