comparison flys-artifacts/src/main/java/de/intevation/flys/exports/XYChartGenerator.java @ 2242:7e8e1d5384c0

Further refactoring of XYChartGenerator / ChartGenerator with the result, that timerange charts are now able to display lines. flys-artifacts/trunk@3890 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Fri, 03 Feb 2012 09:39:22 +0000
parents 23c7c51df772
children 99ef93ce18bd
comparison
equal deleted inserted replaced
2241:2b232871ba28 2242:7e8e1d5384c0
3 import java.awt.BasicStroke; 3 import java.awt.BasicStroke;
4 import java.awt.Color; 4 import java.awt.Color;
5 import java.awt.Font; 5 import java.awt.Font;
6 import java.awt.Paint; 6 import java.awt.Paint;
7 import java.awt.Stroke; 7 import java.awt.Stroke;
8 import java.awt.TexturePaint;
9
10 import java.awt.geom.Rectangle2D;
11
12 import java.awt.image.BufferedImage;
13
14 8
15 import java.text.NumberFormat; 9 import java.text.NumberFormat;
16 10
17 import java.util.ArrayList; 11 import java.util.ArrayList;
18 import java.util.HashMap; 12 import java.util.HashMap;
19 import java.util.TreeMap;
20 import java.util.List; 13 import java.util.List;
21 import java.util.Map; 14 import java.util.Map;
22 import java.util.SortedMap;
23 15
24 import org.w3c.dom.Document; 16 import org.w3c.dom.Document;
25 17
26 import org.apache.log4j.Logger; 18 import org.apache.log4j.Logger;
27 19
28 import org.jfree.chart.ChartFactory; 20 import org.jfree.chart.ChartFactory;
29 import org.jfree.chart.JFreeChart; 21 import org.jfree.chart.JFreeChart;
30 import org.jfree.chart.LegendItem;
31 import org.jfree.chart.LegendItemCollection; 22 import org.jfree.chart.LegendItemCollection;
32 import org.jfree.chart.annotations.XYBoxAnnotation; 23 import org.jfree.chart.annotations.XYBoxAnnotation;
33 import org.jfree.chart.annotations.XYLineAnnotation; 24 import org.jfree.chart.annotations.XYLineAnnotation;
34 import org.jfree.chart.annotations.XYTextAnnotation; 25 import org.jfree.chart.annotations.XYTextAnnotation;
35 import org.jfree.chart.axis.NumberAxis; 26 import org.jfree.chart.axis.NumberAxis;
36 import org.jfree.chart.axis.ValueAxis; 27 import org.jfree.chart.axis.ValueAxis;
37 import org.jfree.chart.plot.PlotOrientation; 28 import org.jfree.chart.plot.PlotOrientation;
38 import org.jfree.chart.plot.XYPlot; 29 import org.jfree.chart.plot.XYPlot;
39 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
40 import org.jfree.data.Range; 30 import org.jfree.data.Range;
31 import org.jfree.data.general.Series;
41 import org.jfree.data.xy.XYSeries; 32 import org.jfree.data.xy.XYSeries;
42 import org.jfree.data.xy.XYSeriesCollection; 33 import org.jfree.data.xy.XYSeriesCollection;
43 import org.jfree.data.xy.XYDataset; 34 import org.jfree.data.xy.XYDataset;
44 35
45 import org.jfree.ui.RectangleInsets; 36 import org.jfree.ui.RectangleInsets;
46 import org.jfree.ui.TextAnchor; 37 import org.jfree.ui.TextAnchor;
47 38
48
49 import de.intevation.artifactdatabase.state.Facet; 39 import de.intevation.artifactdatabase.state.Facet;
50 40
51
52 import de.intevation.flys.jfree.EnhancedLineAndShapeRenderer;
53 import de.intevation.flys.jfree.FLYSAnnotation; 41 import de.intevation.flys.jfree.FLYSAnnotation;
54 import de.intevation.flys.jfree.StableXYDifferenceRenderer;
55 import de.intevation.flys.jfree.StickyAxisAnnotation; 42 import de.intevation.flys.jfree.StickyAxisAnnotation;
56 import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation; 43 import de.intevation.flys.jfree.CollisionFreeXYTextAnnotation;
57 import de.intevation.flys.jfree.StyledAreaSeriesCollection; 44 import de.intevation.flys.jfree.StyledAreaSeriesCollection;
58 import de.intevation.flys.jfree.StyledXYSeries; 45 import de.intevation.flys.jfree.StyledXYSeries;
59 46
124 /** Add a dataset, include its range. */ 111 /** Add a dataset, include its range. */
125 public void addDataset(XYSeries series) { 112 public void addDataset(XYSeries series) {
126 addDataset(new XYSeriesCollection(series)); 113 addDataset(new XYSeriesCollection(series));
127 } 114 }
128 115
116
117 @Override
118 public void setRange(Range range) {
119 this.range = range;
120 }
121
122
123 @Override
124 public Range getRange() {
125 return range;
126 }
127
128
129 @Override
130 public XYDataset[] getDatasets() {
131 return (XYDataset[])
132 datasets.toArray(new XYDataset[datasets.size()]);
133 }
134
129 public void addArea(StyledAreaSeriesCollection series) { 135 public void addArea(StyledAreaSeriesCollection series) {
130 this.datasets.add(series); 136 this.datasets.add(series);
131 } 137 }
132 138
133 /** True if to be renedered as area. */ 139 /** True if to be renedered as area. */
134 public boolean isArea(XYSeriesCollection series) { 140 @Override
141 public boolean isArea(XYDataset series) {
135 return (series instanceof StyledAreaSeriesCollection); 142 return (series instanceof StyledAreaSeriesCollection);
136 } 143 }
137 144
138 /** Adjust range to include given dataset. */ 145 /** Adjust range to include given dataset. */
139 public void includeYRange(XYSeries dataset) { 146 public void includeYRange(XYSeries dataset) {
140 mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY())); 147 mergeRanges(new Range(dataset.getMinY(), dataset.getMaxY()));
141 } 148 }
142 149
143 /** True if no datasets given. */ 150 /** True if no datasets given. */
151 @Override
144 public boolean isEmpty() { 152 public boolean isEmpty() {
145 return this.datasets.isEmpty(); 153 return this.datasets.isEmpty();
146 } 154 }
147 155
148 /** Set the 'real' axis index that this axis is mapped to. */ 156 /** Set the 'real' axis index that this axis is mapped to. */
157 @Override
149 public void setPlotAxisIndex(int axisIndex) { 158 public void setPlotAxisIndex(int axisIndex) {
150 this.plotAxisIndex = axisIndex; 159 this.plotAxisIndex = axisIndex;
151 } 160 }
152 161
153 /** Get the 'real' axis index that this axis is mapped to. */ 162 /** Get the 'real' axis index that this axis is mapped to. */
163 @Override
154 public int getPlotAxisIndex() { 164 public int getPlotAxisIndex() {
155 return this.plotAxisIndex; 165 return this.plotAxisIndex;
156 } 166 }
157 } // class AxisDataset 167 } // class AxisDataset
158 168
235 245
236 // These have to go after the autozoom. 246 // These have to go after the autozoom.
237 addAnnotationsToRenderer(plot); 247 addAnnotationsToRenderer(plot);
238 248
239 return chart; 249 return chart;
250 }
251
252
253 @Override
254 protected Series getSeriesOf(XYDataset dataset, int idx) {
255 return ((XYSeriesCollection) dataset).getSeries(idx);
256 }
257
258
259 @Override
260 protected void setXRange(int axis, Range range) {
261 xRanges.put(Integer.valueOf(axis), range);
262 }
263
264
265 @Override
266 protected void setYRange(int axis, Range range) {
267 yRanges.put(Integer.valueOf(axis), range);
240 } 268 }
241 269
242 270
243 @Override 271 @Override
244 protected AxisDataset createAxisDataset(int idx) { 272 protected AxisDataset createAxisDataset(int idx) {
286 logger.debug("..............."); 314 logger.debug("...............");
287 } 315 }
288 316
289 317
290 /** 318 /**
291 * Add datasets to plot.
292 * @param plot plot to add datasets to.
293 */
294 protected void addDatasets(XYPlot plot) {
295 // AxisDatasets are sorted, but some might be empty.
296 // Thus, generate numbering on the fly.
297 int axisIndex = 0;
298 int datasetIndex = 0;
299 for (Map.Entry<Integer, AxisDataset> entry: datasets.entrySet()) {
300 if (!entry.getValue().isEmpty()) {
301 // Add axis and range information.
302 XYAxisDataset axisDataset = (XYAxisDataset) entry.getValue();
303 NumberAxis axis = createYAxis(entry.getKey());
304
305 plot.setRangeAxis(axisIndex, axis);
306 if (axis.getAutoRangeIncludesZero()) {
307 axisDataset.range = Range.expandToInclude(axisDataset.range, 0d);
308 }
309 yRanges.put(axisIndex, expandPointRange(axisDataset.range));
310
311 // Add contained datasets, mapping to axis.
312 for (XYDataset dataset: axisDataset.datasets) {
313 plot.setDataset(datasetIndex, dataset);
314 plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
315 applyThemes(plot, (XYSeriesCollection) dataset,
316 datasetIndex,
317 axisDataset.isArea((XYSeriesCollection)dataset));
318 datasetIndex++;
319 }
320 axisDataset.setPlotAxisIndex(axisIndex);
321 axisIndex++;
322 }
323 }
324 }
325
326
327 /**
328 * Registers an area to be drawn. 319 * Registers an area to be drawn.
329 * @param area Area to be drawn. 320 * @param area Area to be drawn.
330 * @param index 'axis index' 321 * @param index 'axis index'
331 * @param visible Whether or not to be visible (important for range calculations). 322 * @param visible Whether or not to be visible (important for range calculations).
332 */ 323 */
354 * @param series the dataseries to include in plot. 345 * @param series the dataseries to include in plot.
355 * @param index ('symbolic') index of the series and of its axis. 346 * @param index ('symbolic') index of the series and of its axis.
356 * @param visible whether or not the data should be plotted. 347 * @param visible whether or not the data should be plotted.
357 */ 348 */
358 public void addAxisSeries(XYSeries series, int index, boolean visible) { 349 public void addAxisSeries(XYSeries series, int index, boolean visible) {
359 addAxisDataset(new XYSeriesCollection(series), index, visible);
360
361 if (series == null) { 350 if (series == null) {
362 return; 351 return;
363 } 352 }
353
354 logger.debug("Y Range of XYSeries: " +
355 series.getMinY() + " | " + series.getMaxY());
356
357 addAxisDataset(new XYSeriesCollection(series), index, visible);
364 358
365 XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index); 359 XYAxisDataset axisDataset = (XYAxisDataset) getAxisDataset(index);
366 360
367 if (!visible) { 361 if (!visible) {
368 // Do this also when not visible to have axis scaled by default such 362 // Do this also when not visible to have axis scaled by default such
421 } 415 }
422 } 416 }
423 417
424 418
425 /** 419 /**
426 * Expands a given range if it collapses into one point.
427 * @param Range to be expanded if upper == lower bound.
428 */
429 private Range expandPointRange(Range range) {
430 if (range != null && range.getLowerBound() == range.getUpperBound()) {
431 return expandRange(range, 5);
432 }
433 return range;
434 }
435
436
437 /**
438 * Expands X axes if only a point is shown. 420 * Expands X axes if only a point is shown.
439 */ 421 */
440 private void preparePointRanges(XYPlot plot) { 422 private void preparePointRanges(XYPlot plot) {
441 for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) { 423 for (int i = 0, num = plot.getDomainAxisCount(); i < num; i++) {
442 logger.debug("Check whether to expand a x axis."); 424 logger.debug("Check whether to expand a x axis.");
443 Integer key = Integer.valueOf(i); 425 Integer key = Integer.valueOf(i);
444 426
445 Range r = xRanges.get(key); 427 Range r = xRanges.get(key);
446 if (r != null && r.getLowerBound() == r.getUpperBound()) { 428 if (r != null && r.getLowerBound() == r.getUpperBound()) {
447 xRanges.put(key, expandRange(r, 5)); 429 setXRange(key, ChartHelper.expandRange(r, 5));
448 } 430 }
449 } 431 }
450 }
451
452
453 /**
454 * Expand range by percent.
455 */
456 public static Range expandRange(Range range, double percent) {
457 if (range == null) {
458 return null;
459 }
460
461 double value = range.getLowerBound();
462 double expand = Math.abs(value / 100 * percent);
463
464 return expand != 0
465 ? new Range(value-expand, value+expand)
466 : new Range(-0.01 * percent, 0.01 * percent);
467 } 432 }
468 433
469 434
470 /** 435 /**
471 * This method zooms the plot to the specified ranges in the attribute 436 * This method zooms the plot to the specified ranges in the attribute
920 ((NumberAxis) rangeAxis).setNumberFormatOverride(nf); 885 ((NumberAxis) rangeAxis).setNumberFormatOverride(nf);
921 } 886 }
922 887
923 888
924 /** 889 /**
925 * @param idx "index" of dataset/series (first dataset to be drawn has
926 * index 0), correlates with renderer index.
927 * @param isArea true if the series describes an area and shall be rendered
928 * as such.
929 * @return idx increased by number of items addded.
930 */
931 protected int applyThemes(
932 XYPlot plot,
933 XYSeriesCollection series,
934 int idx,
935 boolean isArea
936 ) {
937 LegendItemCollection lic = new LegendItemCollection();
938 LegendItemCollection anno = plot.getFixedLegendItems();
939
940 Font legendFont = createLegendLabelFont();
941
942 int retidx = idx;
943
944 if (isArea) {
945 logger.debug("Registering an 'area'renderer at idx: " + idx);
946 StyledAreaSeriesCollection area = (StyledAreaSeriesCollection) series;
947
948 StableXYDifferenceRenderer dRenderer = new StableXYDifferenceRenderer();
949 if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
950 dRenderer.setPositivePaint(createTransparentPaint());
951 }
952 plot.setRenderer(idx, dRenderer);
953
954 area.applyTheme(dRenderer);
955
956 LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
957 if (legendItem != null) {
958 legendItem.setLabelFont(legendFont);
959 lic.add(legendItem);
960 }
961 else {
962 logger.warn("Could not get LegentItem for renderer: "
963 + idx + ", series-idx " + 0);
964 }
965 if (anno != null) {
966 lic.addAll(anno);
967 }
968 plot.setFixedLegendItems(lic);
969 return retidx + 1;
970 }
971
972 XYLineAndShapeRenderer renderer = getRenderer(plot, idx);
973
974 for (int s = 0, num = series.getSeriesCount(); s < num; s++) {
975 XYSeries serie = series.getSeries(s);
976
977 if (serie instanceof StyledXYSeries) {
978 ((StyledXYSeries) serie).applyTheme(renderer, s);
979 }
980
981 // special case: if there is just one single item, we need to enable
982 // points for this series, otherwise we would not see anything in
983 // the chart area.
984 if (serie.getItemCount() == 1) {
985 renderer.setSeriesShapesVisible(s, true);
986 }
987
988 LegendItem legendItem = renderer.getLegendItem(idx, s);
989 if (legendItem != null) {
990 legendItem.setLabelFont(legendFont);
991 lic.add(legendItem);
992 }
993 else {
994 logger.warn("Could not get LegentItem for renderer: "
995 + idx + ", series-idx " + s);
996 }
997 // TODO: why that? isnt renderer set per dataset not per series?
998 retidx++;
999 }
1000
1001 if (anno != null) {
1002 lic.addAll(anno);
1003 }
1004
1005 plot.setFixedLegendItems(lic);
1006
1007 plot.setRenderer(idx, renderer);
1008
1009 return retidx;
1010 }
1011
1012
1013 /** Returns a transparently textured paint. */
1014 // TODO why not use a transparent color?
1015 protected static Paint createTransparentPaint() {
1016 BufferedImage texture = new BufferedImage(
1017 1, 1, BufferedImage.TYPE_4BYTE_ABGR);
1018
1019 return new TexturePaint(
1020 texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
1021 }
1022
1023
1024 /**
1025 * Returns a new instance of EnhancedLineAndShapeRenderer always.
1026 */
1027 protected XYLineAndShapeRenderer getRenderer(XYPlot plot, int idx) {
1028 logger.debug("getRenderer: " + idx);
1029
1030 EnhancedLineAndShapeRenderer r =
1031 new EnhancedLineAndShapeRenderer(true, false);
1032
1033 r.setPlot(plot);
1034
1035 return r;
1036 }
1037
1038
1039 /**
1040 * Register annotations like MainValues for later plotting 890 * Register annotations like MainValues for later plotting
1041 * 891 *
1042 * @param o list of annotations (data of facet). 892 * @param o list of annotations (data of facet).
1043 * @param facet The facet. This facet does NOT support any data objects. Use 893 * @param facet The facet. This facet does NOT support any data objects. Use
1044 * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports 894 * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports

http://dive4elements.wald.intevation.org