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