Mercurial > dive4elements > river
comparison artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java @ 5838:5aa05a7a34b7
Rename modules to more fitting names.
author | Sascha L. Teichmann <teichmann@intevation.de> |
---|---|
date | Thu, 25 Apr 2013 15:23:37 +0200 |
parents | flys-artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.java@bd047b71ab37 |
children | 4897a58c8746 |
comparison
equal
deleted
inserted
replaced
5837:d9901a08d0a6 | 5838:5aa05a7a34b7 |
---|---|
1 package org.dive4elements.river.exports; | |
2 | |
3 import java.awt.Color; | |
4 import java.awt.Font; | |
5 import java.text.NumberFormat; | |
6 import java.util.ArrayList; | |
7 import java.util.HashMap; | |
8 import java.util.List; | |
9 import java.util.Map; | |
10 | |
11 import javax.swing.ImageIcon; | |
12 | |
13 import org.apache.log4j.Logger; | |
14 import org.jfree.chart.ChartFactory; | |
15 import org.jfree.chart.JFreeChart; | |
16 import org.jfree.chart.LegendItem; | |
17 import org.jfree.chart.annotations.XYAnnotation; | |
18 import org.jfree.chart.annotations.XYImageAnnotation; | |
19 import org.jfree.chart.annotations.XYTextAnnotation; | |
20 import org.jfree.chart.axis.NumberAxis; | |
21 import org.jfree.chart.axis.ValueAxis; | |
22 import org.jfree.chart.axis.LogarithmicAxis; | |
23 import org.jfree.chart.plot.Marker; | |
24 import org.jfree.chart.plot.PlotOrientation; | |
25 import org.jfree.chart.plot.XYPlot; | |
26 import org.jfree.data.Range; | |
27 import org.jfree.data.general.Series; | |
28 import org.jfree.data.xy.XYDataset; | |
29 import org.jfree.data.xy.XYSeries; | |
30 import org.jfree.data.xy.XYSeriesCollection; | |
31 import org.json.JSONArray; | |
32 import org.json.JSONException; | |
33 import org.w3c.dom.Document; | |
34 | |
35 import org.dive4elements.artifactdatabase.state.ArtifactAndFacet; | |
36 import org.dive4elements.river.jfree.Bounds; | |
37 import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation; | |
38 import org.dive4elements.river.jfree.DoubleBounds; | |
39 import org.dive4elements.river.jfree.FLYSAnnotation; | |
40 import org.dive4elements.river.jfree.StyledAreaSeriesCollection; | |
41 import org.dive4elements.river.jfree.StyledXYSeries; | |
42 | |
43 | |
44 /** | |
45 * An abstract base class for creating XY charts. | |
46 * | |
47 * With respect to datasets, ranges and axis, there are following requirements: | |
48 * <ul> | |
49 * <li> First in, first drawn: "Early" datasets should be of lower Z-Oder | |
50 * than later ones (only works per-axis). </li> | |
51 * <li> Visible axis should initially show the range of all datasets that | |
52 * show data for this axis (even invisible ones). Motivation: Once | |
53 * a dataset (theme) has been activated, it should be on screen. </li> | |
54 * <li> There should always be a Y-Axis on the "left". </li> | |
55 * </ul> | |
56 * | |
57 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> | |
58 */ | |
59 public abstract class XYChartGenerator extends ChartGenerator { | |
60 | |
61 public class XYAxisDataset implements AxisDataset { | |
62 /** Symbolic integer, but also coding the priority (0 goes first). */ | |
63 protected int axisSymbol; | |
64 | |
65 /** List of assigned datasets (in order). */ | |
66 protected List<XYDataset> datasets; | |
67 | |
68 /** Range to use to include all given datasets. */ | |
69 protected Range range; | |
70 | |
71 /** Index of axis in plot. */ | |
72 protected int plotAxisIndex; | |
73 | |
74 /** Create AxisDataset. */ | |
75 public XYAxisDataset(int symb) { | |
76 this.axisSymbol = symb; | |
77 datasets = new ArrayList<XYDataset>(); | |
78 } | |
79 | |
80 /** Merge (or create given range with range so far (if any). */ | |
81 private void mergeRanges(Range subRange) { | |
82 // Avoid merging NaNs, as they take min/max place forever. | |
83 if (subRange == null || | |
84 Double.isNaN(subRange.getLowerBound()) || | |
85 Double.isNaN(subRange.getUpperBound())) { | |
86 return; | |
87 } | |
88 if (range == null) { | |
89 range = subRange; | |
90 return; | |
91 } | |
92 range = Range.combine(range, subRange); | |
93 } | |
94 | |
95 | |
96 /** Add a dataset to internal list for this axis. */ | |
97 @Override | |
98 public void addDataset(XYDataset dataset) { | |
99 datasets.add(dataset); | |
100 includeYRange(((XYSeriesCollection) dataset).getSeries(0)); | |
101 } | |
102 | |
103 /** Add a dataset, include its range. */ | |
104 public void addDataset(XYSeries series) { | |
105 addDataset(new XYSeriesCollection(series)); | |
106 } | |
107 | |
108 | |
109 /** Set Range for this axis. */ | |
110 @Override | |
111 public void setRange(Range range) { | |
112 this.range = range; | |
113 } | |
114 | |
115 | |
116 /** Get Range for this axis. */ | |
117 @Override | |
118 public Range getRange() { | |
119 return range; | |
120 } | |
121 | |
122 | |
123 /** Get Array of Datasets. */ | |
124 @Override | |
125 public XYDataset[] getDatasets() { | |
126 return datasets.toArray(new XYDataset[datasets.size()]); | |
127 } | |
128 | |
129 | |
130 /** Add a Dataset that describes an area. */ | |
131 public void addArea(StyledAreaSeriesCollection series) { | |
132 this.datasets.add(series); | |
133 List<?> allSeries = series.getSeries(); | |
134 /* We do not include the bounds/ranges, if the area includes | |
135 * points at "infinity"/BIG_DOUBLE_VALUE, the charts extents are | |
136 * expanded to include these very small/big value. | |
137 * This is especially used when showing "area above axis". */ | |
138 } | |
139 | |
140 /** True if to be rendered as area. */ | |
141 @Override | |
142 public boolean isArea(XYDataset series) { | |
143 return (series instanceof StyledAreaSeriesCollection); | |
144 } | |
145 | |
146 /** Adjust range to include given dataset. */ | |
147 public void includeYRange(XYSeries dataset) { | |
148 mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY())); | |
149 } | |
150 | |
151 /** True if no datasets given. */ | |
152 @Override | |
153 public boolean isEmpty() { | |
154 return this.datasets.isEmpty(); | |
155 } | |
156 | |
157 /** Set the 'real' axis index that this axis is mapped to. */ | |
158 @Override | |
159 public void setPlotAxisIndex(int axisIndex) { | |
160 this.plotAxisIndex = axisIndex; | |
161 } | |
162 | |
163 /** Get the 'real' axis index that this axis is mapped to. */ | |
164 @Override | |
165 public int getPlotAxisIndex() { | |
166 return this.plotAxisIndex; | |
167 } | |
168 } // class AxisDataset | |
169 | |
170 /** Enumerator over existing axes. */ | |
171 @Override | |
172 protected abstract YAxisWalker getYAxisWalker(); | |
173 | |
174 public static final int AXIS_SPACE = 5; | |
175 | |
176 /** The logger that is used in this generator. */ | |
177 private static Logger logger = Logger.getLogger(XYChartGenerator.class); | |
178 | |
179 protected List<Marker> domainMarkers = new ArrayList<Marker>(); | |
180 | |
181 protected List<Marker> valueMarkers = new ArrayList<Marker>(); | |
182 | |
183 /** The max X range to include all X values of all series for each axis. */ | |
184 protected Map<Integer, Bounds> xBounds; | |
185 | |
186 /** The max Y range to include all Y values of all series for each axis. */ | |
187 protected Map<Integer, Bounds> yBounds; | |
188 | |
189 /** Whether or not the plot is inverted (left-right). */ | |
190 private boolean inverted; | |
191 | |
192 public XYChartGenerator() { | |
193 super(); | |
194 | |
195 xBounds = new HashMap<Integer, Bounds>(); | |
196 yBounds = new HashMap<Integer, Bounds>(); | |
197 } | |
198 | |
199 | |
200 /** | |
201 * Generate the chart anew (including localized axis and all). | |
202 */ | |
203 @Override | |
204 public JFreeChart generateChart() { | |
205 logger.debug("XYChartGenerator.generateChart"); | |
206 | |
207 JFreeChart chart = ChartFactory.createXYLineChart( | |
208 getChartTitle(), | |
209 getXAxisLabel(), | |
210 getYAxisLabel(0), | |
211 null, | |
212 PlotOrientation.VERTICAL, | |
213 isLegendVisible(), | |
214 false, | |
215 false); | |
216 | |
217 XYPlot plot = (XYPlot) chart.getPlot(); | |
218 ValueAxis axis = createXAxis(getXAxisLabel()); | |
219 plot.setDomainAxis(axis); | |
220 | |
221 chart.setBackgroundPaint(Color.WHITE); | |
222 plot.setBackgroundPaint(Color.WHITE); | |
223 addSubtitles(chart); | |
224 adjustPlot(plot); | |
225 | |
226 //debugAxis(plot); | |
227 | |
228 addDatasets(plot); | |
229 | |
230 //debugDatasets(plot); | |
231 | |
232 addMarkers(plot); | |
233 | |
234 recoverEmptyPlot(plot); | |
235 preparePointRanges(plot); | |
236 | |
237 //debugAxis(plot); | |
238 | |
239 localizeAxes(plot); | |
240 adjustAxes(plot); | |
241 if (!(axis instanceof LogarithmicAxis)) { | |
242 // XXX: | |
243 // The auto zoom without a range tries | |
244 // to include 0 in a logarithmic axis | |
245 // which triggers a bug in jfreechart that causes | |
246 // the values to be drawn carthesian | |
247 autoZoom(plot); | |
248 } | |
249 | |
250 //debugAxis(plot); | |
251 | |
252 // These have to go after the autozoom. | |
253 addAnnotationsToRenderer(plot); | |
254 | |
255 // Add a logo (maybe). | |
256 addLogo(plot); | |
257 | |
258 aggregateLegendEntries(plot); | |
259 | |
260 return chart; | |
261 } | |
262 | |
263 | |
264 /** | |
265 * Return left most data points x value (on first axis). | |
266 * Shortcut, especially to be overridden in (LS) charts where | |
267 * axis could be inverted. | |
268 */ | |
269 protected double getLeftX() { | |
270 return (Double)getXBounds(0).getLower(); | |
271 } | |
272 | |
273 | |
274 /** | |
275 * Return right most data points x value (on first axis). | |
276 * Shortcut, especially to be overridden in (LS) charts where | |
277 * axis could be inverted. | |
278 */ | |
279 protected double getRightX() { | |
280 return (Double)getXBounds(0).getUpper(); | |
281 } | |
282 | |
283 | |
284 /** Add a logo as background annotation to plot. */ | |
285 protected void addLogo(XYPlot plot) { | |
286 String logo = showLogo(); | |
287 if (logo == null) { | |
288 logger.debug("No logo to show chosen"); | |
289 return; | |
290 } | |
291 | |
292 ImageIcon imageIcon = null; | |
293 if (logo.equals("none")) { | |
294 return; | |
295 } | |
296 /* | |
297 If you want to add images, remember to change code in these places: | |
298 flys-artifacts: | |
299 XYChartGenerator.java | |
300 Timeseries*Generator.java and | |
301 in the flys-client projects Chart*Propert*Editor.java. | |
302 Also, these images have to be put in | |
303 flys-artifacts/src/main/resources/images/ | |
304 flys-client/src/main/webapp/images/ | |
305 */ | |
306 java.net.URL imageURL; | |
307 if (logo.equals("Intevation")) { | |
308 imageURL = XYChartGenerator.class.getResource("/images/intevation.png"); | |
309 } | |
310 else { // TODO else if ... | |
311 imageURL = XYChartGenerator.class.getResource("/images/bfg_logo.gif"); | |
312 } | |
313 imageIcon = new ImageIcon(imageURL); | |
314 | |
315 | |
316 double xPos = 0d, yPos = 0d; | |
317 | |
318 String placeh = logoHPlace(); | |
319 String placev = logoVPlace(); | |
320 | |
321 if (placev == null || placev.equals("none")) { | |
322 placev = "top"; | |
323 } | |
324 if (placev.equals("top")) { | |
325 yPos = (Double)getYBounds(0).getUpper(); | |
326 } | |
327 else if (placev.equals("bottom")) { | |
328 yPos = (Double)getYBounds(0).getLower(); | |
329 } | |
330 else if (placev.equals("center")) { | |
331 yPos = ((Double)getYBounds(0).getUpper() + (Double)getYBounds(0).getLower())/2d; | |
332 } | |
333 else { | |
334 logger.debug("Unknown place-v value: " + placev); | |
335 } | |
336 | |
337 if (placeh == null || placeh.equals("none")) { | |
338 placeh = "center"; | |
339 } | |
340 if (placeh.equals("left")) { | |
341 xPos = getLeftX(); | |
342 } | |
343 else if (placeh.equals("right")) { | |
344 xPos = getRightX(); | |
345 } | |
346 else if (placeh.equals("center")) { | |
347 xPos = ((Double)getXBounds(0).getUpper() + (Double)getXBounds(0).getLower())/2d; | |
348 } | |
349 else { | |
350 logger.debug("Unknown place-h value: " + placeh); | |
351 } | |
352 | |
353 logger.debug("logo position: " + xPos + "/" + yPos); | |
354 | |
355 org.jfree.ui.RectangleAnchor anchor | |
356 = org.jfree.ui.RectangleAnchor.TOP; | |
357 if (placev.equals("top")) { | |
358 if (placeh.equals("left")) { | |
359 anchor = org.jfree.ui.RectangleAnchor.TOP_LEFT; | |
360 } | |
361 else if (placeh.equals("right")) { | |
362 anchor = org.jfree.ui.RectangleAnchor.TOP_RIGHT; | |
363 } | |
364 else if (placeh.equals("center")) { | |
365 anchor = org.jfree.ui.RectangleAnchor.TOP; | |
366 } | |
367 } | |
368 else if (placev.equals("bottom")) { | |
369 if (placeh.equals("left")) { | |
370 anchor = org.jfree.ui.RectangleAnchor.BOTTOM_LEFT; | |
371 } | |
372 else if (placeh.equals("right")) { | |
373 anchor = org.jfree.ui.RectangleAnchor.BOTTOM_RIGHT; | |
374 } | |
375 else if (placeh.equals("center")) { | |
376 anchor = org.jfree.ui.RectangleAnchor.BOTTOM; | |
377 } | |
378 } | |
379 else if (placev.equals("center")) { | |
380 if (placeh.equals("left")) { | |
381 anchor = org.jfree.ui.RectangleAnchor.LEFT; | |
382 } | |
383 else if (placeh.equals("right")) { | |
384 anchor = org.jfree.ui.RectangleAnchor.RIGHT; | |
385 } | |
386 else if (placeh.equals("center")) { | |
387 anchor = org.jfree.ui.RectangleAnchor.CENTER; | |
388 } | |
389 } | |
390 | |
391 XYAnnotation xyannotation = | |
392 new XYImageAnnotation(xPos, yPos, imageIcon.getImage(), anchor); | |
393 plot.getRenderer().addAnnotation(xyannotation, org.jfree.ui.Layer.BACKGROUND); | |
394 } | |
395 | |
396 | |
397 protected NumberAxis createXAxis(String label) { | |
398 return new NumberAxis(label); | |
399 } | |
400 | |
401 | |
402 @Override | |
403 protected Series getSeriesOf(XYDataset dataset, int idx) { | |
404 return ((XYSeriesCollection) dataset).getSeries(idx); | |
405 } | |
406 | |
407 | |
408 @Override | |
409 protected AxisDataset createAxisDataset(int idx) { | |
410 logger.debug("Create new XYAxisDataset for index: " + idx); | |
411 return new XYAxisDataset(idx); | |
412 } | |
413 | |
414 | |
415 /** | |
416 * Put debug output about datasets. | |
417 */ | |
418 public void debugDatasets(XYPlot plot) { | |
419 logger.debug("Number of datasets: " + plot.getDatasetCount()); | |
420 for (int i = 0, P = plot.getDatasetCount(); i < P; i++) { | |
421 if (plot.getDataset(i) == null) { | |
422 logger.debug("Dataset #" + i + " is null"); | |
423 continue; | |
424 } | |
425 logger.debug("Dataset #" + i + ":" + plot.getDataset(i)); | |
426 XYSeriesCollection series = (XYSeriesCollection) plot.getDataset(i); | |
427 logger.debug("X-Extend of Dataset: " + series.getSeries(0).getMinX() | |
428 + " " + series.getSeries(0).getMaxX()); | |
429 logger.debug("Y-Extend of Dataset: " + series.getSeries(0).getMinY() | |
430 + " " + series.getSeries(0).getMaxY()); | |
431 } | |
432 } | |
433 | |
434 | |
435 /** | |
436 * Put debug output about axes. | |
437 */ | |
438 public void debugAxis(XYPlot plot) { | |
439 logger.debug("..............."); | |
440 for (int i = 0, P = plot.getRangeAxisCount(); i < P; i++) { | |
441 if (plot.getRangeAxis(i) == null) | |
442 logger.debug("Range-Axis #" + i + " == null"); | |
443 else { | |
444 logger.debug("Range-Axis " + i + " != null [" + | |
445 plot.getRangeAxis(i).getRange().getLowerBound() + | |
446 " " + plot.getRangeAxis(i).getRange().getUpperBound() + | |
447 "]"); | |
448 } | |
449 } | |
450 for (int i = 0, P = plot.getDomainAxisCount(); i < P; i++) { | |
451 if (plot.getDomainAxis(i) == null) | |
452 logger.debug("Domain-Axis #" + i + " == null"); | |
453 else { | |
454 logger.debug("Domain-Axis " + i + " != null [" + | |
455 plot.getDomainAxis(i).getRange().getLowerBound() + | |
456 " " + plot.getDomainAxis(i).getRange().getUpperBound() + | |
457 "]"); | |
458 } | |
459 } | |
460 logger.debug("..............."); | |
461 } | |
462 | |
463 | |
464 /** | |
465 * Registers an area to be drawn. | |
466 * @param area Area to be drawn. | |
467 * @param index 'axis index' | |
468 * @param visible Whether or not to be visible (important for range calculations). | |
469 */ | |
470 public void addAreaSeries(StyledAreaSeriesCollection area, int index, boolean visible) { | |
471 if (area == null) { | |
472 logger.warn("Cannot yet render above/under curve."); | |
473 return; | |
474 } | |
475 | |
476 XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index); | |
477 | |
478 if (visible) { | |
479 axisDataset.addArea(area); | |
480 } | |
481 else { | |
482 /* No range merging, for areas extending to infinity this | |
483 * causes problems. */ | |
484 } | |
485 } | |
486 | |
487 | |
488 /** | |
489 * Add given series if visible, if not visible adjust ranges (such that | |
490 * all points in data would be plotted once visible). | |
491 * @param series the data series to include in plot. | |
492 * @param index ('symbolic') index of the series and of its axis. | |
493 * @param visible whether or not the data should be plotted. | |
494 */ | |
495 public void addAxisSeries(XYSeries series, int index, boolean visible) { | |
496 if (series == null) { | |
497 return; | |
498 } | |
499 | |
500 logger.debug("Y Range of XYSeries: " + | |
501 series.getMinY() + " | " + series.getMaxY()); | |
502 | |
503 addAxisDataset(new XYSeriesCollection(series), index, visible); | |
504 | |
505 XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index); | |
506 | |
507 if (!visible) { | |
508 // Do this also when not visible to have axis scaled by default such | |
509 // that every data-point could be seen (except for annotations). | |
510 axisDataset.includeYRange(series); | |
511 } | |
512 } | |
513 | |
514 | |
515 /** | |
516 * Add the given vertical marker to the chart. | |
517 */ | |
518 public void addDomainMarker(Marker marker) { | |
519 addDomainMarker(marker, true); | |
520 } | |
521 | |
522 | |
523 /** | |
524 * Add the given vertical marker to the chart.<b>Note:</b> the marker is | |
525 * added to the chart only if it is not null and if <i>visible</i> is true. | |
526 * @param marker The marker that should be added to the chart. | |
527 * @param visible The visibility of the marker. | |
528 */ | |
529 public void addDomainMarker(Marker marker, boolean visible) { | |
530 if (visible && marker != null) { | |
531 domainMarkers.add(marker); | |
532 } | |
533 } | |
534 | |
535 | |
536 /** | |
537 * Add the given vertical marker to the chart. | |
538 */ | |
539 public void addValueMarker(Marker marker) { | |
540 addValueMarker(marker, true); | |
541 } | |
542 | |
543 | |
544 /** | |
545 * Add the given horizontal marker to the chart.<b>Note:</b> the marker is | |
546 * added to the chart only if it is not null and if <i>visible</i> is true. | |
547 * @param marker The marker that should be added to the chart. | |
548 * @param visible The visibility of the marker. | |
549 */ | |
550 public void addValueMarker(Marker marker, boolean visible) { | |
551 if (visible && marker != null) { | |
552 valueMarkers.add(marker); | |
553 } | |
554 } | |
555 | |
556 | |
557 protected void addMarkers(XYPlot plot) { | |
558 for(Marker marker : domainMarkers) { | |
559 plot.addDomainMarker(marker); | |
560 } | |
561 for(Marker marker : valueMarkers) { | |
562 plot.addRangeMarker(marker); | |
563 } | |
564 } | |
565 | |
566 | |
567 /** | |
568 * Effect: extend range of x axis to include given limits. | |
569 * | |
570 * @param bounds the given ("minimal") bounds. | |
571 * @param index index of axis to be merged. | |
572 */ | |
573 @Override | |
574 protected void combineXBounds(Bounds bounds, int index) { | |
575 if (!(bounds instanceof DoubleBounds)) { | |
576 logger.warn("Unsupported Bounds type: " + bounds.getClass()); | |
577 return; | |
578 } | |
579 | |
580 DoubleBounds dBounds = (DoubleBounds) bounds; | |
581 | |
582 if (dBounds == null | |
583 || Double.isNaN((Double) dBounds.getLower()) | |
584 || Double.isNaN((Double) dBounds.getUpper())) { | |
585 return; | |
586 } | |
587 | |
588 Bounds old = getXBounds(index); | |
589 | |
590 if (old != null) { | |
591 dBounds = (DoubleBounds) dBounds.combine(old); | |
592 } | |
593 | |
594 setXBounds(index, dBounds); | |
595 } | |
596 | |
597 | |
598 @Override | |
599 protected void combineYBounds(Bounds bounds, int index) { | |
600 if (!(bounds instanceof DoubleBounds)) { | |
601 logger.warn("Unsupported Bounds type: " + bounds.getClass()); | |
602 return; | |
603 } | |
604 | |
605 DoubleBounds dBounds = (DoubleBounds) bounds; | |
606 | |
607 if (dBounds == null | |
608 || Double.isNaN((Double) dBounds.getLower()) | |
609 || Double.isNaN((Double) dBounds.getUpper())) { | |
610 return; | |
611 } | |
612 | |
613 Bounds old = getYBounds(index); | |
614 | |
615 if (old != null) { | |
616 dBounds = (DoubleBounds) dBounds.combine(old); | |
617 } | |
618 | |
619 setYBounds(index, dBounds); | |
620 } | |
621 | |
622 | |
623 /** | |
624 * If no data is visible, draw at least empty axis. | |
625 */ | |
626 private void recoverEmptyPlot(XYPlot plot) { | |
627 if (plot.getRangeAxis() == null) { | |
628 logger.debug("debug: No range axis"); | |
629 plot.setRangeAxis(createYAxis(0)); | |
630 } | |
631 } | |
632 | |
633 | |
634 /** | |
635 * Expands X axes if only a point is shown. | |
636 */ | |
637 private void preparePointRanges(XYPlot plot) { | |
638 for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { | |
639 | |
640 Integer key = Integer.valueOf(i); | |
641 Bounds b = getXBounds(key); | |
642 | |
643 | |
644 if (b != null && b.getLower().equals(b.getUpper())) { | |
645 logger.debug("Check whether to expand a x axis.i ("+b.getLower() + "-" + b.getUpper()+")"); | |
646 setXBounds(key, ChartHelper.expandBounds(b, 5)); | |
647 } | |
648 } | |
649 } | |
650 | |
651 | |
652 /** | |
653 * This method zooms the plot to the specified ranges in the attribute | |
654 * document or to the ranges specified by the min/max values in the | |
655 * datasets. <b>Note:</b> We determine the range manually if no zoom ranges | |
656 * are given, because JFreeCharts auto-zoom adds a margin to the left and | |
657 * right of the data area. | |
658 * | |
659 * @param plot The XYPlot. | |
660 */ | |
661 protected void autoZoom(XYPlot plot) { | |
662 logger.debug("Zoom to specified ranges."); | |
663 | |
664 Range xrange = getDomainAxisRange(); | |
665 Range yrange = getValueAxisRange(); | |
666 | |
667 ValueAxis xAxis = plot.getDomainAxis(); | |
668 | |
669 Range fixedXRange = getRangeForAxisFromSettings("X"); | |
670 if (fixedXRange != null) { | |
671 xAxis.setRange(fixedXRange); | |
672 } | |
673 else { | |
674 zoomX(plot, xAxis, getXBounds(0), xrange); | |
675 } | |
676 | |
677 for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { | |
678 ValueAxis yaxis = plot.getRangeAxis(i); | |
679 | |
680 if (yaxis instanceof IdentifiableNumberAxis) { | |
681 IdentifiableNumberAxis idAxis = (IdentifiableNumberAxis) yaxis; | |
682 | |
683 Range fixedRange = getRangeForAxisFromSettings(idAxis.getId()); | |
684 if (fixedRange != null) { | |
685 yaxis.setRange(fixedRange); | |
686 continue; | |
687 } | |
688 } | |
689 | |
690 if (yaxis == null) { | |
691 logger.debug("Zoom problem: no Y Axis for index: " + i); | |
692 continue; | |
693 } | |
694 | |
695 logger.debug("Prepare zoom settings for y axis at index: " + i); | |
696 zoomY(plot, yaxis, getYBounds(Integer.valueOf(i)), yrange); | |
697 } | |
698 } | |
699 | |
700 | |
701 protected Range getDomainAxisRange() { | |
702 String[] ranges = getDomainAxisRangeFromRequest(); | |
703 | |
704 if (ranges == null || ranges.length < 2) { | |
705 logger.debug("No zoom range for domain axis specified."); | |
706 return null; | |
707 } | |
708 | |
709 if (ranges[0].length() > 0 && ranges[1].length() > 0) { | |
710 try { | |
711 double from = Double.parseDouble(ranges[0]); | |
712 double to = Double.parseDouble(ranges[1]); | |
713 | |
714 if (from == 0 && to == 0) { | |
715 logger.debug("No range specified. Lower and upper X == 0"); | |
716 return null; | |
717 } | |
718 | |
719 if (from > to) { | |
720 double tmp = to; | |
721 to = from; | |
722 from = tmp; | |
723 } | |
724 | |
725 return new Range(from, to); | |
726 } | |
727 catch (NumberFormatException nfe) { | |
728 logger.warn("Wrong values for domain axis range."); | |
729 } | |
730 } | |
731 | |
732 return null; | |
733 } | |
734 | |
735 | |
736 protected Range getValueAxisRange() { | |
737 String[] ranges = getValueAxisRangeFromRequest(); | |
738 | |
739 if (ranges == null || ranges.length < 2) { | |
740 logger.debug("No range specified. Lower and upper Y == 0"); | |
741 return null; | |
742 } | |
743 | |
744 if (ranges[0].length() > 0 && ranges[1].length() > 0) { | |
745 try { | |
746 double from = Double.parseDouble(ranges[0]); | |
747 double to = Double.parseDouble(ranges[1]); | |
748 | |
749 if (from == 0 && to == 0) { | |
750 logger.debug("No range specified. Lower and upper Y == 0"); | |
751 return null; | |
752 } | |
753 | |
754 return from > to | |
755 ? new Range(to, from) | |
756 : new Range(from, to); | |
757 } | |
758 catch (NumberFormatException nfe) { | |
759 logger.warn("Wrong values for value axis range."); | |
760 } | |
761 } | |
762 | |
763 return null; | |
764 } | |
765 | |
766 | |
767 protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { | |
768 return zoom(plot, axis, bounds, x); | |
769 } | |
770 | |
771 | |
772 protected boolean zoomY(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { | |
773 return zoom(plot, axis, bounds, x); | |
774 } | |
775 | |
776 | |
777 /** | |
778 * Zooms the x axis to the range specified in the attribute document. | |
779 * | |
780 * @param plot The XYPlot. | |
781 * @param axis The axis the shoud be modified. | |
782 * @param bounds The whole range specified by a dataset. | |
783 * @param x A user defined range (null permitted). | |
784 * | |
785 * @return true, if a zoom range was specified, otherwise false. | |
786 */ | |
787 protected boolean zoom(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) { | |
788 | |
789 if (bounds == null) { | |
790 return false; | |
791 } | |
792 | |
793 if (x != null) { | |
794 Bounds computed = calculateZoom(bounds, x); | |
795 computed.applyBounds(axis, AXIS_SPACE); | |
796 | |
797 logger.debug("Zoom axis to: " + computed); | |
798 | |
799 return true; | |
800 } | |
801 | |
802 bounds.applyBounds(axis, AXIS_SPACE); | |
803 return false; | |
804 } | |
805 | |
806 /** | |
807 * Calculates the start and end km for zoomed charts. | |
808 * @param bounds The given total bounds (unzoomed). | |
809 * @param range The range specifying the zoom. | |
810 * | |
811 * @return The start and end km for the zoomed chart. | |
812 */ | |
813 protected Bounds calculateZoom(Bounds bounds, Range range) { | |
814 double min = bounds.getLower().doubleValue(); | |
815 double max = bounds.getUpper().doubleValue(); | |
816 | |
817 if (logger.isDebugEnabled()) { | |
818 logger.debug("Minimum is: " + min); | |
819 logger.debug("Maximum is: " + max); | |
820 logger.debug("Lower zoom is: " + range.getLowerBound()); | |
821 logger.debug("Upper zoom is: " + range.getUpperBound()); | |
822 } | |
823 | |
824 double diff = max > min ? max - min : min - max; | |
825 | |
826 DoubleBounds computed = new DoubleBounds( | |
827 min + range.getLowerBound() * diff, | |
828 min + range.getUpperBound() * diff); | |
829 return computed; | |
830 } | |
831 | |
832 /** | |
833 * Extract the minimum and maximum values for x and y axes | |
834 * which are stored in <i>xRanges</i> and <i>yRanges</i>. | |
835 * | |
836 * @param index The index of the y-Axis. | |
837 * | |
838 * @return a Range[] as follows: [x-Range, y-Range]. | |
839 */ | |
840 @Override | |
841 public Range[] getRangesForAxis(int index) { | |
842 logger.debug("getRangesForAxis " + index); | |
843 | |
844 Bounds rx = getXBounds(Integer.valueOf(0)); | |
845 Bounds ry = getYBounds(Integer.valueOf(index)); | |
846 | |
847 if (rx == null) { | |
848 logger.warn("Range for x axis not set." + | |
849 " Using default values: 0 - 1."); | |
850 rx = new DoubleBounds(0, 1); | |
851 } | |
852 if (ry == null) { | |
853 logger.warn("Range for y" + index + | |
854 " axis not set. Using default values: 0 - 1."); | |
855 ry = new DoubleBounds(0, 1); | |
856 } | |
857 | |
858 return new Range[] { | |
859 new Range(rx.getLower().doubleValue(), rx.getUpper().doubleValue()), | |
860 new Range(ry.getLower().doubleValue(), ry.getUpper().doubleValue()) | |
861 }; | |
862 } | |
863 | |
864 | |
865 /** Get X (usually horizontal) extent for given axis. */ | |
866 @Override | |
867 public Bounds getXBounds(int axis) { | |
868 return xBounds.get(axis); | |
869 } | |
870 | |
871 | |
872 /** Set X (usually horizontal) extent for given axis. */ | |
873 @Override | |
874 protected void setXBounds(int axis, Bounds bounds) { | |
875 if (bounds.getLower() == bounds.getUpper()) { | |
876 xBounds.put(axis, ChartHelper.expandBounds(bounds, 5d)); | |
877 } | |
878 else { | |
879 xBounds.put(axis, bounds); | |
880 } | |
881 } | |
882 | |
883 | |
884 /** Get Y (usually vertical) extent for given axis. */ | |
885 @Override | |
886 public Bounds getYBounds(int axis) { | |
887 return yBounds.get(axis); | |
888 } | |
889 | |
890 | |
891 /** Set Y (usually vertical) extent for given axis. */ | |
892 @Override | |
893 protected void setYBounds(int axis, Bounds bounds) { | |
894 yBounds.put(axis, bounds); | |
895 } | |
896 | |
897 | |
898 /** | |
899 * Adjusts the axes of a plot. This method sets the <i>labelFont</i> of the | |
900 * X axis. | |
901 * | |
902 * (Duplicate in TimeseriesChartGenerator) | |
903 * | |
904 * @param plot The XYPlot of the chart. | |
905 */ | |
906 protected void adjustAxes(XYPlot plot) { | |
907 ValueAxis xaxis = plot.getDomainAxis(); | |
908 | |
909 ChartSettings chartSettings = getChartSettings(); | |
910 if (chartSettings == null) { | |
911 return; | |
912 } | |
913 | |
914 Font labelFont = new Font( | |
915 DEFAULT_FONT_NAME, | |
916 Font.BOLD, | |
917 getXAxisLabelFontSize()); | |
918 | |
919 xaxis.setLabelFont(labelFont); | |
920 xaxis.setTickLabelFont(labelFont); | |
921 } | |
922 | |
923 | |
924 /** | |
925 * This method walks over all axes (domain and range) of <i>plot</i> and | |
926 * calls localizeDomainAxis() for domain axes or localizeRangeAxis() for | |
927 * range axes. | |
928 * | |
929 * @param plot The XYPlot. | |
930 */ | |
931 private void localizeAxes(XYPlot plot) { | |
932 for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { | |
933 ValueAxis axis = plot.getDomainAxis(i); | |
934 | |
935 if (axis != null) { | |
936 localizeDomainAxis(axis); | |
937 } | |
938 else { | |
939 logger.warn("Domain axis at " + i + " is null."); | |
940 } | |
941 } | |
942 | |
943 for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { | |
944 ValueAxis axis = plot.getRangeAxis(i); | |
945 | |
946 if (axis != null) { | |
947 localizeRangeAxis(axis); | |
948 } | |
949 else { | |
950 logger.warn("Range axis at " + i + " is null."); | |
951 } | |
952 } | |
953 } | |
954 | |
955 | |
956 /** | |
957 * Overrides the NumberFormat with the NumberFormat for the current locale | |
958 * that is provided by getLocale(). | |
959 * | |
960 * @param domainAxis The domain axis that needs localization. | |
961 */ | |
962 protected void localizeDomainAxis(ValueAxis domainAxis) { | |
963 NumberFormat nf = NumberFormat.getInstance(getLocale()); | |
964 ((NumberAxis) domainAxis).setNumberFormatOverride(nf); | |
965 } | |
966 | |
967 | |
968 /** | |
969 * Overrides the NumberFormat with the NumberFormat for the current locale | |
970 * that is provided by getLocale(). | |
971 * | |
972 * @param rangeAxis The domain axis that needs localization. | |
973 */ | |
974 protected void localizeRangeAxis(ValueAxis rangeAxis) { | |
975 NumberFormat nf = NumberFormat.getInstance(getLocale()); | |
976 ((NumberAxis) rangeAxis).setNumberFormatOverride(nf); | |
977 } | |
978 | |
979 | |
980 /** | |
981 * Do Points out. | |
982 */ | |
983 protected void doPoints( | |
984 Object o, | |
985 ArtifactAndFacet aandf, | |
986 Document theme, | |
987 boolean visible, | |
988 int axisIndex | |
989 ) { | |
990 String seriesName = aandf.getFacetDescription(); | |
991 XYSeries series = new StyledXYSeries(seriesName, theme); | |
992 | |
993 // Add text annotations for single points. | |
994 List<XYTextAnnotation> xy = new ArrayList<XYTextAnnotation>(); | |
995 | |
996 try { | |
997 JSONArray points = new JSONArray((String) o); | |
998 for (int i = 0, P = points.length(); i < P; i++) { | |
999 JSONArray array = points.getJSONArray(i); | |
1000 double x = array.getDouble(0); | |
1001 double y = array.getDouble(1); | |
1002 String name = array.getString(2); | |
1003 boolean act = array.getBoolean(3); | |
1004 if (!act) { | |
1005 continue; | |
1006 } | |
1007 //logger.debug(" x " + x + " y " + y ); | |
1008 series.add(x, y, false); | |
1009 xy.add(new CollisionFreeXYTextAnnotation(name, x, y)); | |
1010 } | |
1011 } | |
1012 catch(JSONException e){ | |
1013 logger.error("Could not decode json."); | |
1014 } | |
1015 | |
1016 FLYSAnnotation annotations = new FLYSAnnotation(null, null, null, theme); | |
1017 annotations.setTextAnnotations(xy); | |
1018 | |
1019 // Do not generate second legend entry. (null was passed for the aand before). | |
1020 doAnnotations(annotations, null, theme, visible); | |
1021 addAxisSeries(series, axisIndex, visible); | |
1022 } | |
1023 | |
1024 | |
1025 /** | |
1026 * Create a hash from a legenditem. | |
1027 * This hash can then be used to merge legend items labels. | |
1028 * @return hash for given legenditem to identify mergeables. | |
1029 */ | |
1030 public static String legendItemHash(LegendItem li) { | |
1031 // TODO Do proper implementation. Ensure that only mergable sets are created. | |
1032 // getFillPaint() | |
1033 // getFillPaintTransformer() | |
1034 // getLabel() | |
1035 // getLine() | |
1036 // getLinePaint() | |
1037 // getLineStroke() | |
1038 // getOutlinePaint() | |
1039 // getOutlineStroke() | |
1040 // Shape getShape() | |
1041 // String getToolTipText() | |
1042 // String getURLText() | |
1043 // boolean isLineVisible() | |
1044 // boolean isShapeFilled() | |
1045 // boolean isShapeOutlineVisible() | |
1046 // boolean isShapeVisible() | |
1047 String hash = li.getLinePaint().toString(); | |
1048 String label = li.getLabel(); | |
1049 if (label.startsWith("W (") || label.startsWith("W(")) { | |
1050 hash += "-W-"; | |
1051 } | |
1052 else if (label.startsWith("Q(") || label.startsWith("Q (")) { | |
1053 hash += "-Q-"; | |
1054 } | |
1055 | |
1056 // WQ.java holds example of using regex Matcher/Pattern. | |
1057 | |
1058 return hash; | |
1059 } | |
1060 | |
1061 /** True if x axis has been inverted. */ | |
1062 public boolean isInverted() { | |
1063 return inverted; | |
1064 } | |
1065 | |
1066 | |
1067 /** Set to true if x axis has been inverted. */ | |
1068 public void setInverted(boolean inverted) { | |
1069 this.inverted = inverted; | |
1070 } | |
1071 | |
1072 | |
1073 } | |
1074 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : |