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