Mercurial > dive4elements > river
comparison artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.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 | 557cb3a3d772 |
children | 726d998dce29 |
comparison
equal
deleted
inserted
replaced
7042:599d3c48474c | 7043:06a9a241faac |
---|---|
19 import org.dive4elements.river.artifacts.access.RangeAccess; | 19 import org.dive4elements.river.artifacts.access.RangeAccess; |
20 import org.dive4elements.river.artifacts.D4EArtifact; | 20 import org.dive4elements.river.artifacts.D4EArtifact; |
21 import org.dive4elements.river.artifacts.resources.Resources; | 21 import org.dive4elements.river.artifacts.resources.Resources; |
22 import org.dive4elements.river.collections.D4EArtifactCollection; | 22 import org.dive4elements.river.collections.D4EArtifactCollection; |
23 import org.dive4elements.river.jfree.Bounds; | 23 import org.dive4elements.river.jfree.Bounds; |
24 import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation; | |
25 import org.dive4elements.river.jfree.DoubleBounds; | 24 import org.dive4elements.river.jfree.DoubleBounds; |
26 import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer; | 25 import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer; |
27 import org.dive4elements.river.jfree.RiverAnnotation; | 26 import org.dive4elements.river.jfree.RiverAnnotation; |
28 import org.dive4elements.river.jfree.StableXYDifferenceRenderer; | 27 import org.dive4elements.river.jfree.StableXYDifferenceRenderer; |
29 import org.dive4elements.river.jfree.StickyAxisAnnotation; | |
30 import org.dive4elements.river.jfree.Style; | 28 import org.dive4elements.river.jfree.Style; |
31 import org.dive4elements.river.jfree.StyledAreaSeriesCollection; | 29 import org.dive4elements.river.jfree.StyledAreaSeriesCollection; |
32 import org.dive4elements.river.jfree.StyledSeries; | 30 import org.dive4elements.river.jfree.StyledSeries; |
33 import org.dive4elements.river.jfree.AxisDataset; | 31 import org.dive4elements.river.jfree.AxisDataset; |
34 import org.dive4elements.river.model.River; | 32 import org.dive4elements.river.model.River; |
58 | 56 |
59 import org.apache.log4j.Logger; | 57 import org.apache.log4j.Logger; |
60 import org.jfree.chart.JFreeChart; | 58 import org.jfree.chart.JFreeChart; |
61 import org.jfree.chart.LegendItem; | 59 import org.jfree.chart.LegendItem; |
62 import org.jfree.chart.LegendItemCollection; | 60 import org.jfree.chart.LegendItemCollection; |
63 import org.jfree.chart.annotations.XYLineAnnotation; | |
64 import org.jfree.chart.annotations.XYTextAnnotation; | |
65 import org.jfree.chart.axis.NumberAxis; | 61 import org.jfree.chart.axis.NumberAxis; |
66 import org.jfree.chart.plot.XYPlot; | 62 import org.jfree.chart.plot.XYPlot; |
67 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; | 63 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; |
68 import org.jfree.chart.title.TextTitle; | 64 import org.jfree.chart.title.TextTitle; |
69 import org.jfree.data.Range; | 65 import org.jfree.data.Range; |
75 import org.w3c.dom.Element; | 71 import org.w3c.dom.Element; |
76 | 72 |
77 import org.dive4elements.river.utils.Formatter; | 73 import org.dive4elements.river.utils.Formatter; |
78 | 74 |
79 /** | 75 /** |
80 * The base class for chart creation. It should provide some basic things that | 76 * Implementation of the OutGenerator interface for charts. |
81 * equal in all chart types. | 77 * It should provide some basic things that equal in all chart types. |
82 * | 78 * |
83 * Annotations are added as RiverAnnotations and come in mutliple basic forms: | |
84 * TextAnnotations are labels somewhere in data space, StickyAnnotations are | |
85 * labels of a slice or line in one data dimension (i.e. visualized as label | |
86 * on a single axis). | |
87 * | |
88 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> | |
89 */ | 79 */ |
90 public abstract class ChartGenerator2 implements OutGenerator { | 80 public abstract class ChartGenerator2 implements OutGenerator { |
91 | 81 |
92 private static Logger logger = Logger.getLogger(ChartGenerator2.class); | 82 private static Logger logger = Logger.getLogger(ChartGenerator2.class); |
93 | 83 |
97 public static final Color DEFAULT_GRID_COLOR = Color.GRAY; | 87 public static final Color DEFAULT_GRID_COLOR = Color.GRAY; |
98 public static final float DEFAULT_GRID_LINE_WIDTH = 0.3f; | 88 public static final float DEFAULT_GRID_LINE_WIDTH = 0.3f; |
99 public static final int DEFAULT_FONT_SIZE = 12; | 89 public static final int DEFAULT_FONT_SIZE = 12; |
100 public static final String DEFAULT_FONT_NAME = "Tahoma"; | 90 public static final String DEFAULT_FONT_NAME = "Tahoma"; |
101 | 91 |
102 protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f; | |
103 | 92 |
104 public static final String XPATH_CHART_SIZE = | 93 public static final String XPATH_CHART_SIZE = |
105 "/art:action/art:attributes/art:size"; | 94 "/art:action/art:attributes/art:size"; |
106 | 95 |
107 public static final String XPATH_CHART_FORMAT = | 96 public static final String XPATH_CHART_FORMAT = |
153 */ | 142 */ |
154 public ChartGenerator2() { | 143 public ChartGenerator2() { |
155 datasets = new TreeMap<Integer, AxisDataset>(); | 144 datasets = new TreeMap<Integer, AxisDataset>(); |
156 } | 145 } |
157 | 146 |
158 | |
159 /** | 147 /** |
160 * Adds annotations to list. The given annotation will be visible. | 148 * Adds annotations to list. The given annotation will be visible. |
161 */ | 149 */ |
162 public void addAnnotations(RiverAnnotation annotation) { | 150 public void addAnnotations(RiverAnnotation annotation) { |
163 annotations.add(annotation); | 151 annotations.add(annotation); |
164 } | 152 } |
165 | |
166 /** | |
167 * Add a text and a line annotation. | |
168 * @param area convenience to determine positions in plot. | |
169 * @param theme (optional) theme document | |
170 */ | |
171 protected void addStickyAnnotation( | |
172 StickyAxisAnnotation annotation, | |
173 XYPlot plot, | |
174 ChartArea area, | |
175 LineStyle lineStyle, | |
176 TextStyle textStyle, | |
177 ThemeDocument theme | |
178 ) { | |
179 // OPTIMIZE pre-calculate area-related values | |
180 final float TEXT_OFF = 0.03f; | |
181 | |
182 XYLineAnnotation lineAnnotation = null; | |
183 XYTextAnnotation textAnnotation = null; | |
184 | |
185 int rendererIndex = 0; | |
186 | |
187 if (annotation.atX()) { | |
188 textAnnotation = new CollisionFreeXYTextAnnotation( | |
189 annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF)); | |
190 // OPTIMIZE externalize the calculation involving PI. | |
191 //textAnnotation.setRotationAngle(270f*Math.PI/180f); | |
192 lineAnnotation = createGroundStickAnnotation( | |
193 area, annotation.getPos(), lineStyle); | |
194 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); | |
195 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); | |
196 } | |
197 else { | |
198 // Do the more complicated case where we stick to the Y-Axis. | |
199 // There is one nasty case (duration curves, where annotations | |
200 // might stick to the second y-axis). | |
201 // FIXME: Remove dependency to XYChartGenerator2 here | |
202 AxisDataset dataset = getAxisDataset( | |
203 new Integer(annotation.getAxisSymbol())); | |
204 if (dataset == null) { | |
205 logger.warn("Annotation should stick to unfindable y-axis: " | |
206 + annotation.getAxisSymbol()); | |
207 rendererIndex = 0; | |
208 } | |
209 else { | |
210 rendererIndex = dataset.getPlotAxisIndex(); | |
211 } | |
212 | |
213 // Stick to the "right" (opposed to left) Y-Axis. | |
214 if (rendererIndex != 0) { | |
215 // OPTIMIZE: Pass a different area to this function, | |
216 // do the adding to renderer outside (let this | |
217 // function return the annotations). | |
218 // Note that this path is travelled rarely. | |
219 ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex)); | |
220 textAnnotation = new CollisionFreeXYTextAnnotation( | |
221 annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos()); | |
222 textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT); | |
223 textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT); | |
224 lineAnnotation = createRightStickAnnotation( | |
225 area2, annotation.getPos(), lineStyle); | |
226 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) { | |
227 // New line annotation to hit curve. | |
228 if (theme.parseShowVerticalLine()) { | |
229 XYLineAnnotation hitLineAnnotation = | |
230 createStickyLineAnnotation( | |
231 StickyAxisAnnotation.SimpleAxis.X_AXIS, | |
232 annotation.getHitPoint(), annotation.getPos(),// annotation.getHitPoint(), | |
233 area2, lineStyle); | |
234 plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation, | |
235 org.jfree.ui.Layer.BACKGROUND); | |
236 } | |
237 if (theme.parseShowHorizontalLine()) { | |
238 XYLineAnnotation lineBackAnnotation = | |
239 createStickyLineAnnotation( | |
240 StickyAxisAnnotation.SimpleAxis.Y_AXIS2, | |
241 annotation.getPos(), annotation.getHitPoint(), | |
242 area2, lineStyle); | |
243 plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation, | |
244 org.jfree.ui.Layer.BACKGROUND); | |
245 } | |
246 } | |
247 } | |
248 else { // Stick to the left y-axis. | |
249 textAnnotation = new CollisionFreeXYTextAnnotation( | |
250 annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos()); | |
251 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT); | |
252 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT); | |
253 lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle); | |
254 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) { | |
255 // New line annotation to hit curve. | |
256 if (theme.parseShowHorizontalLine()) { | |
257 XYLineAnnotation hitLineAnnotation = | |
258 createStickyLineAnnotation( | |
259 StickyAxisAnnotation.SimpleAxis.Y_AXIS, | |
260 annotation.getPos(), annotation.getHitPoint(), | |
261 area, lineStyle); | |
262 plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation, | |
263 org.jfree.ui.Layer.BACKGROUND); | |
264 } | |
265 if (theme.parseShowVerticalLine()) { | |
266 XYLineAnnotation lineBackAnnotation = | |
267 createStickyLineAnnotation( | |
268 StickyAxisAnnotation.SimpleAxis.X_AXIS, | |
269 annotation.getHitPoint(), annotation.getPos(), | |
270 area, lineStyle); | |
271 plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation, | |
272 org.jfree.ui.Layer.BACKGROUND); | |
273 } | |
274 } | |
275 } | |
276 } | |
277 | |
278 // Style the text. | |
279 if (textStyle != null) { | |
280 textStyle.apply(textAnnotation); | |
281 } | |
282 | |
283 // Add the Annotations to renderer. | |
284 plot.getRenderer(rendererIndex).addAnnotation(textAnnotation, | |
285 org.jfree.ui.Layer.FOREGROUND); | |
286 plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation, | |
287 org.jfree.ui.Layer.FOREGROUND); | |
288 } | |
289 | |
290 /** | |
291 * Create annotation that sticks to "ground" (X) axis. | |
292 * @param area helper to calculate coordinates | |
293 * @param pos one-dimensional position (distance from axis) | |
294 * @param lineStyle the line style to use for the line. | |
295 */ | |
296 protected static XYLineAnnotation createGroundStickAnnotation( | |
297 ChartArea area, float pos, LineStyle lineStyle | |
298 ) { | |
299 // Style the line. | |
300 if (lineStyle != null) { | |
301 return new XYLineAnnotation( | |
302 pos, area.atGround(), | |
303 pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET), | |
304 new BasicStroke(lineStyle.getWidth()),lineStyle.getColor()); | |
305 } | |
306 else { | |
307 return new XYLineAnnotation( | |
308 pos, area.atGround(), | |
309 pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET)); | |
310 } | |
311 } | |
312 | |
313 | |
314 /** | |
315 * Create annotation that sticks to the second Y axis ("right"). | |
316 * @param area helper to calculate coordinates | |
317 * @param pos one-dimensional position (distance from axis) | |
318 * @param lineStyle the line style to use for the line. | |
319 */ | |
320 protected static XYLineAnnotation createRightStickAnnotation( | |
321 ChartArea area, float pos, LineStyle lineStyle | |
322 ) { | |
323 // Style the line. | |
324 if (lineStyle != null) { | |
325 return new XYLineAnnotation( | |
326 area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos, | |
327 area.atRight(), pos, | |
328 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor()); | |
329 } | |
330 else { | |
331 return new XYLineAnnotation( | |
332 area.atRight(), pos, | |
333 area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos); | |
334 } | |
335 } | |
336 | |
337 | |
338 /** | |
339 * Create annotation that sticks to the first Y axis ("left"). | |
340 * @param area helper to calculate coordinates | |
341 * @param pos one-dimensional position (distance from axis) | |
342 * @param lineStyle the line style to use for the line. | |
343 */ | |
344 protected static XYLineAnnotation createLeftStickAnnotation( | |
345 ChartArea area, float pos, LineStyle lineStyle | |
346 ) { | |
347 // Style the line. | |
348 if (lineStyle != null) { | |
349 return new XYLineAnnotation( | |
350 area.atLeft(), pos, | |
351 area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos, | |
352 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor()); | |
353 } | |
354 else { | |
355 return new XYLineAnnotation( | |
356 area.atLeft(), pos, | |
357 area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos); | |
358 } | |
359 } | |
360 | |
361 | |
362 /** | |
363 * Create a line from a axis to a given point. | |
364 * @param axis The "simple" axis. | |
365 * @param fromD1 from-location in first dimension. | |
366 * @param toD2 to-location in second dimension. | |
367 * @param area helper to calculate offsets. | |
368 * @param lineStyle optional line style. | |
369 */ | |
370 protected static XYLineAnnotation createStickyLineAnnotation( | |
371 StickyAxisAnnotation.SimpleAxis axis, float fromD1, float toD2, | |
372 ChartArea area, LineStyle lineStyle | |
373 ) { | |
374 double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d; | |
375 switch(axis) { | |
376 case X_AXIS: | |
377 anchorX1 = fromD1; | |
378 anchorX2 = fromD1; | |
379 anchorY1 = area.atGround(); | |
380 anchorY2 = toD2; | |
381 break; | |
382 case Y_AXIS: | |
383 anchorX1 = area.atLeft(); | |
384 anchorX2 = toD2; | |
385 anchorY1 = fromD1; | |
386 anchorY2 = fromD1; | |
387 break; | |
388 case Y_AXIS2: | |
389 anchorX1 = area.atRight(); | |
390 anchorX2 = toD2; | |
391 anchorY1 = fromD1; | |
392 anchorY2 = fromD1; | |
393 break; | |
394 } | |
395 // Style the line. | |
396 if (lineStyle != null) { | |
397 return new XYLineAnnotation( | |
398 anchorX1, anchorY1, | |
399 anchorX2, anchorY2, | |
400 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor()); | |
401 } | |
402 else { | |
403 return new XYLineAnnotation( | |
404 anchorX1, anchorY1, | |
405 anchorX2, anchorY2); | |
406 } | |
407 } | |
408 | |
409 /** | |
410 * Add the annotations (Sticky, Text and hyk zones) stored | |
411 * in the annotations field. | |
412 * @param plot Plot to add annotations to. | |
413 */ | |
414 protected void addAnnotationsToRenderer(XYPlot plot) { | |
415 logger.debug("addAnnotationsToRenderer"); | |
416 | |
417 if (annotations == null || annotations.isEmpty()) { | |
418 logger.debug("addAnnotationsToRenderer: no annotations."); | |
419 return; | |
420 } | |
421 | |
422 // OPTMIMIZE: Pre-calculate positions | |
423 ChartArea area = new ChartArea( | |
424 plot.getDomainAxis(0).getRange(), | |
425 plot.getRangeAxis().getRange()); | |
426 | |
427 // Walk over all Annotation sets. | |
428 for (RiverAnnotation fa: annotations) { | |
429 | |
430 // Access text styling, if any. | |
431 ThemeDocument theme = fa.getTheme(); | |
432 TextStyle textStyle = null; | |
433 LineStyle lineStyle = null; | |
434 | |
435 // Get Themeing information and add legend item. | |
436 if (theme != null) { | |
437 textStyle = theme.parseComplexTextStyle(); | |
438 lineStyle = theme.parseComplexLineStyle(); | |
439 if (fa.getLabel() != null) { | |
440 LegendItemCollection lic = new LegendItemCollection(); | |
441 LegendItemCollection old = plot.getFixedLegendItems(); | |
442 lic.add(createLegendItem(theme, fa.getLabel())); | |
443 // (Re-)Add prior legend entries. | |
444 if (old != null) { | |
445 old.addAll(lic); | |
446 } | |
447 else { | |
448 old = lic; | |
449 } | |
450 plot.setFixedLegendItems(old); | |
451 } | |
452 } | |
453 | |
454 // The 'Sticky' Annotations (at axis, with line and text). | |
455 for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) { | |
456 addStickyAnnotation( | |
457 sta, plot, area, lineStyle, textStyle, theme); | |
458 } | |
459 | |
460 // Other Text Annotations (e.g. labels of (manual) points). | |
461 for (XYTextAnnotation ta: fa.getTextAnnotations()) { | |
462 // Style the text. | |
463 if (textStyle != null) { | |
464 textStyle.apply(ta); | |
465 } | |
466 ta.setY(area.above(0.05d, ta.getY())); | |
467 plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND); | |
468 } | |
469 } | |
470 } | |
471 | |
472 | 153 |
473 /** | 154 /** |
474 * This method needs to be implemented by concrete subclasses to create new | 155 * This method needs to be implemented by concrete subclasses to create new |
475 * instances of JFreeChart. | 156 * instances of JFreeChart. |
476 * | 157 * |
582 | 263 |
583 if (subtitle != null && subtitle.length() > 0) { | 264 if (subtitle != null && subtitle.length() > 0) { |
584 chart.addSubtitle(new TextTitle(subtitle)); | 265 chart.addSubtitle(new TextTitle(subtitle)); |
585 } | 266 } |
586 } | 267 } |
587 | |
588 | |
589 /** | |
590 * Register annotations like MainValues for later plotting | |
591 * | |
592 * @param annotations list of annotations (data of facet). | |
593 * @param aandf Artifact and the facet. | |
594 * @param theme Theme document for given annotations. | |
595 * @param visible The visibility of the annotations. | |
596 */ | |
597 public void doAnnotations( | |
598 RiverAnnotation annotations, | |
599 ArtifactAndFacet aandf, | |
600 ThemeDocument theme, | |
601 boolean visible | |
602 ){ | |
603 logger.debug("doAnnotations"); | |
604 | |
605 // Add all annotations to our annotation pool. | |
606 annotations.setTheme(theme); | |
607 if (aandf != null) { | |
608 annotations.setLabel(aandf.getFacetDescription()); | |
609 } | |
610 else { | |
611 logger.error( | |
612 "Art/Facet for Annotations is null. " + | |
613 "This should never happen!"); | |
614 } | |
615 | |
616 if (visible) { | |
617 addAnnotations(annotations); | |
618 } | |
619 } | |
620 | |
621 | 268 |
622 /** | 269 /** |
623 * Generate chart. | 270 * Generate chart. |
624 */ | 271 */ |
625 @Override | 272 @Override |