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