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