comparison artifacts/src/main/java/org/dive4elements/river/exports/ChartGenerator2.java @ 7116:3c7471b929d1

Merge branch generator-refectoring into default.
author Andre Heinecke <aheinecke@intevation.de>
date Tue, 24 Sep 2013 12:29:44 +0200
parents 41567bf1e131
children 8c20f16ab650
comparison
equal deleted inserted replaced
7095:bdadffad35b1 7116:3c7471b929d1
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 java.awt.BasicStroke;
12 import java.awt.Color;
13 import java.awt.Font;
14 import java.awt.Graphics2D;
15 import java.awt.Paint;
16 import java.awt.Stroke;
17 import java.awt.TexturePaint;
18 import java.awt.Transparency;
19
20 import java.awt.geom.Rectangle2D;
21
22 import java.awt.image.BufferedImage;
23
24 import java.io.IOException;
25 import java.io.OutputStream;
26
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.SortedMap;
32 import java.util.TreeMap;
33
34 import javax.xml.xpath.XPathConstants;
35
36 import org.apache.log4j.Logger;
37
38 import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
39 import org.dive4elements.artifactdatabase.state.Settings;
40
41 import org.dive4elements.artifacts.Artifact;
42 import org.dive4elements.artifacts.ArtifactNamespaceContext;
43 import org.dive4elements.artifacts.CallContext;
44 import org.dive4elements.artifacts.CallMeta;
45 import org.dive4elements.artifacts.PreferredLocale;
46
47 import org.dive4elements.artifacts.common.utils.XMLUtils;
48
49 import org.dive4elements.river.artifacts.D4EArtifact;
50
51 import org.dive4elements.river.artifacts.access.RangeAccess;
52
53 import org.dive4elements.river.artifacts.resources.Resources;
54
55 import org.dive4elements.river.collections.D4EArtifactCollection;
56
57 import org.dive4elements.river.java2d.NOPGraphics2D;
58
59 import org.dive4elements.river.jfree.AxisDataset;
60 import org.dive4elements.river.jfree.Bounds;
61 import org.dive4elements.river.jfree.DoubleBounds;
62 import org.dive4elements.river.jfree.EnhancedLineAndShapeRenderer;
63 import org.dive4elements.river.jfree.RiverAnnotation;
64 import org.dive4elements.river.jfree.StableXYDifferenceRenderer;
65 import org.dive4elements.river.jfree.Style;
66 import org.dive4elements.river.jfree.StyledAreaSeriesCollection;
67 import org.dive4elements.river.jfree.StyledSeries;
68
69 import org.dive4elements.river.model.River;
70
71 import org.dive4elements.river.themes.ThemeDocument;
72
73 import org.dive4elements.river.utils.Formatter;
74 import org.dive4elements.river.utils.RiverUtils;
75
76 import org.jfree.chart.ChartRenderingInfo;
77 import org.jfree.chart.JFreeChart;
78 import org.jfree.chart.LegendItem;
79 import org.jfree.chart.LegendItemCollection;
80
81 import org.jfree.chart.axis.NumberAxis;
82
83 import org.jfree.chart.plot.XYPlot;
84
85 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
86
87 import org.jfree.chart.title.TextTitle;
88
89 import org.jfree.data.Range;
90
91 import org.jfree.data.general.Series;
92
93 import org.jfree.data.xy.XYDataset;
94
95 import org.jfree.ui.RectangleInsets;
96
97 import org.w3c.dom.Document;
98 import org.w3c.dom.Element;
99
100 /**
101 * Implementation of the OutGenerator interface for charts.
102 * It should provide some basic things that equal in all chart types.
103 *
104 */
105 public abstract class ChartGenerator2 implements OutGenerator {
106
107 private static Logger logger = Logger.getLogger(ChartGenerator2.class);
108
109 public static final boolean USE_NOP_GRAPHICS =
110 Boolean.getBoolean("info.rendering.nop.graphics");
111
112
113 public static final int DEFAULT_CHART_WIDTH = 600;
114 public static final int DEFAULT_CHART_HEIGHT = 400;
115 public static final String DEFAULT_CHART_FORMAT = "png";
116 public static final Color DEFAULT_GRID_COLOR = Color.GRAY;
117 public static final float DEFAULT_GRID_LINE_WIDTH = 0.3f;
118 public static final int DEFAULT_FONT_SIZE = 12;
119 public static final String DEFAULT_FONT_NAME = "Tahoma";
120
121
122 public static final String XPATH_CHART_SIZE =
123 "/art:action/art:attributes/art:size";
124
125 public static final String XPATH_CHART_FORMAT =
126 "/art:action/art:attributes/art:format/@art:value";
127
128 public static final String XPATH_CHART_X_RANGE =
129 "/art:action/art:attributes/art:xrange";
130
131 public static final String XPATH_CHART_Y_RANGE =
132 "/art:action/art:attributes/art:yrange";
133
134
135 /** The document of the incoming out() request.*/
136 protected Document request;
137
138 /** The output stream where the data should be written to.*/
139 protected OutputStream out;
140
141 /** The CallContext object.*/
142 protected CallContext context;
143
144 protected D4EArtifactCollection collection;
145
146 /** The artifact that is used to decorate the chart with meta information.*/
147 protected Artifact master;
148
149 /** The settings that should be used during output creation.*/
150 protected Settings settings;
151
152 /** Map of datasets ("index"). */
153 protected SortedMap<Integer, AxisDataset> datasets;
154
155 /** List of annotations to insert in plot. */
156 protected List<RiverAnnotation> annotations = new ArrayList<RiverAnnotation>();
157
158 protected abstract List<AxisSection> buildYAxisSections();
159
160 protected String outName;
161
162 /**
163 * Default constructor that initializes internal data structures.
164 */
165 public ChartGenerator2() {
166 datasets = new TreeMap<Integer, AxisDataset>();
167 }
168
169 /**
170 * Adds annotations to list. The given annotation will be visible.
171 */
172 public void addAnnotations(RiverAnnotation annotation) {
173 annotations.add(annotation);
174 }
175
176 /**
177 * This method needs to be implemented by concrete subclasses to create new
178 * instances of JFreeChart.
179 *
180 * @return a new instance of a JFreeChart.
181 */
182 public abstract JFreeChart generateChart();
183
184
185 /** For every outable (i.e. facets), this function is
186 * called and handles the data accordingly. */
187 @Override
188 public abstract void doOut(
189 ArtifactAndFacet bundle,
190 ThemeDocument attr,
191 boolean visible);
192
193
194
195 protected abstract Series getSeriesOf(XYDataset dataset, int idx);
196
197 /**
198 * Returns the default title of a chart.
199 *
200 * @return the default title of a chart.
201 */
202 protected abstract String getDefaultChartTitle();
203
204 protected abstract String getDefaultYAxisLabel(String axisName);
205
206
207 /**
208 * Returns the default X-Axis label of a chart.
209 *
210 * @return the default X-Axis label of a chart.
211 */
212 protected abstract String getDefaultXAxisLabel();
213
214 /**
215 * This method is used to create new AxisDataset instances which may differ
216 * in concrete subclasses.
217 *
218 * @param idx The index of an axis.
219 */
220 protected abstract AxisDataset createAxisDataset(int idx);
221
222
223 /**
224 * Combines the ranges of the X axis at index <i>idx</i>.
225 *
226 * @param bounds A new Bounds.
227 * @param idx The index of the X axis that should be comined with
228 * <i>range</i>.
229 */
230 protected abstract void combineXBounds(Bounds bounds, int idx);
231
232
233 /**
234 * Combines the ranges of the Y axis at index <i>idx</i>.
235 *
236 * @param bounds A new Bounds.
237 * @param index The index of the Y axis that should be comined with.
238 * <i>range</i>.
239 */
240 protected abstract void combineYBounds(Bounds bounds, int index);
241
242
243 /**
244 * This method is used to determine the ranges for axes at a given index.
245 *
246 * @param index The index of the axes at the plot.
247 *
248 * @return a Range[] with [xrange, yrange];
249 */
250 public abstract Range[] getRangesForAxis(int index);
251
252 public abstract Bounds getXBounds(int axis);
253
254 protected abstract void setXBounds(int axis, Bounds bounds);
255
256 public abstract Bounds getYBounds(int axis);
257
258 protected abstract void setYBounds(int axis, Bounds bounds);
259
260
261 /**
262 * This method retrieves the chart subtitle by calling getChartSubtitle()
263 * and adds it as TextTitle to the chart.
264 * The default implementation of getChartSubtitle() returns the same
265 * as getDefaultChartSubtitle() which must be implemented by derived
266 * classes. If you want to add multiple subtitles to the chart override
267 * this method and add your subtitles manually.
268 *
269 * @param chart The JFreeChart chart object.
270 */
271 protected void addSubtitles(JFreeChart chart) {
272 String subtitle = getChartSubtitle();
273
274 if (subtitle != null && subtitle.length() > 0) {
275 chart.addSubtitle(new TextTitle(subtitle));
276 }
277 }
278
279 /**
280 * Generate chart.
281 */
282 @Override
283 public void generate() throws IOException {
284
285 logger.debug("ChartGenerator2.generate");
286
287 if (outName.indexOf("chartinfo") > 0) {
288 generateInfo();
289 }
290 else {
291 generateImage();
292 }
293 }
294
295
296 /** Generate only meta infos */
297 private void generateInfo() throws IOException {
298
299 logger.debug("ChartInfoGenerator2.generateInfo");
300
301 JFreeChart chart = generateChart();
302
303 int[] size = getSize();
304 if (size == null) {
305 size = getDefaultSize();
306 }
307
308 ChartRenderingInfo info = new ChartRenderingInfo();
309
310 long startTime = System.currentTimeMillis();
311
312 if (USE_NOP_GRAPHICS) {
313 BufferedImage image =
314 new BufferedImage(size[0], size[1], Transparency.BITMASK);
315
316 Graphics2D g2d = image.createGraphics();
317 Graphics2D nop = new NOPGraphics2D(g2d);
318
319 chart.draw(
320 nop,
321 new Rectangle2D.Double(0, 0, size[0], size[1]),
322 null,
323 info);
324
325 nop.dispose();
326 }
327 else {
328 chart.createBufferedImage(
329 size[0], size[1], Transparency.BITMASK, info);
330 }
331
332 long stopTime = System.currentTimeMillis();
333
334 if (logger.isDebugEnabled()) {
335 logger.debug("Rendering info took: " +
336 (stopTime-startTime) + "ms");
337 }
338
339
340 InfoGeneratorHelper2 helper = new InfoGeneratorHelper2(this);
341 Document doc = helper.createInfoDocument(chart, info);
342
343 XMLUtils.toStream(doc, out);
344 }
345
346 /** Generate the diagram as an image. */
347 private void generateImage() throws IOException {
348 logger.debug("ChartGenerator2.generateImage");
349
350 JFreeChart chart = generateChart();
351
352 String format = getFormat();
353 int[] size = getSize();
354
355 if (size == null) {
356 size = getExportDimension();
357 }
358
359 context.putContextValue("chart.width", size[0]);
360 context.putContextValue("chart.height", size[1]);
361
362 if (format.equals(ChartExportHelper.FORMAT_PNG)) {
363 context.putContextValue("chart.image.format", "png");
364
365 ChartExportHelper.exportImage(
366 out,
367 chart,
368 context);
369 }
370 else if (format.equals(ChartExportHelper.FORMAT_PDF)) {
371 preparePDFContext(context);
372
373 ChartExportHelper.exportPDF(
374 out,
375 chart,
376 context);
377 }
378 else if (format.equals(ChartExportHelper.FORMAT_SVG)) {
379 prepareSVGContext(context);
380
381 ChartExportHelper.exportSVG(
382 out,
383 chart,
384 context);
385 }
386 else if (format.equals(ChartExportHelper.FORMAT_CSV)) {
387 context.putContextValue("chart.image.format", "csv");
388
389 ChartExportHelper.exportCSV(
390 out,
391 chart,
392 context);
393 }
394 }
395
396
397 @Override
398 public void init(String outName, Document request, OutputStream out, CallContext context) {
399 logger.debug("ChartGenerator2.init");
400
401 this.outName = outName;
402 this.request = request;
403 this.out = out;
404 this.context = context;
405 }
406
407
408 /** Sets the master artifact. */
409 @Override
410 public void setMasterArtifact(Artifact master) {
411 this.master = master;
412 }
413
414
415 /**
416 * Gets the master artifact.
417 * @return the master artifact.
418 */
419 public Artifact getMaster() {
420 return master;
421 }
422
423
424 /** Sets the collection. */
425 @Override
426 public void setCollection(D4EArtifactCollection collection) {
427 this.collection = collection;
428 }
429
430
431 @Override
432 public void setSettings(Settings settings) {
433 this.settings = settings;
434 }
435
436
437 /**
438 * Returns an instance of <i>ChartSettings</i> with a chart specific section
439 * but with no axes settings.
440 *
441 * @return an instance of <i>ChartSettings</i>.
442 */
443 @Override
444 public Settings getSettings() {
445 if (this.settings != null) {
446 return this.settings;
447 }
448
449 ChartSettings settings = new ChartSettings();
450
451 ChartSection chartSection = buildChartSection();
452 LegendSection legendSection = buildLegendSection();
453 ExportSection exportSection = buildExportSection();
454
455 settings.setChartSection(chartSection);
456 settings.setLegendSection(legendSection);
457 settings.setExportSection(exportSection);
458
459 List<AxisSection> axisSections = buildAxisSections();
460 for (AxisSection axisSection: axisSections) {
461 settings.addAxisSection(axisSection);
462 }
463
464 return settings;
465 }
466
467
468 /**
469 * Creates a new <i>ChartSection</i>.
470 *
471 * @return a new <i>ChartSection</i>.
472 */
473 protected ChartSection buildChartSection() {
474 ChartSection chartSection = new ChartSection();
475 chartSection.setTitle(getChartTitle());
476 chartSection.setSubtitle(getChartSubtitle());
477 chartSection.setDisplayGrid(isGridVisible());
478 chartSection.setDisplayLogo(showLogo());
479 chartSection.setLogoVPlacement(logoVPlace());
480 chartSection.setLogoHPlacement(logoHPlace());
481 return chartSection;
482 }
483
484
485 /**
486 * Creates a new <i>LegendSection</i>.
487 *
488 * @return a new <i>LegendSection</i>.
489 */
490 protected LegendSection buildLegendSection() {
491 LegendSection legendSection = new LegendSection();
492 legendSection.setVisibility(isLegendVisible());
493 legendSection.setFontSize(getLegendFontSize());
494 legendSection.setAggregationThreshold(10);
495 return legendSection;
496 }
497
498
499 /**
500 * Creates a new <i>ExportSection</i> with default values <b>WIDTH=600</b>
501 * and <b>HEIGHT=400</b>.
502 *
503 * @return a new <i>ExportSection</i>.
504 */
505 protected ExportSection buildExportSection() {
506 ExportSection exportSection = new ExportSection();
507 exportSection.setWidth(600);
508 exportSection.setHeight(400);
509 return exportSection;
510 }
511
512
513 /**
514 * Creates a list of Sections that contains all axes of the chart (including
515 * X and Y axes).
516 *
517 * @return a list of Sections for each axis in this chart.
518 */
519 protected List<AxisSection> buildAxisSections() {
520 List<AxisSection> axisSections = new ArrayList<AxisSection>();
521
522 axisSections.addAll(buildXAxisSections());
523 axisSections.addAll(buildYAxisSections());
524
525 return axisSections;
526 }
527
528
529 /**
530 * Creates a new Section for chart's X axis.
531 *
532 * @return a List that contains a Section for the X axis.
533 */
534 protected List<AxisSection> buildXAxisSections() {
535 List<AxisSection> axisSections = new ArrayList<AxisSection>();
536
537 String identifier = "X";
538
539 AxisSection axisSection = new AxisSection();
540 axisSection.setIdentifier(identifier);
541 axisSection.setLabel(getXAxisLabel());
542 axisSection.setFontSize(14);
543 axisSection.setFixed(false);
544
545 // XXX We are able to find better default ranges that [0,0], but the Y
546 // axes currently have no better ranges set.
547 axisSection.setUpperRange(0d);
548 axisSection.setLowerRange(0d);
549
550 axisSections.add(axisSection);
551
552 return axisSections;
553 }
554
555
556 /**
557 * Returns the <i>settings</i> as <i>ChartSettings</i>.
558 *
559 * @return the <i>settings</i> as <i>ChartSettings</i> or null, if
560 * <i>settings</i> is not an instance of <i>ChartSettings</i>.
561 */
562 public ChartSettings getChartSettings() {
563 if (settings instanceof ChartSettings) {
564 return (ChartSettings) settings;
565 }
566
567 return null;
568 }
569
570
571 /**
572 * Returns the chart title provided by <i>settings</i>.
573 *
574 * @param settings A ChartSettings object.
575 *
576 * @return the title provided by <i>settings</i> or null if no
577 * <i>ChartSection</i> is provided by <i>settings</i>.
578 *
579 * @throws NullPointerException if <i>settings</i> is null.
580 */
581 public String getChartTitle(ChartSettings settings) {
582 ChartSection cs = settings.getChartSection();
583 return cs != null ? cs.getTitle() : null;
584 }
585
586
587 /**
588 * Returns the chart subtitle provided by <i>settings</i>.
589 *
590 * @param settings A ChartSettings object.
591 *
592 * @return the subtitle provided by <i>settings</i> or null if no
593 * <i>ChartSection</i> is provided by <i>settings</i>.
594 *
595 * @throws NullPointerException if <i>settings</i> is null.
596 */
597 public String getChartSubtitle(ChartSettings settings) {
598 ChartSection cs = settings.getChartSection();
599 return cs != null ? cs.getSubtitle() : null;
600 }
601
602
603 /**
604 * Returns a boolean object that determines if the chart grid should be
605 * visible or not. This information needs to be provided by <i>settings</i>,
606 * otherweise the default is true.
607 *
608 * @param settings A ChartSettings object.
609 *
610 * @return true, if the chart grid should be visible otherwise false.
611 *
612 * @throws NullPointerException if <i>settings</i> is null.
613 */
614 public boolean isGridVisible(ChartSettings settings) {
615 ChartSection cs = settings.getChartSection();
616 Boolean displayGrid = cs.getDisplayGrid();
617
618 return displayGrid != null ? displayGrid : true;
619 }
620
621
622 /**
623 * Returns a boolean object that determines if the chart legend should be
624 * visible or not. This information needs to be provided by <i>settings</i>,
625 * otherwise the default is true.
626 *
627 * @param settings A ChartSettings object.
628 *
629 * @return true, if the chart legend should be visible otherwise false.
630 *
631 * @throws NullPointerException if <i>settings</i> is null.
632 */
633 public boolean isLegendVisible(ChartSettings settings) {
634 LegendSection ls = settings.getLegendSection();
635 Boolean displayLegend = ls.getVisibility();
636
637 return displayLegend != null ? displayLegend : true;
638 }
639
640
641 /**
642 * Returns the legend font size specified in <i>settings</i> or null if no
643 * <i>LegendSection</i> is provided by <i>settings</i>.
644 *
645 * @param settings A ChartSettings object.
646 *
647 * @return the legend font size or null.
648 *
649 * @throws NullPointerException if <i>settings</i> is null.
650 */
651 public Integer getLegendFontSize(ChartSettings settings) {
652 LegendSection ls = settings.getLegendSection();
653 return ls != null ? ls.getFontSize() : null;
654 }
655
656
657 /**
658 * Returns the title of a chart. The return value depends on the existence
659 * of ChartSettings: if there are ChartSettings set, this method returns the
660 * chart title provided by those settings. Otherwise, this method returns
661 * getDefaultChartTitle().
662 *
663 * @return the title of a chart.
664 */
665 protected String getChartTitle() {
666 ChartSettings chartSettings = getChartSettings();
667
668 if (chartSettings != null) {
669 return getChartTitle(chartSettings);
670 }
671
672 return getDefaultChartTitle();
673 }
674
675
676 /**
677 * Returns the subtitle of a chart. The return value depends on the
678 * existence of ChartSettings: if there are ChartSettings set, this method
679 * returns the chart title provided by those settings. Otherwise, this
680 * method returns getDefaultChartSubtitle().
681 *
682 * @return the subtitle of a chart.
683 */
684 protected String getChartSubtitle() {
685 ChartSettings chartSettings = getChartSettings();
686
687 if (chartSettings != null) {
688 return getChartSubtitle(chartSettings);
689 }
690
691 return getDefaultChartSubtitle();
692 }
693
694
695 /**
696 * This method always returns null. Override it in subclasses that require
697 * subtitles.
698 *
699 * @return null.
700 */
701 protected String getDefaultChartSubtitle() {
702 // Override this method in subclasses
703 return null;
704 }
705
706
707 /**
708 * This method is used to determine, if the chart's legend is visible or
709 * not. If a <i>settings</i> instance is set, this instance determines the
710 * visibility otherwise, this method returns true as default if no
711 * <i>settings</i> is set.
712 *
713 * @return true, if the legend should be visible, otherwise false.
714 */
715 protected boolean isLegendVisible() {
716 ChartSettings chartSettings = getChartSettings();
717 if (chartSettings != null) {
718 return isLegendVisible(chartSettings);
719 }
720
721 return true;
722 }
723
724
725 /** Where to place the logo. */
726 protected String logoHPlace() {
727 ChartSettings chartSettings = getChartSettings();
728 if (chartSettings != null) {
729 ChartSection cs = chartSettings.getChartSection();
730 String place = cs.getLogoHPlacement();
731
732 return place;
733 }
734 return "center";
735 }
736
737
738 /** Where to place the logo. */
739 protected String logoVPlace() {
740 ChartSettings chartSettings = getChartSettings();
741 if (chartSettings != null) {
742 ChartSection cs = chartSettings.getChartSection();
743 String place = cs.getLogoVPlacement();
744
745 return place;
746 }
747 return "top";
748 }
749
750
751 /** Return the logo id from settings. */
752 protected String showLogo(ChartSettings chartSettings) {
753 if (chartSettings != null) {
754 ChartSection cs = chartSettings.getChartSection();
755 String logo = cs.getDisplayLogo();
756
757 return logo;
758 }
759 return "none";
760 }
761
762
763 /**
764 * This method is used to determine if a logo should be added to the plot.
765 *
766 * @return logo name (null if none).
767 */
768 protected String showLogo() {
769 ChartSettings chartSettings = getChartSettings();
770 return showLogo(chartSettings);
771 }
772
773
774 /**
775 * This method is used to determine the font size of the chart's legend. If
776 * a <i>settings</i> instance is set, this instance determines the font
777 * size, otherwise this method returns 12 as default if no <i>settings</i>
778 * is set or if it doesn't provide a legend font size.
779 *
780 * @return a legend font size.
781 */
782 protected int getLegendFontSize() {
783 Integer fontSize = null;
784
785 ChartSettings chartSettings = getChartSettings();
786 if (chartSettings != null) {
787 fontSize = getLegendFontSize(chartSettings);
788 }
789
790 return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
791 }
792
793
794 /**
795 * This method is used to determine if the resulting chart should display
796 * grid lines or not. <b>Note: this method always returns true!</b>
797 *
798 * @return true, if the chart should display grid lines, otherwise false.
799 */
800 protected boolean isGridVisible() {
801 return true;
802 }
803
804
805 /**
806 * Returns the X-Axis label of a chart.
807 *
808 * @return the X-Axis label of a chart.
809 */
810 protected String getXAxisLabel() {
811 ChartSettings chartSettings = getChartSettings();
812 if (chartSettings == null) {
813 return getDefaultXAxisLabel();
814 }
815
816 AxisSection as = chartSettings.getAxisSection("X");
817 if (as != null) {
818 String label = as.getLabel();
819
820 if (label != null) {
821 return label;
822 }
823 }
824
825 return getDefaultXAxisLabel();
826 }
827
828
829 /**
830 * This method returns the font size for the X axis. If the font size is
831 * specified in ChartSettings (if <i>chartSettings</i> is set), this size is
832 * returned. Otherwise the default font size 12 is returned.
833 *
834 * @return the font size for the x axis.
835 */
836 protected int getXAxisLabelFontSize() {
837 ChartSettings chartSettings = getChartSettings();
838 if (chartSettings == null) {
839 return DEFAULT_FONT_SIZE;
840 }
841
842 AxisSection as = chartSettings.getAxisSection("X");
843 Integer fontSize = as.getFontSize();
844
845 return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
846 }
847
848 /**
849 * Glue between axis names and index.
850 */
851 protected abstract String axisIndexToName(int index);
852
853 /**
854 * This method returns the font size for an Y axis. If the font size is
855 * specified in ChartSettings (if <i>chartSettings</i> is set), this size is
856 * returned. Otherwise the default font size 12 is returned.
857 *
858 * @return the font size for the x axis.
859 */
860 protected int getYAxisFontSize(int index) {
861 ChartSettings chartSettings = getChartSettings();
862 if (chartSettings == null) {
863 return DEFAULT_FONT_SIZE;
864 }
865
866 AxisSection as = chartSettings.getAxisSection(axisIndexToName(index));
867 if (as == null) {
868 return DEFAULT_FONT_SIZE;
869 }
870 Integer fontSize = as.getFontSize();
871
872 return fontSize != null ? fontSize : DEFAULT_FONT_SIZE;
873 }
874
875 /**
876 * This method returns the export dimension specified in ChartSettings as
877 * int array [width,height].
878 *
879 * @return an int array with [width,height].
880 */
881 protected int[] getExportDimension() {
882 ChartSettings chartSettings = getChartSettings();
883 if (chartSettings == null) {
884 return new int[] { 600, 400 };
885 }
886
887 ExportSection export = chartSettings.getExportSection();
888 Integer width = export.getWidth();
889 Integer height = export.getHeight();
890
891 if (width != null && height != null) {
892 return new int[] { width, height };
893 }
894
895 return new int[] { 600, 400 };
896 }
897
898 protected abstract String getYAxisLabel(String axisName);
899
900 /**
901 * This method searches for a specific axis in the <i>settings</i> if
902 * <i>settings</i> is set. If the axis was found, this method returns the
903 * specified axis range if the axis range is fixed. Otherwise, this method
904 * returns null.
905 *
906 * @param axisId The identifier of an axis.
907 *
908 * @return the specified axis range from <i>settings</i> if the axis is
909 * fixed, otherwise null.
910 */
911 public Range getRangeForAxisFromSettings(String axisId) {
912 ChartSettings chartSettings = getChartSettings();
913 if (chartSettings == null) {
914 return null;
915 }
916
917 AxisSection as = chartSettings.getAxisSection(axisId);
918
919 if (as == null) {
920 return null;
921 }
922
923 Boolean fixed = as.isFixed();
924
925 if (fixed != null && fixed) {
926 Double upper = as.getUpperRange();
927 Double lower = as.getLowerRange();
928
929 if (upper != null && lower != null) {
930 return lower < upper
931 ? new Range(lower, upper)
932 : new Range(upper, lower);
933 }
934 }
935
936 return null;
937 }
938
939
940 /**
941 * Adds a new AxisDataset which contains <i>dataset</i> at index <i>idx</i>.
942 *
943 * @param dataset An XYDataset.
944 * @param idx The axis index.
945 * @param visible Determines, if the dataset should be visible or not.
946 */
947 public void addAxisDataset(XYDataset dataset, int idx, boolean visible) {
948 if (dataset == null || idx < 0) {
949 return;
950 }
951
952 AxisDataset axisDataset = getAxisDataset(idx);
953
954 Bounds[] xyBounds = ChartHelper.getBounds(dataset);
955
956 if (xyBounds == null) {
957 logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
958 return;
959 }
960
961 if (visible) {
962 if (logger.isDebugEnabled()) {
963 logger.debug("Add new AxisDataset at index: " + idx);
964 logger.debug("X extent: " + xyBounds[0]);
965 logger.debug("Y extent: " + xyBounds[1]);
966 }
967
968 axisDataset.addDataset(dataset);
969 }
970
971 combineXBounds(xyBounds[0], 0);
972 combineYBounds(xyBounds[1], idx);
973 }
974
975
976 /**
977 * This method grants access to the AxisDatasets stored in <i>datasets</i>.
978 * If no AxisDataset exists for index <i>idx</i>, a new AxisDataset is
979 * created using <i>createAxisDataset()</i>.
980 *
981 * @param idx The index of the desired AxisDataset.
982 *
983 * @return an existing or new AxisDataset.
984 */
985 public AxisDataset getAxisDataset(int idx) {
986 AxisDataset axisDataset = datasets.get(idx);
987
988 if (axisDataset == null) {
989 axisDataset = createAxisDataset(idx);
990 datasets.put(idx, axisDataset);
991 }
992
993 return axisDataset;
994 }
995
996
997 /**
998 * Adjust some Stroke/Grid parameters for <i>plot</i>. The chart
999 * <i>Settings</i> are applied in this method.
1000 *
1001 * @param plot The XYPlot which is adapted.
1002 */
1003 protected void adjustPlot(XYPlot plot) {
1004 Stroke gridStroke = new BasicStroke(
1005 DEFAULT_GRID_LINE_WIDTH,
1006 BasicStroke.CAP_BUTT,
1007 BasicStroke.JOIN_MITER,
1008 3.0f,
1009 new float[] { 3.0f },
1010 0.0f);
1011
1012 ChartSettings cs = getChartSettings();
1013 boolean isGridVisible = cs != null ? isGridVisible(cs) : true;
1014
1015 plot.setDomainGridlineStroke(gridStroke);
1016 plot.setDomainGridlinePaint(DEFAULT_GRID_COLOR);
1017 plot.setDomainGridlinesVisible(isGridVisible);
1018
1019 plot.setRangeGridlineStroke(gridStroke);
1020 plot.setRangeGridlinePaint(DEFAULT_GRID_COLOR);
1021 plot.setRangeGridlinesVisible(isGridVisible);
1022
1023 plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));
1024 }
1025
1026
1027 /**
1028 * This helper mehtod is used to extract the current locale from instance
1029 * vairable <i>context</i>.
1030 *
1031 * @return the current locale.
1032 */
1033 protected Locale getLocale() {
1034 CallMeta meta = context.getMeta();
1035 PreferredLocale[] prefs = meta.getLanguages();
1036
1037 int len = prefs != null ? prefs.length : 0;
1038
1039 Locale[] locales = new Locale[len];
1040
1041 for (int i = 0; i < len; i++) {
1042 locales[i] = prefs[i].getLocale();
1043 }
1044
1045 return meta.getPreferredLocale(locales);
1046 }
1047
1048
1049 /**
1050 * Look up \param key in i18n dictionary.
1051 * @param key key for which to find i18nd version.
1052 * @param def default, returned if lookup failed.
1053 * @return value found in i18n dictionary, \param def if no value found.
1054 */
1055 public String msg(String key, String def) {
1056 return Resources.getMsg(context.getMeta(), key, def);
1057 }
1058
1059 /**
1060 * Look up \param key in i18n dictionary.
1061 * @param key key for which to find i18nd version.
1062 * @return value found in i18n dictionary, key itself if failed.
1063 */
1064 public String msg(String key) {
1065 return Resources.getMsg(context.getMeta(), key, key);
1066 }
1067
1068 public String msg(String key, String def, Object[] args) {
1069 return Resources.getMsg(context.getMeta(), key, def, args);
1070 }
1071
1072
1073 protected String getRiverName() {
1074 D4EArtifact flys = (D4EArtifact) master;
1075
1076 River river = RiverUtils.getRiver(flys);
1077 return (river != null) ? river.getName() : "";
1078 }
1079
1080
1081 protected double[] getRange() {
1082 D4EArtifact flys = (D4EArtifact) master;
1083
1084 RangeAccess rangeAccess = new RangeAccess(flys);
1085 return rangeAccess.getKmRange();
1086 }
1087
1088
1089 /**
1090 * Returns the size of a chart export as array which has been specified by
1091 * the incoming request document.
1092 *
1093 * @return the size of a chart as [width, height] or null if no width or
1094 * height are given in the request document.
1095 */
1096 protected int[] getSize() {
1097 int[] size = new int[2];
1098
1099 Element sizeEl = (Element)XMLUtils.xpath(
1100 request,
1101 XPATH_CHART_SIZE,
1102 XPathConstants.NODE,
1103 ArtifactNamespaceContext.INSTANCE);
1104
1105 if (sizeEl != null) {
1106 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
1107
1108 String w = sizeEl.getAttributeNS(uri, "width");
1109 String h = sizeEl.getAttributeNS(uri, "height");
1110
1111 if (w.length() > 0 && h.length() > 0) {
1112 try {
1113 size[0] = Integer.parseInt(w);
1114 size[1] = Integer.parseInt(h);
1115 }
1116 catch (NumberFormatException nfe) {
1117 logger.warn("Wrong values for chart width/height.");
1118 }
1119 }
1120 }
1121
1122 return size[0] > 0 && size[1] > 0 ? size : null;
1123 }
1124
1125
1126 /**
1127 * This method returns the format specified in the <i>request</i> document
1128 * or <i>DEFAULT_CHART_FORMAT</i> if no format is specified in
1129 * <i>request</i>.
1130 *
1131 * @return the format used to export this chart.
1132 */
1133 protected String getFormat() {
1134 String format = (String) XMLUtils.xpath(
1135 request,
1136 XPATH_CHART_FORMAT,
1137 XPathConstants.STRING,
1138 ArtifactNamespaceContext.INSTANCE);
1139
1140 return format == null || format.length() == 0
1141 ? DEFAULT_CHART_FORMAT
1142 : format;
1143 }
1144
1145
1146 /**
1147 * Returns the X-Axis range as String array from request document.
1148 * If the (x|y)range elements are not found in request document, return
1149 * null (i.e. not zoomed).
1150 *
1151 * @return a String array with [lower, upper], null if not in document.
1152 */
1153 protected String[] getDomainAxisRangeFromRequest() {
1154 Element xrange = (Element)XMLUtils.xpath(
1155 request,
1156 XPATH_CHART_X_RANGE,
1157 XPathConstants.NODE,
1158 ArtifactNamespaceContext.INSTANCE);
1159
1160 if (xrange == null) {
1161 return null;
1162 }
1163
1164 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
1165
1166 String lower = xrange.getAttributeNS(uri, "from");
1167 String upper = xrange.getAttributeNS(uri, "to");
1168
1169 return new String[] { lower, upper };
1170 }
1171
1172
1173 /** Returns null if the (x|y)range-element was not found in request document.
1174 * This usally means that the axis are not manually zoomed, i.e. showing
1175 * full data extent. */
1176 protected String[] getValueAxisRangeFromRequest() {
1177 Element yrange = (Element)XMLUtils.xpath(
1178 request,
1179 XPATH_CHART_Y_RANGE,
1180 XPathConstants.NODE,
1181 ArtifactNamespaceContext.INSTANCE);
1182
1183 if (yrange == null) {
1184 return null;
1185 }
1186
1187
1188 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
1189
1190 String lower = yrange.getAttributeNS(uri, "from");
1191 String upper = yrange.getAttributeNS(uri, "to");
1192
1193 return new String[] { lower, upper };
1194 }
1195
1196
1197 /**
1198 * Returns the default size of a chart export as array.
1199 *
1200 * @return the default size of a chart as [width, height].
1201 */
1202 protected int[] getDefaultSize() {
1203 return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT };
1204 }
1205
1206
1207 /**
1208 * Add datasets stored in instance variable <i>datasets</i> to plot.
1209 * <i>datasets</i> actually stores instances of AxisDataset, so each of this
1210 * datasets is mapped to a specific axis as well.
1211 *
1212 * @param plot plot to add datasets to.
1213 */
1214 protected void addDatasets(XYPlot plot) {
1215 logger.debug("addDatasets()");
1216
1217 // AxisDatasets are sorted, but some might be empty.
1218 // Thus, generate numbering on the fly.
1219 int axisIndex = 0;
1220 int datasetIndex = 0;
1221
1222 for (Map.Entry<Integer, AxisDataset> entry: datasets.entrySet()) {
1223 if (!entry.getValue().isEmpty()) {
1224 // Add axis and range information.
1225 AxisDataset axisDataset = entry.getValue();
1226 NumberAxis axis = createYAxis(entry.getKey());
1227
1228 plot.setRangeAxis(axisIndex, axis);
1229
1230 if (axis.getAutoRangeIncludesZero()) {
1231 axisDataset.setRange(
1232 Range.expandToInclude(axisDataset.getRange(), 0d));
1233 }
1234
1235 setYBounds(axisIndex, expandPointRange(axisDataset.getRange()));
1236
1237 // Add contained datasets, mapping to axis.
1238 for (XYDataset dataset: axisDataset.getDatasets()) {
1239 plot.setDataset(datasetIndex, dataset);
1240 plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
1241
1242 applyThemes(plot, dataset,
1243 datasetIndex,
1244 axisDataset.isArea(dataset));
1245
1246 datasetIndex++;
1247 }
1248
1249 axisDataset.setPlotAxisIndex(axisIndex);
1250 axisIndex++;
1251 }
1252 }
1253 }
1254
1255
1256 /**
1257 * @param idx "index" of dataset/series (first dataset to be drawn has
1258 * index 0), correlates with renderer index.
1259 * @param isArea true if the series describes an area and shall be rendered
1260 * as such.
1261 */
1262 protected void applyThemes(
1263 XYPlot plot,
1264 XYDataset series,
1265 int idx,
1266 boolean isArea
1267 ) {
1268 if (isArea) {
1269 applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx);
1270 }
1271 else {
1272 applyLineTheme(plot, series, idx);
1273 }
1274 }
1275
1276
1277 /**
1278 * This method applies the themes defined in the series itself. Therefore,
1279 * <i>StyledXYSeries.applyTheme()</i> is called, which modifies the renderer
1280 * for the series.
1281 *
1282 * @param plot The plot.
1283 * @param dataset The XYDataset which needs to support Series objects.
1284 * @param idx The index of the renderer / dataset.
1285 */
1286 protected void applyLineTheme(XYPlot plot, XYDataset dataset, int idx) {
1287 logger.debug("Apply LineTheme for dataset at index: " + idx);
1288
1289 LegendItemCollection lic = new LegendItemCollection();
1290 LegendItemCollection anno = plot.getFixedLegendItems();
1291
1292 Font legendFont = createLegendLabelFont();
1293
1294 XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
1295
1296 for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
1297 Series series = getSeriesOf(dataset, s);
1298
1299 if (series instanceof StyledSeries) {
1300 Style style = ((StyledSeries) series).getStyle();
1301 style.applyTheme(renderer, s);
1302 }
1303
1304 // special case: if there is just one single item, we need to enable
1305 // points for this series, otherwise we would not see anything in
1306 // the chart area.
1307 if (series.getItemCount() == 1) {
1308 renderer.setSeriesShapesVisible(s, true);
1309 }
1310
1311 LegendItem legendItem = renderer.getLegendItem(idx, s);
1312 if (legendItem.getLabel().endsWith(" ") ||
1313 legendItem.getLabel().endsWith("interpol")) {
1314 legendItem = null;
1315 }
1316
1317 if (legendItem != null) {
1318 legendItem.setLabelFont(legendFont);
1319 lic.add(legendItem);
1320 }
1321 else {
1322 logger.warn("Could not get LegentItem for renderer: "
1323 + idx + ", series-idx " + s);
1324 }
1325 }
1326
1327 if (anno != null) {
1328 lic.addAll(anno);
1329 }
1330
1331 plot.setFixedLegendItems(lic);
1332
1333 plot.setRenderer(idx, renderer);
1334 }
1335
1336
1337 /**
1338 * @param plot The plot.
1339 * @param area A StyledAreaSeriesCollection object.
1340 * @param idx The index of the dataset.
1341 */
1342 protected void applyAreaTheme(
1343 XYPlot plot,
1344 StyledAreaSeriesCollection area,
1345 int idx
1346 ) {
1347 LegendItemCollection lic = new LegendItemCollection();
1348 LegendItemCollection anno = plot.getFixedLegendItems();
1349
1350 Font legendFont = createLegendLabelFont();
1351
1352 logger.debug("Registering an 'area'renderer at idx: " + idx);
1353
1354 StableXYDifferenceRenderer dRenderer =
1355 new StableXYDifferenceRenderer();
1356
1357 if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
1358 dRenderer.setPositivePaint(createTransparentPaint());
1359 }
1360
1361 plot.setRenderer(idx, dRenderer);
1362
1363 area.applyTheme(dRenderer);
1364
1365 // i18n
1366 dRenderer.setAreaLabelNumberFormat(Formatter.getFormatter(context.getMeta(), 2, 4));
1367
1368 dRenderer.setAreaLabelTemplate(Resources.getMsg(
1369 context.getMeta(), "area.label.template", "Area=%sm2"));
1370
1371 LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
1372 if (legendItem != null) {
1373 legendItem.setLabelFont(legendFont);
1374 lic.add(legendItem);
1375 }
1376 else {
1377 logger.warn("Could not get LegentItem for renderer: "
1378 + idx + ", series-idx " + 0);
1379 }
1380
1381 if (anno != null) {
1382 lic.addAll(anno);
1383 }
1384
1385 plot.setFixedLegendItems(lic);
1386 }
1387
1388
1389 /**
1390 * Expands a given range if it collapses into one point.
1391 *
1392 * @param range Range to be expanded if upper == lower bound.
1393 *
1394 * @return Bounds of point plus 5 percent in each direction.
1395 */
1396 private Bounds expandPointRange(Range range) {
1397 if (range == null) {
1398 return null;
1399 }
1400 else if (range.getLowerBound() == range.getUpperBound()) {
1401 Range expandedRange = ChartHelper.expandRange(range, 5d);
1402 return new DoubleBounds(expandedRange.getLowerBound(), expandedRange.getUpperBound());
1403 }
1404
1405 return new DoubleBounds(range.getLowerBound(), range.getUpperBound());
1406 }
1407
1408
1409 /**
1410 * Creates a new instance of EnhancedLineAndShapeRenderer.
1411 *
1412 * @param plot The plot which is set for the new renderer.
1413 * @param idx This value is not used in the current implementation.
1414 *
1415 * @return a new instance of EnhancedLineAndShapeRenderer.
1416 */
1417 protected XYLineAndShapeRenderer createRenderer(XYPlot plot, int idx) {
1418 logger.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx);
1419
1420 EnhancedLineAndShapeRenderer r =
1421 new EnhancedLineAndShapeRenderer(true, false);
1422
1423 r.setPlot(plot);
1424
1425 return r;
1426 }
1427
1428
1429 /**
1430 * Creates a new instance of <i>IdentifiableNumberAxis</i>.
1431 *
1432 * @param idx The index of the new axis.
1433 * @param label The label of the new axis.
1434 *
1435 * @return an instance of IdentifiableNumberAxis.
1436 */
1437 protected NumberAxis createNumberAxis(int idx, String label) {
1438 return new IdentifiableNumberAxis(axisIndexToName(idx), label);
1439 }
1440
1441
1442 /**
1443 * Create Y (range) axis for given index.
1444 * Shall be overriden by subclasses.
1445 */
1446 protected NumberAxis createYAxis(int index) {
1447
1448 Font labelFont = new Font(
1449 DEFAULT_FONT_NAME,
1450 Font.BOLD,
1451 getYAxisFontSize(index));
1452
1453 String axisName = axisIndexToName(index);
1454
1455 IdentifiableNumberAxis axis = new IdentifiableNumberAxis(
1456 axisName, getYAxisLabel(axisName));
1457
1458 axis.setAutoRangeIncludesZero(false);
1459 axis.setLabelFont(labelFont);
1460 axis.setTickLabelFont(labelFont);
1461
1462 return axis;
1463 }
1464
1465
1466 /**
1467 * Creates a new LegendItem with <i>name</i> and font provided by
1468 * <i>createLegendLabelFont()</i>.
1469 *
1470 * @param theme The theme of the chart line.
1471 * @param name The displayed name of the item.
1472 *
1473 * @return a new LegendItem instance.
1474 */
1475 public LegendItem createLegendItem(ThemeDocument theme, String name) {
1476 // OPTIMIZE Pass font, parsed Theme items.
1477
1478 Color color = theme.parseLineColorField();
1479 if (color == null) {
1480 color = Color.BLACK;
1481 }
1482
1483 LegendItem legendItem = new LegendItem(name, color);
1484
1485 legendItem.setLabelFont(createLegendLabelFont());
1486 return legendItem;
1487 }
1488
1489
1490 /**
1491 * Creates Font (Family and size) to use when creating Legend Items. The
1492 * font size depends in the return value of <i>getLegendFontSize()</i>.
1493 *
1494 * @return a new Font instance with <i>DEFAULT_FONT_NAME</i>.
1495 */
1496 protected Font createLegendLabelFont() {
1497 return new Font(
1498 DEFAULT_FONT_NAME,
1499 Font.PLAIN,
1500 getLegendFontSize()
1501 );
1502 }
1503
1504
1505 /**
1506 * Create new legend entries, dependent on settings.
1507 * @param plot The plot for which to modify the legend.
1508 */
1509 public void aggregateLegendEntries(XYPlot plot) {
1510 int AGGR_THRESHOLD = 0;
1511
1512 if (getChartSettings() == null) {
1513 return;
1514 }
1515 Integer threshold = getChartSettings().getLegendSection()
1516 .getAggregationThreshold();
1517
1518 AGGR_THRESHOLD = (threshold != null) ? threshold.intValue() : 0;
1519
1520 LegendProcessor.aggregateLegendEntries(plot, AGGR_THRESHOLD);
1521 }
1522
1523
1524 /**
1525 * Returns a transparently textured paint.
1526 *
1527 * @return a transparently textured paint.
1528 */
1529 protected static Paint createTransparentPaint() {
1530 // TODO why not use a transparent color?
1531 BufferedImage texture = new BufferedImage(
1532 1, 1, BufferedImage.TYPE_4BYTE_ABGR);
1533
1534 return new TexturePaint(
1535 texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
1536 }
1537
1538
1539 protected void preparePDFContext(CallContext context) {
1540 int[] dimension = getExportDimension();
1541
1542 context.putContextValue("chart.width", dimension[0]);
1543 context.putContextValue("chart.height", dimension[1]);
1544 context.putContextValue("chart.marginLeft", 5f);
1545 context.putContextValue("chart.marginRight", 5f);
1546 context.putContextValue("chart.marginTop", 5f);
1547 context.putContextValue("chart.marginBottom", 5f);
1548 context.putContextValue(
1549 "chart.page.format",
1550 ChartExportHelper.DEFAULT_PAGE_SIZE);
1551 }
1552
1553
1554 protected void prepareSVGContext(CallContext context) {
1555 int[] dimension = getExportDimension();
1556
1557 context.putContextValue("chart.width", dimension[0]);
1558 context.putContextValue("chart.height", dimension[1]);
1559 context.putContextValue(
1560 "chart.encoding",
1561 ChartExportHelper.DEFAULT_ENCODING);
1562 }
1563
1564 /**
1565 * Retuns the call context. May be null if init hasn't been called yet.
1566 *
1567 * @return the CallContext instance
1568 */
1569 public CallContext getCallContext() {
1570 return context;
1571 }
1572 }

http://dive4elements.wald.intevation.org