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