comparison artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java @ 7033:0d91a6598a89 generator-refactoring

Add a copy of the ChartGenerator class. This class will have very reduced functionality and mostly provide the interface defined in OutGenerator and work as a bridge for the Charts that are not in a Diagram (think svg export) This will later replace the ChartGenerator class again after the refactoring is done.
author Andre Heinecke <aheinecke@intevation.de>
date Tue, 17 Sep 2013 16:49:37 +0200
parents artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator.java@819481cc9195
children 557cb3a3d772
comparison
equal deleted inserted replaced
7032:069acf3cf45e 7033:0d91a6598a89
1 /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
2 * Software engineering by Intevation GmbH
3 *
4 * This file is Free Software under the GNU AGPL (>=v3)
5 * and comes with ABSOLUTELY NO WARRANTY! Check out the
6 * documentation coming with Dive4Elements River for details.
7 */
8
9 package org.dive4elements.river.exports;
10
11 import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
12 import org.dive4elements.artifactdatabase.state.Settings;
13 import org.dive4elements.artifacts.Artifact;
14 import org.dive4elements.artifacts.ArtifactNamespaceContext;
15 import org.dive4elements.artifacts.CallContext;
16 import org.dive4elements.artifacts.CallMeta;
17 import org.dive4elements.artifacts.PreferredLocale;
18 import org.dive4elements.artifacts.common.utils.XMLUtils;
19 import org.dive4elements.river.artifacts.access.RangeAccess;
20 import org.dive4elements.river.artifacts.D4EArtifact;
21 import org.dive4elements.river.artifacts.resources.Resources;
22 import org.dive4elements.river.collections.D4EArtifactCollection;
23 import org.dive4elements.river.jfree.Bounds;
24 import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
25 import org.dive4elements.river.jfree.DoubleBounds;
26 import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer;
27 import org.dive4elements.river.jfree.RiverAnnotation;
28 import org.dive4elements.river.jfree.StableXYDifferenceRenderer;
29 import org.dive4elements.river.jfree.StickyAxisAnnotation;
30 import org.dive4elements.river.jfree.Style;
31 import org.dive4elements.river.jfree.StyledAreaSeriesCollection;
32 import org.dive4elements.river.jfree.StyledSeries;
33 import org.dive4elements.river.model.River;
34 import org.dive4elements.river.themes.LineStyle;
35 import org.dive4elements.river.themes.TextStyle;
36 import org.dive4elements.river.themes.ThemeDocument;
37 import org.dive4elements.river.utils.RiverUtils;
38
39 import java.awt.BasicStroke;
40 import java.awt.Color;
41 import java.awt.Font;
42 import java.awt.Paint;
43 import java.awt.Stroke;
44 import java.awt.TexturePaint;
45 import java.awt.geom.Rectangle2D;
46 import java.awt.image.BufferedImage;
47 import java.io.IOException;
48 import java.io.OutputStream;
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Locale;
52 import java.util.Map;
53 import java.util.SortedMap;
54 import java.util.TreeMap;
55
56 import javax.xml.xpath.XPathConstants;
57
58 import org.apache.log4j.Logger;
59 import org.jfree.chart.JFreeChart;
60 import org.jfree.chart.LegendItem;
61 import org.jfree.chart.LegendItemCollection;
62 import org.jfree.chart.annotations.XYLineAnnotation;
63 import org.jfree.chart.annotations.XYTextAnnotation;
64 import org.jfree.chart.axis.NumberAxis;
65 import org.jfree.chart.plot.XYPlot;
66 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
67 import org.jfree.chart.title.TextTitle;
68 import org.jfree.data.Range;
69 import org.jfree.data.general.Series;
70 import org.jfree.data.xy.XYDataset;
71 import org.jfree.ui.RectangleInsets;
72 import org.jfree.ui.TextAnchor;
73 import org.w3c.dom.Document;
74 import org.w3c.dom.Element;
75
76 import org.dive4elements.river.utils.Formatter;
77
78 /**
79 * The base class for chart creation. It should provide some basic things that
80 * equal in all chart types.
81 *
82 * Annotations are added as RiverAnnotations and come in mutliple basic forms:
83 * TextAnnotations are labels somewhere in data space, StickyAnnotations are
84 * labels of a slice or line in one data dimension (i.e. visualized as label
85 * on a single axis).
86 *
87 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
88 */
89 public abstract class ChartGenerator2 implements OutGenerator {
90
91 private static Logger logger = Logger.getLogger(ChartGenerator2.class);
92
93 public static final int DEFAULT_CHART_WIDTH = 600;
94 public static final int DEFAULT_CHART_HEIGHT = 400;
95 public static final String DEFAULT_CHART_FORMAT = "png";
96 public static final Color DEFAULT_GRID_COLOR = Color.GRAY;
97 public static final float DEFAULT_GRID_LINE_WIDTH = 0.3f;
98 public static final int DEFAULT_FONT_SIZE = 12;
99 public static final String DEFAULT_FONT_NAME = "Tahoma";
100
101 protected static float ANNOTATIONS_AXIS_OFFSET = 0.02f;
102
103 public static final String XPATH_CHART_SIZE =
104 "/art:action/art:attributes/art:size";
105
106 public static final String XPATH_CHART_FORMAT =
107 "/art:action/art:attributes/art:format/@art:value";
108
109 public static final String XPATH_CHART_X_RANGE =
110 "/art:action/art:attributes/art:xrange";
111
112 public static final String XPATH_CHART_Y_RANGE =
113 "/art:action/art:attributes/art:yrange";
114
115
116 /** The document of the incoming out() request.*/
117 protected Document request;
118
119 /** The output stream where the data should be written to.*/
120 protected OutputStream out;
121
122 /** The CallContext object.*/
123 protected CallContext context;
124
125 protected D4EArtifactCollection collection;
126
127 /** The artifact that is used to decorate the chart with meta information.*/
128 protected Artifact master;
129
130 /** The settings that should be used during output creation.*/
131 protected Settings settings;
132
133 /** Map of datasets ("index"). */
134 protected SortedMap<Integer, AxisDataset> datasets;
135
136 /** List of annotations to insert in plot. */
137 protected List<RiverAnnotation> annotations = new ArrayList<RiverAnnotation>();
138
139 /**
140 * A mini interface that allows to walk over the YAXIS enums defined in
141 * subclasses.
142 */
143 public interface YAxisWalker {
144
145 int length();
146
147 String getId(int idx);
148 } // end of YAxisWalker interface
149
150
151
152 public interface AxisDataset {
153
154 void addDataset(XYDataset dataset);
155
156 XYDataset[] getDatasets();
157
158 boolean isEmpty();
159
160 void setRange(Range range);
161
162 Range getRange();
163
164 boolean isArea(XYDataset dataset);
165
166 void setPlotAxisIndex(int idx);
167
168 int getPlotAxisIndex();
169
170 } // end of AxisDataset interface
171
172
173
174 /**
175 * Default constructor that initializes internal data structures.
176 */
177 public ChartGenerator2() {
178 datasets = new TreeMap<Integer, AxisDataset>();
179 }
180
181
182 /**
183 * Adds annotations to list. The given annotation will be visible.
184 */
185 public void addAnnotations(RiverAnnotation annotation) {
186 annotations.add(annotation);
187 }
188
189 /**
190 * Add a text and a line annotation.
191 * @param area convenience to determine positions in plot.
192 * @param theme (optional) theme document
193 */
194 protected void addStickyAnnotation(
195 StickyAxisAnnotation annotation,
196 XYPlot plot,
197 ChartArea area,
198 LineStyle lineStyle,
199 TextStyle textStyle,
200 ThemeDocument theme
201 ) {
202 // OPTIMIZE pre-calculate area-related values
203 final float TEXT_OFF = 0.03f;
204
205 XYLineAnnotation lineAnnotation = null;
206 XYTextAnnotation textAnnotation = null;
207
208 int rendererIndex = 0;
209
210 if (annotation.atX()) {
211 textAnnotation = new CollisionFreeXYTextAnnotation(
212 annotation.getText(), annotation.getPos(), area.ofGround(TEXT_OFF));
213 // OPTIMIZE externalize the calculation involving PI.
214 //textAnnotation.setRotationAngle(270f*Math.PI/180f);
215 lineAnnotation = createGroundStickAnnotation(
216 area, annotation.getPos(), lineStyle);
217 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
218 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
219 }
220 else {
221 // Do the more complicated case where we stick to the Y-Axis.
222 // There is one nasty case (duration curves, where annotations
223 // might stick to the second y-axis).
224 // FIXME: Remove dependency to XYChartGenerator2 here
225 AxisDataset dataset = getAxisDataset(
226 new Integer(annotation.getAxisSymbol()));
227 if (dataset == null) {
228 logger.warn("Annotation should stick to unfindable y-axis: "
229 + annotation.getAxisSymbol());
230 rendererIndex = 0;
231 }
232 else {
233 rendererIndex = dataset.getPlotAxisIndex();
234 }
235
236 // Stick to the "right" (opposed to left) Y-Axis.
237 if (rendererIndex != 0) {
238 // OPTIMIZE: Pass a different area to this function,
239 // do the adding to renderer outside (let this
240 // function return the annotations).
241 // Note that this path is travelled rarely.
242 ChartArea area2 = new ChartArea(plot.getDomainAxis(), plot.getRangeAxis(rendererIndex));
243 textAnnotation = new CollisionFreeXYTextAnnotation(
244 annotation.getText(), area2.ofRight(TEXT_OFF), annotation.getPos());
245 textAnnotation.setRotationAnchor(TextAnchor.CENTER_RIGHT);
246 textAnnotation.setTextAnchor(TextAnchor.CENTER_RIGHT);
247 lineAnnotation = createRightStickAnnotation(
248 area2, annotation.getPos(), lineStyle);
249 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
250 // New line annotation to hit curve.
251 if (theme.parseShowVerticalLine()) {
252 XYLineAnnotation hitLineAnnotation =
253 createStickyLineAnnotation(
254 StickyAxisAnnotation.SimpleAxis.X_AXIS,
255 annotation.getHitPoint(), annotation.getPos(),// annotation.getHitPoint(),
256 area2, lineStyle);
257 plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
258 org.jfree.ui.Layer.BACKGROUND);
259 }
260 if (theme.parseShowHorizontalLine()) {
261 XYLineAnnotation lineBackAnnotation =
262 createStickyLineAnnotation(
263 StickyAxisAnnotation.SimpleAxis.Y_AXIS2,
264 annotation.getPos(), annotation.getHitPoint(),
265 area2, lineStyle);
266 plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
267 org.jfree.ui.Layer.BACKGROUND);
268 }
269 }
270 }
271 else { // Stick to the left y-axis.
272 textAnnotation = new CollisionFreeXYTextAnnotation(
273 annotation.getText(), area.ofLeft(TEXT_OFF), annotation.getPos());
274 textAnnotation.setRotationAnchor(TextAnchor.CENTER_LEFT);
275 textAnnotation.setTextAnchor(TextAnchor.CENTER_LEFT);
276 lineAnnotation = createLeftStickAnnotation(area, annotation.getPos(), lineStyle);
277 if (!Float.isNaN(annotation.getHitPoint()) && theme != null) {
278 // New line annotation to hit curve.
279 if (theme.parseShowHorizontalLine()) {
280 XYLineAnnotation hitLineAnnotation =
281 createStickyLineAnnotation(
282 StickyAxisAnnotation.SimpleAxis.Y_AXIS,
283 annotation.getPos(), annotation.getHitPoint(),
284 area, lineStyle);
285 plot.getRenderer(rendererIndex).addAnnotation(hitLineAnnotation,
286 org.jfree.ui.Layer.BACKGROUND);
287 }
288 if (theme.parseShowVerticalLine()) {
289 XYLineAnnotation lineBackAnnotation =
290 createStickyLineAnnotation(
291 StickyAxisAnnotation.SimpleAxis.X_AXIS,
292 annotation.getHitPoint(), annotation.getPos(),
293 area, lineStyle);
294 plot.getRenderer(rendererIndex).addAnnotation(lineBackAnnotation,
295 org.jfree.ui.Layer.BACKGROUND);
296 }
297 }
298 }
299 }
300
301 // Style the text.
302 if (textStyle != null) {
303 textStyle.apply(textAnnotation);
304 }
305
306 // Add the Annotations to renderer.
307 plot.getRenderer(rendererIndex).addAnnotation(textAnnotation,
308 org.jfree.ui.Layer.FOREGROUND);
309 plot.getRenderer(rendererIndex).addAnnotation(lineAnnotation,
310 org.jfree.ui.Layer.FOREGROUND);
311 }
312
313 /**
314 * Create annotation that sticks to "ground" (X) axis.
315 * @param area helper to calculate coordinates
316 * @param pos one-dimensional position (distance from axis)
317 * @param lineStyle the line style to use for the line.
318 */
319 protected static XYLineAnnotation createGroundStickAnnotation(
320 ChartArea area, float pos, LineStyle lineStyle
321 ) {
322 // Style the line.
323 if (lineStyle != null) {
324 return new XYLineAnnotation(
325 pos, area.atGround(),
326 pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET),
327 new BasicStroke(lineStyle.getWidth()),lineStyle.getColor());
328 }
329 else {
330 return new XYLineAnnotation(
331 pos, area.atGround(),
332 pos, area.ofGround(ANNOTATIONS_AXIS_OFFSET));
333 }
334 }
335
336
337 /**
338 * Create annotation that sticks to the second Y axis ("right").
339 * @param area helper to calculate coordinates
340 * @param pos one-dimensional position (distance from axis)
341 * @param lineStyle the line style to use for the line.
342 */
343 protected static XYLineAnnotation createRightStickAnnotation(
344 ChartArea area, float pos, LineStyle lineStyle
345 ) {
346 // Style the line.
347 if (lineStyle != null) {
348 return new XYLineAnnotation(
349 area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos,
350 area.atRight(), pos,
351 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
352 }
353 else {
354 return new XYLineAnnotation(
355 area.atRight(), pos,
356 area.ofRight(ANNOTATIONS_AXIS_OFFSET), pos);
357 }
358 }
359
360
361 /**
362 * Create annotation that sticks to the first Y axis ("left").
363 * @param area helper to calculate coordinates
364 * @param pos one-dimensional position (distance from axis)
365 * @param lineStyle the line style to use for the line.
366 */
367 protected static XYLineAnnotation createLeftStickAnnotation(
368 ChartArea area, float pos, LineStyle lineStyle
369 ) {
370 // Style the line.
371 if (lineStyle != null) {
372 return new XYLineAnnotation(
373 area.atLeft(), pos,
374 area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos,
375 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
376 }
377 else {
378 return new XYLineAnnotation(
379 area.atLeft(), pos,
380 area.ofLeft(ANNOTATIONS_AXIS_OFFSET), pos);
381 }
382 }
383
384
385 /**
386 * Create a line from a axis to a given point.
387 * @param axis The "simple" axis.
388 * @param fromD1 from-location in first dimension.
389 * @param toD2 to-location in second dimension.
390 * @param area helper to calculate offsets.
391 * @param lineStyle optional line style.
392 */
393 protected static XYLineAnnotation createStickyLineAnnotation(
394 StickyAxisAnnotation.SimpleAxis axis, float fromD1, float toD2,
395 ChartArea area, LineStyle lineStyle
396 ) {
397 double anchorX1 = 0d, anchorX2 = 0d, anchorY1 = 0d, anchorY2 = 0d;
398 switch(axis) {
399 case X_AXIS:
400 anchorX1 = fromD1;
401 anchorX2 = fromD1;
402 anchorY1 = area.atGround();
403 anchorY2 = toD2;
404 break;
405 case Y_AXIS:
406 anchorX1 = area.atLeft();
407 anchorX2 = toD2;
408 anchorY1 = fromD1;
409 anchorY2 = fromD1;
410 break;
411 case Y_AXIS2:
412 anchorX1 = area.atRight();
413 anchorX2 = toD2;
414 anchorY1 = fromD1;
415 anchorY2 = fromD1;
416 break;
417 }
418 // Style the line.
419 if (lineStyle != null) {
420 return new XYLineAnnotation(
421 anchorX1, anchorY1,
422 anchorX2, anchorY2,
423 new BasicStroke(lineStyle.getWidth()), lineStyle.getColor());
424 }
425 else {
426 return new XYLineAnnotation(
427 anchorX1, anchorY1,
428 anchorX2, anchorY2);
429 }
430 }
431
432 /**
433 * Add the annotations (Sticky, Text and hyk zones) stored
434 * in the annotations field.
435 * @param plot Plot to add annotations to.
436 */
437 protected void addAnnotationsToRenderer(XYPlot plot) {
438 logger.debug("addAnnotationsToRenderer");
439
440 if (annotations == null || annotations.isEmpty()) {
441 logger.debug("addAnnotationsToRenderer: no annotations.");
442 return;
443 }
444
445 // OPTMIMIZE: Pre-calculate positions
446 ChartArea area = new ChartArea(
447 plot.getDomainAxis(0).getRange(),
448 plot.getRangeAxis().getRange());
449
450 // Walk over all Annotation sets.
451 for (RiverAnnotation fa: annotations) {
452
453 // Access text styling, if any.
454 ThemeDocument theme = fa.getTheme();
455 TextStyle textStyle = null;
456 LineStyle lineStyle = null;
457
458 // Get Themeing information and add legend item.
459 if (theme != null) {
460 textStyle = theme.parseComplexTextStyle();
461 lineStyle = theme.parseComplexLineStyle();
462 if (fa.getLabel() != null) {
463 LegendItemCollection lic = new LegendItemCollection();
464 LegendItemCollection old = plot.getFixedLegendItems();
465 lic.add(createLegendItem(theme, fa.getLabel()));
466 // (Re-)Add prior legend entries.
467 if (old != null) {
468 old.addAll(lic);
469 }
470 else {
471 old = lic;
472 }
473 plot.setFixedLegendItems(old);
474 }
475 }
476
477 // The 'Sticky' Annotations (at axis, with line and text).
478 for (StickyAxisAnnotation sta: fa.getAxisTextAnnotations()) {
479 addStickyAnnotation(
480 sta, plot, area, lineStyle, textStyle, theme);
481 }
482
483 // Other Text Annotations (e.g. labels of (manual) points).
484 for (XYTextAnnotation ta: fa.getTextAnnotations()) {
485 // Style the text.
486 if (textStyle != null) {
487 textStyle.apply(ta);
488 }
489 ta.setY(area.above(0.05d, ta.getY()));
490 plot.getRenderer().addAnnotation(ta, org.jfree.ui.Layer.FOREGROUND);
491 }
492 }
493 }
494
495
496 /**
497 * This method needs to be implemented by concrete subclasses to create new
498 * instances of JFreeChart.
499 *
500 * @return a new instance of a JFreeChart.
501 */
502 public abstract JFreeChart generateChart();
503
504
505 /** For every outable (i.e. facets), this function is
506 * called and handles the data accordingly. */
507 @Override
508 public abstract void doOut(
509 ArtifactAndFacet bundle,
510 ThemeDocument attr,
511 boolean visible);
512
513
514 protected abstract YAxisWalker getYAxisWalker();
515
516
517 protected abstract Series getSeriesOf(XYDataset dataset, int idx);
518
519 /**
520 * Returns the default title of a chart.
521 *
522 * @return the default title of a chart.
523 */
524 protected abstract String getDefaultChartTitle();
525
526
527 /**
528 * Returns the default X-Axis label of a chart.
529 *
530 * @return the default X-Axis label of a chart.
531 */
532 protected abstract String getDefaultXAxisLabel();
533
534
535 /**
536 * This method is called to retrieve the default label for an Y axis at
537 * position <i>pos</i>.
538 *
539 * @param pos The position of an Y axis.
540 *
541 * @return the default Y axis label at position <i>pos</i>.
542 */
543 protected abstract String getDefaultYAxisLabel(int pos);
544
545
546 /**
547 * This method is used to create new AxisDataset instances which may differ
548 * in concrete subclasses.
549 *
550 * @param idx The index of an axis.
551 */
552 protected abstract AxisDataset createAxisDataset(int idx);
553
554
555 /**
556 * Combines the ranges of the X axis at index <i>idx</i>.
557 *
558 * @param bounds A new Bounds.
559 * @param idx The index of the X axis that should be comined with
560 * <i>range</i>.
561 */
562 protected abstract void combineXBounds(Bounds bounds, int idx);
563
564
565 /**
566 * Combines the ranges of the Y axis at index <i>idx</i>.
567 *
568 * @param bounds A new Bounds.
569 * @param index The index of the Y axis that should be comined with.
570 * <i>range</i>.
571 */
572 protected abstract void combineYBounds(Bounds bounds, int index);
573
574
575 /**
576 * This method is used to determine the ranges for axes at a given index.
577 *
578 * @param index The index of the axes at the plot.
579 *
580 * @return a Range[] with [xrange, yrange];
581 */
582 public abstract Range[] getRangesForAxis(int index);
583
584 public abstract Bounds getXBounds(int axis);
585
586 protected abstract void setXBounds(int axis, Bounds bounds);
587
588 public abstract Bounds getYBounds(int axis);
589
590 protected abstract void setYBounds(int axis, Bounds bounds);
591
592
593 /**
594 * This method retrieves the chart subtitle by calling getChartSubtitle()
595 * and adds it as TextTitle to the chart.
596 * The default implementation of getChartSubtitle() returns the same
597 * as getDefaultChartSubtitle() which must be implemented by derived
598 * classes. If you want to add multiple subtitles to the chart override
599 * this method and add your subtitles manually.
600 *
601 * @param chart The JFreeChart chart object.
602 */
603 protected void addSubtitles(JFreeChart chart) {
604 String subtitle = getChartSubtitle();
605
606 if (subtitle != null && subtitle.length() > 0) {
607 chart.addSubtitle(new TextTitle(subtitle));
608 }
609 }
610
611
612 /**
613 * Register annotations like MainValues for later plotting
614 *
615 * @param annotations list of annotations (data of facet).
616 * @param aandf Artifact and the facet.
617 * @param theme Theme document for given annotations.
618 * @param visible The visibility of the annotations.
619 */
620 public void doAnnotations(
621 RiverAnnotation annotations,
622 ArtifactAndFacet aandf,
623 ThemeDocument theme,
624 boolean visible
625 ){
626 logger.debug("doAnnotations");
627
628 // Add all annotations to our annotation pool.
629 annotations.setTheme(theme);
630 if (aandf != null) {
631 annotations.setLabel(aandf.getFacetDescription());
632 }
633 else {
634 logger.error(
635 "Art/Facet for Annotations is null. " +
636 "This should never happen!");
637 }
638
639 if (visible) {
640 addAnnotations(annotations);
641 }
642 }
643
644
645 /**
646 * Generate chart.
647 */
648 @Override
649 public void generate()
650 throws IOException
651 {
652 logger.debug("ChartGenerator2.generate");
653
654 JFreeChart chart = generateChart();
655
656 String format = getFormat();
657 int[] size = getSize();
658
659 if (size == null) {
660 size = getExportDimension();
661 }
662
663 context.putContextValue("chart.width", size[0]);
664 context.putContextValue("chart.height", size[1]);
665
666 if (format.equals(ChartExportHelper.FORMAT_PNG)) {
667 context.putContextValue("chart.image.format", "png");
668
669 ChartExportHelper.exportImage(
670 out,
671 chart,
672 context);
673 }
674 else if (format.equals(ChartExportHelper.FORMAT_PDF)) {
675 preparePDFContext(context);
676
677 ChartExportHelper.exportPDF(
678 out,
679 chart,
680 context);
681 }
682 else if (format.equals(ChartExportHelper.FORMAT_SVG)) {
683 prepareSVGContext(context);
684
685 ChartExportHelper.exportSVG(
686 out,
687 chart,
688 context);
689 }
690 else if (format.equals(ChartExportHelper.FORMAT_CSV)) {
691 context.putContextValue("chart.image.format", "csv");
692
693 ChartExportHelper.exportCSV(
694 out,
695 chart,
696 context);
697 }
698 }
699
700
701 @Override
702 public void init(Document request, OutputStream out, CallContext context) {
703 logger.debug("ChartGenerator2.init");
704
705 this.request = request;
706 this.out = out;
707 this.context = context;
708 }
709
710
711 /** Sets the master artifact. */
712 @Override
713 public void setMasterArtifact(Artifact master) {
714 this.master = master;
715 }
716
717
718 /**
719 * Gets the master artifact.
720 * @return the master artifact.
721 */
722 public Artifact getMaster() {
723 return master;
724 }
725
726
727 /** Sets the collection. */
728 @Override
729 public void setCollection(D4EArtifactCollection collection) {
730 this.collection = collection;
731 }
732
733
734 @Override
735 public void setSettings(Settings settings) {
736 this.settings = settings;
737 }
738
739
740 /**
741 * Returns an instance of <i>ChartSettings</i> with a chart specific section
742 * but with no axes settings.
743 *
744 * @return an instance of <i>ChartSettings</i>.
745 */
746 @Override
747 public Settings getSettings() {
748 if (this.settings != null) {
749 return this.settings;
750 }
751
752 ChartSettings settings = new ChartSettings();
753
754 ChartSection chartSection = buildChartSection();
755 LegendSection legendSection = buildLegendSection();
756 ExportSection exportSection = buildExportSection();
757
758 settings.setChartSection(chartSection);
759 settings.setLegendSection(legendSection);
760 settings.setExportSection(exportSection);
761
762 List<AxisSection> axisSections = buildAxisSections();
763 for (AxisSection axisSection: axisSections) {
764 settings.addAxisSection(axisSection);
765 }
766
767 return settings;
768 }
769
770
771 /**
772 * Creates a new <i>ChartSection</i>.
773 *
774 * @return a new <i>ChartSection</i>.
775 */
776 protected ChartSection buildChartSection() {
777 ChartSection chartSection = new ChartSection();
778 chartSection.setTitle(getChartTitle());
779 chartSection.setSubtitle(getChartSubtitle());
780 chartSection.setDisplayGrid(isGridVisible());
781 chartSection.setDisplayLogo(showLogo());
782 chartSection.setLogoVPlacement(logoVPlace());
783 chartSection.setLogoHPlacement(logoHPlace());
784 return chartSection;
785 }
786
787
788 /**
789 * Creates a new <i>LegendSection</i>.
790 *
791 * @return a new <i>LegendSection</i>.
792 */
793 protected LegendSection buildLegendSection() {
794 LegendSection legendSection = new LegendSection();
795 legendSection.setVisibility(isLegendVisible());
796 legendSection.setFontSize(getLegendFontSize());
797 legendSection.setAggregationThreshold(10);
798 return legendSection;
799 }
800
801
802 /**
803 * Creates a new <i>ExportSection</i> with default values <b>WIDTH=600</b>
804 * and <b>HEIGHT=400</b>.
805 *
806 * @return a new <i>ExportSection</i>.
807 */
808 protected ExportSection buildExportSection() {
809 ExportSection exportSection = new ExportSection();
810 exportSection.setWidth(600);
811 exportSection.setHeight(400);
812 return exportSection;
813 }
814
815
816 /**
817 * Creates a list of Sections that contains all axes of the chart (including
818 * X and Y axes).
819 *
820 * @return a list of Sections for each axis in this chart.
821 */
822 protected List<AxisSection> buildAxisSections() {
823 List<AxisSection> axisSections = new ArrayList<AxisSection>();
824
825 axisSections.addAll(buildXAxisSections());
826 axisSections.addAll(buildYAxisSections());
827
828 return axisSections;
829 }
830
831
832 /**
833 * Creates a new Section for chart's X axis.
834 *
835 * @return a List that contains a Section for the X axis.
836 */
837 protected List<AxisSection> buildXAxisSections() {
838 List<AxisSection> axisSections = new ArrayList<AxisSection>();
839
840 String identifier = "X";
841
842 AxisSection axisSection = new AxisSection();
843 axisSection.setIdentifier(identifier);
844 axisSection.setLabel(getXAxisLabel());
845 axisSection.setFontSize(14);
846 axisSection.setFixed(false);
847
848 // XXX We are able to find better default ranges that [0,0], but the Y
849 // axes currently have no better ranges set.
850 axisSection.setUpperRange(0d);
851 axisSection.setLowerRange(0d);
852
853 axisSections.add(axisSection);
854
855 return axisSections;
856 }
857
858
859 /**
860 * Creates a list of Section for the chart's Y axes. This method makes use
861 * of <i>getYAxisWalker</i> to be able to access all Y axes defined in
862 * subclasses.
863 *
864 * @return a list of Y axis sections.
865 */
866 protected List<AxisSection> buildYAxisSections() {
867 List<AxisSection> axisSections = new ArrayList<AxisSection>();
868
869 YAxisWalker walker = getYAxisWalker();
870 for (int i = 0, n = walker.length(); i < n; i++) {
871 AxisSection ySection = new AxisSection();
872 ySection.setIdentifier(walker.getId(i));
873 ySection.setLabel(getYAxisLabel(i));
874 ySection.setFontSize(14);
875 ySection.setFixed(false);
876
877 // XXX We are able to find better default ranges that [0,0], the
878 // only problem is, that we do NOT have a better range than [0,0]
879 // for each axis, because the initial chart will not have a dataset
880 // for each axis set!
881 ySection.setUpperRange(0d);
882 ySection.setLowerRange(0d);
883
884 axisSections.add(ySection);
885 }
886
887 return axisSections;
888 }
889
890
891 /**
892 * Returns the <i>settings</i> as <i>ChartSettings</i>.
893 *
894 * @return the <i>settings</i> as <i>ChartSettings</i> or null, if
895 * <i>settings</i> is not an instance of <i>ChartSettings</i>.
896 */
897 public ChartSettings getChartSettings() {
898 if (settings instanceof ChartSettings) {
899 return (ChartSettings) settings;
900 }
901
902 return null;
903 }
904
905
906 /**
907 * Returns the chart title provided by <i>settings</i>.
908 *
909 * @param settings A ChartSettings object.
910 *
911 * @return the title provided by <i>settings</i> or null if no
912 * <i>ChartSection</i> is provided by <i>settings</i>.
913 *
914 * @throws NullPointerException if <i>settings</i> is null.
915 */
916 public String getChartTitle(ChartSettings settings) {
917 ChartSection cs = settings.getChartSection();
918 return cs != null ? cs.getTitle() : null;
919 }
920
921
922 /**
923 * Returns the chart subtitle provided by <i>settings</i>.
924 *
925 * @param settings A ChartSettings object.
926 *
927 * @return the subtitle provided by <i>settings</i> or null if no
928 * <i>ChartSection</i> is provided by <i>settings</i>.
929 *
930 * @throws NullPointerException if <i>settings</i> is null.
931 */
932 public String getChartSubtitle(ChartSettings settings) {
933 ChartSection cs = settings.getChartSection();
934 return cs != null ? cs.getSubtitle() : null;
935 }
936
937
938 /**
939 * Returns a boolean object that determines if the chart grid should be
940 * visible or not. This information needs to be provided by <i>settings</i>,
941 * otherweise the default is true.
942 *
943 * @param settings A ChartSettings object.
944 *
945 * @return true, if the chart grid should be visible otherwise false.
946 *
947 * @throws NullPointerException if <i>settings</i> is null.
948 */
949 public boolean isGridVisible(ChartSettings settings) {
950 ChartSection cs = settings.getChartSection();
951 Boolean displayGrid = cs.getDisplayGrid();
952
953 return displayGrid != null ? displayGrid : true;
954 }
955
956
957 /**
958 * Returns a boolean object that determines if the chart legend should be
959 * visible or not. This information needs to be provided by <i>settings</i>,
960 * otherwise the default is true.
961 *
962 * @param settings A ChartSettings object.
963 *
964 * @return true, if the chart legend should be visible otherwise false.
965 *
966 * @throws NullPointerException if <i>settings</i> is null.
967 */
968 public boolean isLegendVisible(ChartSettings settings) {
969 LegendSection ls = settings.getLegendSection();
970 Boolean displayLegend = ls.getVisibility();
971
972 return displayLegend != null ? displayLegend : true;
973 }
974
975
976 /**
977 * Returns the legend font size specified in <i>settings</i> or null if no
978 * <i>LegendSection</i> is provided by <i>settings</i>.
979 *
980 * @param settings A ChartSettings object.
981 *
982 * @return the legend font size or null.
983 *
984 * @throws NullPointerException if <i>settings</i> is null.
985 */
986 public Integer getLegendFontSize(ChartSettings settings) {
987 LegendSection ls = settings.getLegendSection();
988 return ls != null ? ls.getFontSize() : null;
989 }
990
991
992 /**
993 * Returns the title of a chart. The return value depends on the existence
994 * of ChartSettings: if there are ChartSettings set, this method returns the
995 * chart title provided by those settings. Otherwise, this method returns
996 * getDefaultChartTitle().
997 *
998 * @return the title of a chart.
999 */
1000 protected String getChartTitle() {
1001 ChartSettings chartSettings = getChartSettings();
1002
1003 if (chartSettings != null) {
1004 return getChartTitle(chartSettings);
1005 }
1006
1007 return getDefaultChartTitle();
1008 }
1009
1010
1011 /**
1012 * Returns the subtitle of a chart. The return value depends on the
1013 * existence of ChartSettings: if there are ChartSettings set, this method
1014 * returns the chart title provided by those settings. Otherwise, this
1015 * method returns getDefaultChartSubtitle().
1016 *
1017 * @return the subtitle of a chart.
1018 */
1019 protected String getChartSubtitle() {
1020 ChartSettings chartSettings = getChartSettings();
1021
1022 if (chartSettings != null) {
1023 return getChartSubtitle(chartSettings);
1024 }
1025
1026 return getDefaultChartSubtitle();
1027 }
1028
1029
1030 /**
1031 * This method always returns null. Override it in subclasses that require
1032 * subtitles.
1033 *
1034 * @return null.
1035 */
1036 protected String getDefaultChartSubtitle() {
1037 // Override this method in subclasses
1038 return null;
1039 }
1040
1041
1042 /**
1043 * This method is used to determine, if the chart's legend is visible or
1044 * not. If a <i>settings</i> instance is set, this instance determines the
1045 * visibility otherwise, this method returns true as default if no
1046 * <i>settings</i> is set.
1047 *
1048 * @return true, if the legend should be visible, otherwise false.
1049 */
1050 protected boolean isLegendVisible() {
1051 ChartSettings chartSettings = getChartSettings();
1052 if (chartSettings != null) {
1053 return isLegendVisible(chartSettings);
1054 }
1055
1056 return true;
1057 }
1058
1059
1060 /** Where to place the logo. */
1061 protected String logoHPlace() {
1062 ChartSettings chartSettings = getChartSettings();
1063 if (chartSettings != null) {
1064 ChartSection cs = chartSettings.getChartSection();
1065 String place = cs.getLogoHPlacement();
1066
1067 return place;
1068 }
1069 return "center";
1070 }
1071
1072
1073 /** Where to place the logo. */
1074 protected String logoVPlace() {
1075 ChartSettings chartSettings = getChartSettings();
1076 if (chartSettings != null) {
1077 ChartSection cs = chartSettings.getChartSection();
1078 String place = cs.getLogoVPlacement();
1079
1080 return place;
1081 }
1082 return "top";
1083 }
1084
1085
1086 /** Return the logo id from settings. */
1087 protected String showLogo(ChartSettings chartSettings) {
1088 if (chartSettings != null) {
1089 ChartSection cs = chartSettings.getChartSection();
1090 String logo = cs.getDisplayLogo();
1091
1092 return logo;
1093 }
1094 return "none";
1095 }
1096
1097
1098 /**
1099 * This method is used to determine if a logo should be added to the plot.
1100 *
1101 * @return logo name (null if none).
1102 */
1103 protected String showLogo() {
1104 ChartSettings chartSettings = getChartSettings();
1105 return showLogo(chartSettings);
1106 }
1107
1108
1109 /**
1110 * This method is used to determine the font size of the chart's legend. If
1111 * a <i>settings</i> instance is set, this instance determines the font
1112 * size, otherwise this method returns 12 as default if no <i>settings</i>
1113 * is set or if it doesn't provide a legend font size.
1114 *
1115 * @return a legend font size.
1116 */
1117 protected int getLegendFontSize() {
1118 Integer fontSize = null;
1119
1120 ChartSettings chartSettings = getChartSettings();
1121 if (chartSettings != null) {
1122 fontSize = getLegendFontSize(chartSettings);
1123 }
1124
1125 return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
1126 }
1127
1128
1129 /**
1130 * This method is used to determine if the resulting chart should display
1131 * grid lines or not. <b>Note: this method always returns true!</b>
1132 *
1133 * @return true, if the chart should display grid lines, otherwise false.
1134 */
1135 protected boolean isGridVisible() {
1136 return true;
1137 }
1138
1139
1140 /**
1141 * Returns the X-Axis label of a chart.
1142 *
1143 * @return the X-Axis label of a chart.
1144 */
1145 protected String getXAxisLabel() {
1146 ChartSettings chartSettings = getChartSettings();
1147 if (chartSettings == null) {
1148 return getDefaultXAxisLabel();
1149 }
1150
1151 AxisSection as = chartSettings.getAxisSection("X");
1152 if (as != null) {
1153 String label = as.getLabel();
1154
1155 if (label != null) {
1156 return label;
1157 }
1158 }
1159
1160 return getDefaultXAxisLabel();
1161 }
1162
1163
1164 /**
1165 * This method returns the font size for the X axis. If the font size is
1166 * specified in ChartSettings (if <i>chartSettings</i> is set), this size is
1167 * returned. Otherwise the default font size 12 is returned.
1168 *
1169 * @return the font size for the x axis.
1170 */
1171 protected int getXAxisLabelFontSize() {
1172 ChartSettings chartSettings = getChartSettings();
1173 if (chartSettings == null) {
1174 return DEFAULT_FONT_SIZE;
1175 }
1176
1177 AxisSection as = chartSettings.getAxisSection("X");
1178 Integer fontSize = as.getFontSize();
1179
1180 return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
1181 }
1182
1183
1184 /**
1185 * This method returns the font size for an Y axis. If the font size is
1186 * specified in ChartSettings (if <i>chartSettings</i> is set), this size is
1187 * returned. Otherwise the default font size 12 is returned.
1188 *
1189 * @return the font size for the x axis.
1190 */
1191 protected int getYAxisFontSize(int pos) {
1192 ChartSettings chartSettings = getChartSettings();
1193 if (chartSettings == null) {
1194 return DEFAULT_FONT_SIZE;
1195 }
1196
1197 YAxisWalker walker = getYAxisWalker();
1198
1199 AxisSection as = chartSettings.getAxisSection(walker.getId(pos));
1200 if (as == null) {
1201 return DEFAULT_FONT_SIZE;
1202 }
1203 Integer fontSize = as.getFontSize();
1204
1205 return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
1206 }
1207
1208
1209 /**
1210 * This method returns the export dimension specified in ChartSettings as
1211 * int array [width,height].
1212 *
1213 * @return an int array with [width,height].
1214 */
1215 protected int[] getExportDimension() {
1216 ChartSettings chartSettings = getChartSettings();
1217 if (chartSettings == null) {
1218 return new int[] { 600, 400 };
1219 }
1220
1221 ExportSection export = chartSettings.getExportSection();
1222 Integer width = export.getWidth();
1223 Integer height = export.getHeight();
1224
1225 if (width != null && height != null) {
1226 return new int[] { width, height };
1227 }
1228
1229 return new int[] { 600, 400 };
1230 }
1231
1232
1233 /**
1234 * Returns the Y-Axis label of a chart at position <i>pos</i>.
1235 *
1236 * @return the Y-Axis label of a chart at position <i>0</i>.
1237 */
1238 protected String getYAxisLabel(int pos) {
1239 ChartSettings chartSettings = getChartSettings();
1240 if (chartSettings == null) {
1241 return getDefaultYAxisLabel(pos);
1242 }
1243
1244 YAxisWalker walker = getYAxisWalker();
1245 AxisSection as = chartSettings.getAxisSection(walker.getId(pos));
1246 if (as != null) {
1247 String label = as.getLabel();
1248
1249 if (label != null) {
1250 return label;
1251 }
1252 }
1253
1254 return getDefaultYAxisLabel(pos);
1255 }
1256
1257
1258 /**
1259 * This method searches for a specific axis in the <i>settings</i> if
1260 * <i>settings</i> is set. If the axis was found, this method returns the
1261 * specified axis range if the axis range is fixed. Otherwise, this method
1262 * returns null.
1263 *
1264 * @param axisId The identifier of an axis.
1265 *
1266 * @return the specified axis range from <i>settings</i> if the axis is
1267 * fixed, otherwise null.
1268 */
1269 public Range getRangeForAxisFromSettings(String axisId) {
1270 ChartSettings chartSettings = getChartSettings();
1271 if (chartSettings == null) {
1272 return null;
1273 }
1274
1275 AxisSection as = chartSettings.getAxisSection(axisId);
1276
1277 if (as == null) {
1278 return null;
1279 }
1280
1281 Boolean fixed = as.isFixed();
1282
1283 if (fixed != null && fixed) {
1284 Double upper = as.getUpperRange();
1285 Double lower = as.getLowerRange();
1286
1287 if (upper != null && lower != null) {
1288 return lower < upper
1289 ? new Range(lower, upper)
1290 : new Range(upper, lower);
1291 }
1292 }
1293
1294 return null;
1295 }
1296
1297
1298 /**
1299 * Adds a new AxisDataset which contains <i>dataset</i> at index <i>idx</i>.
1300 *
1301 * @param dataset An XYDataset.
1302 * @param idx The axis index.
1303 * @param visible Determines, if the dataset should be visible or not.
1304 */
1305 public void addAxisDataset(XYDataset dataset, int idx, boolean visible) {
1306 if (dataset == null || idx < 0) {
1307 return;
1308 }
1309
1310 AxisDataset axisDataset = getAxisDataset(idx);
1311
1312 Bounds[] xyBounds = ChartHelper.getBounds(dataset);
1313
1314 if (xyBounds == null) {
1315 logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
1316 return;
1317 }
1318
1319 if (visible) {
1320 if (logger.isDebugEnabled()) {
1321 logger.debug("Add new AxisDataset at index: " + idx);
1322 logger.debug("X extent: " + xyBounds[0]);
1323 logger.debug("Y extent: " + xyBounds[1]);
1324 }
1325
1326 axisDataset.addDataset(dataset);
1327 }
1328
1329 combineXBounds(xyBounds[0], 0);
1330 combineYBounds(xyBounds[1], idx);
1331 }
1332
1333
1334 /**
1335 * This method grants access to the AxisDatasets stored in <i>datasets</i>.
1336 * If no AxisDataset exists for index <i>idx</i>, a new AxisDataset is
1337 * created using <i>createAxisDataset()</i>.
1338 *
1339 * @param idx The index of the desired AxisDataset.
1340 *
1341 * @return an existing or new AxisDataset.
1342 */
1343 public AxisDataset getAxisDataset(int idx) {
1344 AxisDataset axisDataset = datasets.get(idx);
1345
1346 if (axisDataset == null) {
1347 axisDataset = createAxisDataset(idx);
1348 datasets.put(idx, axisDataset);
1349 }
1350
1351 return axisDataset;
1352 }
1353
1354
1355 /**
1356 * Adjust some Stroke/Grid parameters for <i>plot</i>. The chart
1357 * <i>Settings</i> are applied in this method.
1358 *
1359 * @param plot The XYPlot which is adapted.
1360 */
1361 protected void adjustPlot(XYPlot plot) {
1362 Stroke gridStroke = new BasicStroke(
1363 DEFAULT_GRID_LINE_WIDTH,
1364 BasicStroke.CAP_BUTT,
1365 BasicStroke.JOIN_MITER,
1366 3.0f,
1367 new float[] { 3.0f },
1368 0.0f);
1369
1370 ChartSettings cs = getChartSettings();
1371 boolean isGridVisible = cs != null ? isGridVisible(cs) : true;
1372
1373 plot.setDomainGridlineStroke(gridStroke);
1374 plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR);
1375 plot.setDomainGridlinesVisible(isGridVisible);
1376
1377 plot.setRangeGridlineStroke(gridStroke);
1378 plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR);
1379 plot.setRangeGridlinesVisible(isGridVisible);
1380
1381 plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));
1382 }
1383
1384
1385 /**
1386 * This helper mehtod is used to extract the current locale from instance
1387 * vairable <i>context</i>.
1388 *
1389 * @return the current locale.
1390 */
1391 protected Locale getLocale() {
1392 CallMeta meta = context.getMeta();
1393 PreferredLocale[] prefs = meta.getLanguages();
1394
1395 int len = prefs != null ? prefs.length : 0;
1396
1397 Locale[] locales = new Locale[len];
1398
1399 for (int i = 0; i < len; i++) {
1400 locales[i] = prefs[i].getLocale();
1401 }
1402
1403 return meta.getPreferredLocale(locales);
1404 }
1405
1406
1407 /**
1408 * Look up \param key in i18n dictionary.
1409 * @param key key for which to find i18nd version.
1410 * @param def default, returned if lookup failed.
1411 * @return value found in i18n dictionary, \param def if no value found.
1412 */
1413 protected String msg(String key, String def) {
1414 return Resources.getMsg(context.getMeta(), key, def);
1415 }
1416
1417 /**
1418 * Look up \param key in i18n dictionary.
1419 * @param key key for which to find i18nd version.
1420 * @return value found in i18n dictionary, key itself if failed.
1421 */
1422 protected String msg(String key) {
1423 return Resources.getMsg(context.getMeta(), key, key);
1424 }
1425
1426 protected String msg(String key, String def, Object[] args) {
1427 return Resources.getMsg(context.getMeta(), key, def, args);
1428 }
1429
1430
1431 protected String getRiverName() {
1432 D4EArtifact flys = (D4EArtifact) master;
1433
1434 River river = RiverUtils.getRiver(flys);
1435 return (river != null) ? river.getName() : "";
1436 }
1437
1438
1439 protected double[] getRange() {
1440 D4EArtifact flys = (D4EArtifact) master;
1441
1442 RangeAccess rangeAccess = new RangeAccess(flys);
1443 return rangeAccess.getKmRange();
1444 }
1445
1446
1447 /**
1448 * Returns the size of a chart export as array which has been specified by
1449 * the incoming request document.
1450 *
1451 * @return the size of a chart as [width, height] or null if no width or
1452 * height are given in the request document.
1453 */
1454 protected int[] getSize() {
1455 int[] size = new int[2];
1456
1457 Element sizeEl = (Element)XMLUtils.xpath(
1458 request,
1459 XPATH_CHART_SIZE,
1460 XPathConstants.NODE,
1461 ArtifactNamespaceContext.INSTANCE);
1462
1463 if (sizeEl != null) {
1464 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
1465
1466 String w = sizeEl.getAttributeNS(uri, "width");
1467 String h = sizeEl.getAttributeNS(uri, "height");
1468
1469 if (w.length() > 0 && h.length() > 0) {
1470 try {
1471 size[0] = Integer.parseInt(w);
1472 size[1] = Integer.parseInt(h);
1473 }
1474 catch (NumberFormatException nfe) {
1475 logger.warn("Wrong values for chart width/height.");
1476 }
1477 }
1478 }
1479
1480 return size[0] > 0 && size[1] > 0 ? size : null;
1481 }
1482
1483
1484 /**
1485 * This method returns the format specified in the <i>request</i> document
1486 * or <i>DEFAULT_CHART_FORMAT</i> if no format is specified in
1487 * <i>request</i>.
1488 *
1489 * @return the format used to export this chart.
1490 */
1491 protected String getFormat() {
1492 String format = (String) XMLUtils.xpath(
1493 request,
1494 XPATH_CHART_FORMAT,
1495 XPathConstants.STRING,
1496 ArtifactNamespaceContext.INSTANCE);
1497
1498 return format == null || format.length() == 0
1499 ? DEFAULT_CHART_FORMAT
1500 : format;
1501 }
1502
1503
1504 /**
1505 * Returns the X-Axis range as String array from request document.
1506 * If the (x|y)range elements are not found in request document, return
1507 * null (i.e. not zoomed).
1508 *
1509 * @return a String array with [lower, upper], null if not in document.
1510 */
1511 protected String[] getDomainAxisRangeFromRequest() {
1512 Element xrange = (Element)XMLUtils.xpath(
1513 request,
1514 XPATH_CHART_X_RANGE,
1515 XPathConstants.NODE,
1516 ArtifactNamespaceContext.INSTANCE);
1517
1518 if (xrange == null) {
1519 return null;
1520 }
1521
1522 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
1523
1524 String lower = xrange.getAttributeNS(uri, "from");
1525 String upper = xrange.getAttributeNS(uri, "to");
1526
1527 return new String[] { lower, upper };
1528 }
1529
1530
1531 /** Returns null if the (x|y)range-element was not found in request document.
1532 * This usally means that the axis are not manually zoomed, i.e. showing
1533 * full data extent. */
1534 protected String[] getValueAxisRangeFromRequest() {
1535 Element yrange = (Element)XMLUtils.xpath(
1536 request,
1537 XPATH_CHART_Y_RANGE,
1538 XPathConstants.NODE,
1539 ArtifactNamespaceContext.INSTANCE);
1540
1541 if (yrange == null) {
1542 return null;
1543 }
1544
1545
1546 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
1547
1548 String lower = yrange.getAttributeNS(uri, "from");
1549 String upper = yrange.getAttributeNS(uri, "to");
1550
1551 return new String[] { lower, upper };
1552 }
1553
1554
1555 /**
1556 * Returns the default size of a chart export as array.
1557 *
1558 * @return the default size of a chart as [width, height].
1559 */
1560 protected int[] getDefaultSize() {
1561 return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT };
1562 }
1563
1564
1565 /**
1566 * Add datasets stored in instance variable <i>datasets</i> to plot.
1567 * <i>datasets</i> actually stores instances of AxisDataset, so each of this
1568 * datasets is mapped to a specific axis as well.
1569 *
1570 * @param plot plot to add datasets to.
1571 */
1572 protected void addDatasets(XYPlot plot) {
1573 logger.debug("addDatasets()");
1574
1575 // AxisDatasets are sorted, but some might be empty.
1576 // Thus, generate numbering on the fly.
1577 int axisIndex = 0;
1578 int datasetIndex = 0;
1579
1580 for (Map.Entry<Integer, AxisDataset> entry: datasets.entrySet()) {
1581 if (!entry.getValue().isEmpty()) {
1582 // Add axis and range information.
1583 AxisDataset axisDataset = entry.getValue();
1584 NumberAxis axis = createYAxis(entry.getKey());
1585
1586 plot.setRangeAxis(axisIndex, axis);
1587
1588 if (axis.getAutoRangeIncludesZero()) {
1589 axisDataset.setRange(
1590 Range.expandToInclude(axisDataset.getRange(), 0d));
1591 }
1592
1593 setYBounds(axisIndex, expandPointRange(axisDataset.getRange()));
1594
1595 // Add contained datasets, mapping to axis.
1596 for (XYDataset dataset: axisDataset.getDatasets()) {
1597 plot.setDataset(datasetIndex, dataset);
1598 plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
1599
1600 applyThemes(plot, dataset,
1601 datasetIndex,
1602 axisDataset.isArea(dataset));
1603
1604 datasetIndex++;
1605 }
1606
1607 axisDataset.setPlotAxisIndex(axisIndex);
1608 axisIndex++;
1609 }
1610 }
1611 }
1612
1613
1614 /**
1615 * @param idx "index" of dataset/series (first dataset to be drawn has
1616 * index 0), correlates with renderer index.
1617 * @param isArea true if the series describes an area and shall be rendered
1618 * as such.
1619 */
1620 protected void applyThemes(
1621 XYPlot plot,
1622 XYDataset series,
1623 int idx,
1624 boolean isArea
1625 ) {
1626 if (isArea) {
1627 applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx);
1628 }
1629 else {
1630 applyLineTheme(plot, series, idx);
1631 }
1632 }
1633
1634
1635 /**
1636 * This method applies the themes defined in the series itself. Therefore,
1637 * <i>StyledXYSeries.applyTheme()</i> is called, which modifies the renderer
1638 * for the series.
1639 *
1640 * @param plot The plot.
1641 * @param dataset The XYDataset which needs to support Series objects.
1642 * @param idx The index of the renderer / dataset.
1643 */
1644 protected void applyLineTheme(XYPlot plot, XYDataset dataset, int idx) {
1645 logger.debug("Apply LineTheme for dataset at index: " + idx);
1646
1647 LegendItemCollection lic = new LegendItemCollection();
1648 LegendItemCollection anno = plot.getFixedLegendItems();
1649
1650 Font legendFont = createLegendLabelFont();
1651
1652 XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
1653
1654 for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
1655 Series series = getSeriesOf(dataset, s);
1656
1657 if (series instanceof StyledSeries) {
1658 Style style = ((StyledSeries) series).getStyle();
1659 style.applyTheme(renderer, s);
1660 }
1661
1662 // special case: if there is just one single item, we need to enable
1663 // points for this series, otherwise we would not see anything in
1664 // the chart area.
1665 if (series.getItemCount() == 1) {
1666 renderer.setSeriesShapesVisible(s, true);
1667 }
1668
1669 LegendItem legendItem = renderer.getLegendItem(idx, s);
1670 if (legendItem.getLabel().endsWith(" ") ||
1671 legendItem.getLabel().endsWith("interpol")) {
1672 legendItem = null;
1673 }
1674
1675 if (legendItem != null) {
1676 legendItem.setLabelFont(legendFont);
1677 lic.add(legendItem);
1678 }
1679 else {
1680 logger.warn("Could not get LegentItem for renderer: "
1681 + idx + ", series-idx " + s);
1682 }
1683 }
1684
1685 if (anno != null) {
1686 lic.addAll(anno);
1687 }
1688
1689 plot.setFixedLegendItems(lic);
1690
1691 plot.setRenderer(idx, renderer);
1692 }
1693
1694
1695 /**
1696 * @param plot The plot.
1697 * @param area A StyledAreaSeriesCollection object.
1698 * @param idx The index of the dataset.
1699 */
1700 protected void applyAreaTheme(
1701 XYPlot plot,
1702 StyledAreaSeriesCollection area,
1703 int idx
1704 ) {
1705 LegendItemCollection lic = new LegendItemCollection();
1706 LegendItemCollection anno = plot.getFixedLegendItems();
1707
1708 Font legendFont = createLegendLabelFont();
1709
1710 logger.debug("Registering an 'area'renderer at idx: " + idx);
1711
1712 StableXYDifferenceRenderer dRenderer =
1713 new StableXYDifferenceRenderer();
1714
1715 if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
1716 dRenderer.setPositivePaint(createTransparentPaint());
1717 }
1718
1719 plot.setRenderer(idx, dRenderer);
1720
1721 area.applyTheme(dRenderer);
1722
1723 // i18n
1724 dRenderer.setAreaLabelNumberFormat(Formatter.getFormatter(context.getMeta(), 2, 4));
1725
1726 dRenderer.setAreaLabelTemplate(Resources.getMsg(
1727 context.getMeta(), "area.label.template", "Area=%sm2"));
1728
1729 LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
1730 if (legendItem != null) {
1731 legendItem.setLabelFont(legendFont);
1732 lic.add(legendItem);
1733 }
1734 else {
1735 logger.warn("Could not get LegentItem for renderer: "
1736 + idx + ", series-idx " + 0);
1737 }
1738
1739 if (anno != null) {
1740 lic.addAll(anno);
1741 }
1742
1743 plot.setFixedLegendItems(lic);
1744 }
1745
1746
1747 /**
1748 * Expands a given range if it collapses into one point.
1749 *
1750 * @param range Range to be expanded if upper == lower bound.
1751 *
1752 * @return Bounds of point plus 5 percent in each direction.
1753 */
1754 private Bounds expandPointRange(Range range) {
1755 if (range == null) {
1756 return null;
1757 }
1758 else if (range.getLowerBound() == range.getUpperBound()) {
1759 Range expandedRange = ChartHelper.expandRange(range, 5d);
1760 return new DoubleBounds(expandedRange.getLowerBound(), expandedRange.getUpperBound());
1761 }
1762
1763 return new DoubleBounds(range.getLowerBound(), range.getUpperBound());
1764 }
1765
1766
1767 /**
1768 * Creates a new instance of EnhancedLineAndShapeRenderer.
1769 *
1770 * @param plot The plot which is set for the new renderer.
1771 * @param idx This value is not used in the current implementation.
1772 *
1773 * @return a new instance of EnhancedLineAndShapeRenderer.
1774 */
1775 protected XYLineAndShapeRenderer createRenderer(XYPlot plot, int idx) {
1776 logger.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx);
1777
1778 EnhancedLineAndShapeRenderer r =
1779 new EnhancedLineAndShapeRenderer(true, false);
1780
1781 r.setPlot(plot);
1782
1783 return r;
1784 }
1785
1786
1787 /**
1788 * Creates a new instance of <i>IdentifiableNumberAxis</i>.
1789 *
1790 * @param idx The index of the new axis.
1791 * @param label The label of the new axis.
1792 *
1793 * @return an instance of IdentifiableNumberAxis.
1794 */
1795 protected NumberAxis createNumberAxis(int idx, String label) {
1796 YAxisWalker walker = getYAxisWalker();
1797
1798 return new IdentifiableNumberAxis(walker.getId(idx), label);
1799 }
1800
1801
1802 /**
1803 * Create Y (range) axis for given index.
1804 * Shall be overriden by subclasses.
1805 */
1806 protected NumberAxis createYAxis(int index) {
1807 YAxisWalker walker = getYAxisWalker();
1808
1809 Font labelFont = new Font(
1810 DEFAULT_FONT_NAME,
1811 Font.BOLD,
1812 getYAxisFontSize(index));
1813
1814 IdentifiableNumberAxis axis = new IdentifiableNumberAxis(
1815 walker.getId(index),
1816 getYAxisLabel(index));
1817
1818 axis.setAutoRangeIncludesZero(false);
1819 axis.setLabelFont(labelFont);
1820 axis.setTickLabelFont(labelFont);
1821
1822 return axis;
1823 }
1824
1825
1826 /**
1827 * Creates a new LegendItem with <i>name</i> and font provided by
1828 * <i>createLegendLabelFont()</i>.
1829 *
1830 * @param theme The theme of the chart line.
1831 * @param name The displayed name of the item.
1832 *
1833 * @return a new LegendItem instance.
1834 */
1835 public LegendItem createLegendItem(ThemeDocument theme, String name) {
1836 // OPTIMIZE Pass font, parsed Theme items.
1837
1838 Color color = theme.parseLineColorField();
1839 if (color == null) {
1840 color = Color.BLACK;
1841 }
1842
1843 LegendItem legendItem = new LegendItem(name, color);
1844
1845 legendItem.setLabelFont(createLegendLabelFont());
1846 return legendItem;
1847 }
1848
1849
1850 /**
1851 * Creates Font (Family and size) to use when creating Legend Items. The
1852 * font size depends in the return value of <i>getLegendFontSize()</i>.
1853 *
1854 * @return a new Font instance with <i>DEFAULT_FONT_NAME</i>.
1855 */
1856 protected Font createLegendLabelFont() {
1857 return new Font(
1858 DEFAULT_FONT_NAME,
1859 Font.PLAIN,
1860 getLegendFontSize()
1861 );
1862 }
1863
1864
1865 /**
1866 * Create new legend entries, dependent on settings.
1867 * @param plot The plot for which to modify the legend.
1868 */
1869 public void aggregateLegendEntries(XYPlot plot) {
1870 int AGGR_THRESHOLD = 0;
1871
1872 if (getChartSettings() == null) {
1873 return;
1874 }
1875 Integer threshold = getChartSettings().getLegendSection()
1876 .getAggregationThreshold();
1877
1878 AGGR_THRESHOLD = (threshold != null) ? threshold.intValue() : 0;
1879
1880 LegendProcessor.aggregateLegendEntries(plot, AGGR_THRESHOLD);
1881 }
1882
1883
1884 /**
1885 * Returns a transparently textured paint.
1886 *
1887 * @return a transparently textured paint.
1888 */
1889 protected static Paint createTransparentPaint() {
1890 // TODO why not use a transparent color?
1891 BufferedImage texture = new BufferedImage(
1892 1, 1, BufferedImage.TYPE_4BYTE_ABGR);
1893
1894 return new TexturePaint(
1895 texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
1896 }
1897
1898
1899 protected void preparePDFContext(CallContext context) {
1900 int[] dimension = getExportDimension();
1901
1902 context.putContextValue("chart.width", dimension[0]);
1903 context.putContextValue("chart.height", dimension[1]);
1904 context.putContextValue("chart.marginLeft", 5f);
1905 context.putContextValue("chart.marginRight", 5f);
1906 context.putContextValue("chart.marginTop", 5f);
1907 context.putContextValue("chart.marginBottom", 5f);
1908 context.putContextValue(
1909 "chart.page.format",
1910 ChartExportHelper.DEFAULT_PAGE_SIZE);
1911 }
1912
1913
1914 protected void prepareSVGContext(CallContext context) {
1915 int[] dimension = getExportDimension();
1916
1917 context.putContextValue("chart.width", dimension[0]);
1918 context.putContextValue("chart.height", dimension[1]);
1919 context.putContextValue(
1920 "chart.encoding",
1921 ChartExportHelper.DEFAULT_ENCODING);
1922 }
1923
1924 /**
1925 * Retuns the call context. May be null if init hasn't been called yet.
1926 *
1927 * @return the CallContext instance
1928 */
1929 public CallContext getCallContext() {
1930 return context;
1931 }
1932 }

http://dive4elements.wald.intevation.org