comparison flys-artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator.java @ 5831:bd047b71ab37

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

http://dive4elements.wald.intevation.org