comparison flys-artifacts/src/main/java/org/dive4elements/river/exports/XYChartGenerator.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/XYChartGenerator.java@c2a590925ec3
children
comparison
equal deleted inserted replaced
5830:160f53ee0870 5831:bd047b71ab37
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 :

http://dive4elements.wald.intevation.org