comparison artifacts/src/main/java/org/dive4elements/river/exports/DiagramGenerator.java @ 7116:3c7471b929d1

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

http://dive4elements.wald.intevation.org