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

http://dive4elements.wald.intevation.org