comparison flys-artifacts/src/main/java/de/intevation/flys/exports/ChartGenerator.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 6aeb71517136
comparison
equal deleted inserted replaced
2241:2b232871ba28 2242:7e8e1d5384c0
1 package de.intevation.flys.exports; 1 package de.intevation.flys.exports;
2 2
3 import java.awt.Color; 3 import java.awt.Color;
4 import java.awt.Font; 4 import java.awt.Font;
5 import java.awt.Paint;
6 import java.awt.TexturePaint;
7 import java.awt.geom.Rectangle2D;
8 import java.awt.image.BufferedImage;
5 9
6 import java.io.IOException; 10 import java.io.IOException;
7 import java.io.OutputStream; 11 import java.io.OutputStream;
8 12
9 import java.util.ArrayList; 13 import java.util.ArrayList;
10 import java.util.List; 14 import java.util.List;
11 import java.util.Locale; 15 import java.util.Locale;
16 import java.util.Map;
12 import java.util.TreeMap; 17 import java.util.TreeMap;
13 import java.util.SortedMap; 18 import java.util.SortedMap;
14 19
15 import javax.xml.xpath.XPathConstants; 20 import javax.xml.xpath.XPathConstants;
16 21
19 import org.w3c.dom.Document; 24 import org.w3c.dom.Document;
20 import org.w3c.dom.Element; 25 import org.w3c.dom.Element;
21 26
22 import org.jfree.chart.JFreeChart; 27 import org.jfree.chart.JFreeChart;
23 import org.jfree.chart.LegendItem; 28 import org.jfree.chart.LegendItem;
29 import org.jfree.chart.LegendItemCollection;
24 import org.jfree.chart.axis.NumberAxis; 30 import org.jfree.chart.axis.NumberAxis;
31 import org.jfree.chart.plot.XYPlot;
32 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
25 import org.jfree.data.Range; 33 import org.jfree.data.Range;
34 import org.jfree.data.general.Series;
26 import org.jfree.data.xy.XYDataset; 35 import org.jfree.data.xy.XYDataset;
27 36
28 import de.intevation.artifacts.Artifact; 37 import de.intevation.artifacts.Artifact;
29 import de.intevation.artifacts.CallContext; 38 import de.intevation.artifacts.CallContext;
30 import de.intevation.artifacts.CallMeta; 39 import de.intevation.artifacts.CallMeta;
38 47
39 import de.intevation.flys.model.River; 48 import de.intevation.flys.model.River;
40 49
41 import de.intevation.flys.artifacts.FLYSArtifact; 50 import de.intevation.flys.artifacts.FLYSArtifact;
42 import de.intevation.flys.artifacts.resources.Resources; 51 import de.intevation.flys.artifacts.resources.Resources;
52 import de.intevation.flys.jfree.EnhancedLineAndShapeRenderer;
53 import de.intevation.flys.jfree.StableXYDifferenceRenderer;
54 import de.intevation.flys.jfree.StyledAreaSeriesCollection;
55 import de.intevation.flys.jfree.StyledXYSeries;
43 import de.intevation.flys.utils.FLYSUtils; 56 import de.intevation.flys.utils.FLYSUtils;
44 import de.intevation.flys.utils.ThemeAccess; 57 import de.intevation.flys.utils.ThemeAccess;
45 58
46 59
47 /** 60 /**
111 124
112 public interface AxisDataset { 125 public interface AxisDataset {
113 126
114 void addDataset(XYDataset dataset); 127 void addDataset(XYDataset dataset);
115 128
129 XYDataset[] getDatasets();
130
116 boolean isEmpty(); 131 boolean isEmpty();
132
133 void setRange(Range range);
134
135 Range getRange();
136
137 boolean isArea(XYDataset dataset);
138
139 void setPlotAxisIndex(int idx);
140
141 int getPlotAxisIndex();
117 142
118 } // end of AxisDataset interface 143 } // end of AxisDataset interface
119 144
120 145
121 146
142 Document attr, 167 Document attr,
143 boolean visible); 168 boolean visible);
144 169
145 170
146 protected abstract YAxisWalker getYAxisWalker(); 171 protected abstract YAxisWalker getYAxisWalker();
172
173
174 protected abstract Series getSeriesOf(XYDataset dataset, int idx);
175
176
177 /**
178 * This method is used to set the range of the X axis at index <i>axis</i>.
179 *
180 * @param axis The index of an X axis.
181 * @param range The new range for the X axis at index <i>axis</i>.
182 */
183 protected abstract void setXRange(int axis, Range range);
184
185
186 /**
187 * This method is used to set the range of the Y axis at index <i>axis</i>.
188 *
189 * @param axis The index of an Y axis.
190 * @param range The new range for the Y axis at index <i>axis</i>.
191 */
192 protected abstract void setYRange(int axis, Range range);
147 193
148 194
149 /** 195 /**
150 * Returns the default title of a chart. 196 * Returns the default title of a chart.
151 * 197 *
792 838
793 AxisDataset axisDataset = getAxisDataset(idx); 839 AxisDataset axisDataset = getAxisDataset(idx);
794 840
795 Range[] xyRanges = ChartHelper.getRanges(dataset); 841 Range[] xyRanges = ChartHelper.getRanges(dataset);
796 842
843 if (xyRanges == null) {
844 logger.warn("Skip XYDataset for Axis (invalid ranges): " + idx);
845 return;
846 }
847
797 if (visible) { 848 if (visible) {
798 logger.debug("Add new AxisDataset at index: " + idx); 849 if (logger.isDebugEnabled()) {
850 logger.debug("Add new AxisDataset at index: " + idx);
851 logger.debug("X extent: " + xyRanges[0]);
852 logger.debug("Y extent: " + xyRanges[1]);
853 }
854
799 axisDataset.addDataset(dataset); 855 axisDataset.addDataset(dataset);
800 combineXRanges(xyRanges[0], 0); 856 combineXRanges(xyRanges[0], 0);
801 } 857 }
802 else { 858 else {
803 combineXRanges(xyRanges[0], 0); 859 combineXRanges(xyRanges[0], 0);
1031 return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT }; 1087 return new int[] { DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT };
1032 } 1088 }
1033 1089
1034 1090
1035 /** 1091 /**
1092 * Add datasets stored in instance variable <i>datasets</i> to plot.
1093 * <i>datasets</i> actually stores instances of AxisDataset, so each of this
1094 * datasets is mapped to a specific axis as well.
1095 *
1096 * @param plot plot to add datasets to.
1097 */
1098 protected void addDatasets(XYPlot plot) {
1099 // AxisDatasets are sorted, but some might be empty.
1100 // Thus, generate numbering on the fly.
1101 int axisIndex = 0;
1102 int datasetIndex = 0;
1103
1104 for (Map.Entry<Integer, AxisDataset> entry: datasets.entrySet()) {
1105 if (!entry.getValue().isEmpty()) {
1106 // Add axis and range information.
1107 AxisDataset axisDataset = entry.getValue();
1108 NumberAxis axis = createYAxis(entry.getKey());
1109
1110 plot.setRangeAxis(axisIndex, axis);
1111
1112 if (axis.getAutoRangeIncludesZero()) {
1113 axisDataset.setRange(
1114 Range.expandToInclude(axisDataset.getRange(), 0d));
1115 }
1116
1117 setYRange(axisIndex, expandPointRange(axisDataset.getRange()));
1118
1119 // Add contained datasets, mapping to axis.
1120 for (XYDataset dataset: axisDataset.getDatasets()) {
1121 plot.setDataset(datasetIndex, dataset);
1122 plot.mapDatasetToRangeAxis(datasetIndex, axisIndex);
1123
1124 applyThemes(plot, (XYDataset) dataset,
1125 datasetIndex,
1126 axisDataset.isArea((XYDataset) dataset));
1127
1128 datasetIndex++;
1129 }
1130
1131 axisDataset.setPlotAxisIndex(axisIndex);
1132 axisIndex++;
1133 }
1134 }
1135 }
1136
1137
1138 /**
1139 * @param idx "index" of dataset/series (first dataset to be drawn has
1140 * index 0), correlates with renderer index.
1141 * @param isArea true if the series describes an area and shall be rendered
1142 * as such.
1143 * @return idx increased by number of items addded.
1144 */
1145 protected void applyThemes(
1146 XYPlot plot,
1147 XYDataset series,
1148 int idx,
1149 boolean isArea
1150 ) {
1151 if (isArea) {
1152 applyAreaTheme(plot, (StyledAreaSeriesCollection) series, idx);
1153 }
1154 else {
1155 applyLineTheme(plot, series, idx);
1156 }
1157 }
1158
1159
1160 /**
1161 * This method applies the themes defined in the series itself. Therefore,
1162 * <i>StyledXYSeries.applyTheme()</i> is called, which modifies the renderer
1163 * for the series.
1164 *
1165 * @param plot The plot.
1166 * @param dataset The XYDataset which needs to support Series objects.
1167 * @param idx The index of the renderer / dataset.
1168 */
1169 protected void applyLineTheme(XYPlot plot, XYDataset dataset, int idx) {
1170 LegendItemCollection lic = new LegendItemCollection();
1171 LegendItemCollection anno = plot.getFixedLegendItems();
1172
1173 Font legendFont = createLegendLabelFont();
1174
1175 XYLineAndShapeRenderer renderer = createRenderer(plot, idx);
1176
1177 for (int s = 0, num = dataset.getSeriesCount(); s < num; s++) {
1178 Series series = getSeriesOf(dataset, s);
1179
1180 if (series instanceof StyledXYSeries) {
1181 ((StyledXYSeries) series).applyTheme(renderer, s);
1182 }
1183
1184 // special case: if there is just one single item, we need to enable
1185 // points for this series, otherwise we would not see anything in
1186 // the chart area.
1187 if (series.getItemCount() == 1) {
1188 renderer.setSeriesShapesVisible(s, true);
1189 }
1190
1191 LegendItem legendItem = renderer.getLegendItem(idx, s);
1192 if (legendItem != null) {
1193 legendItem.setLabelFont(legendFont);
1194 lic.add(legendItem);
1195 }
1196 else {
1197 logger.warn("Could not get LegentItem for renderer: "
1198 + idx + ", series-idx " + s);
1199 }
1200 }
1201
1202 if (anno != null) {
1203 lic.addAll(anno);
1204 }
1205
1206 plot.setFixedLegendItems(lic);
1207
1208 plot.setRenderer(idx, renderer);
1209 }
1210
1211
1212 /**
1213 * @param plot The plot.
1214 * @param area A StyledAreaSeriesCollection object.
1215 * @param idx The index of the dataset.
1216 *
1217 * @return
1218 */
1219 protected void applyAreaTheme(
1220 XYPlot plot,
1221 StyledAreaSeriesCollection area,
1222 int idx
1223 ) {
1224 LegendItemCollection lic = new LegendItemCollection();
1225 LegendItemCollection anno = plot.getFixedLegendItems();
1226
1227 Font legendFont = createLegendLabelFont();
1228
1229 logger.debug("Registering an 'area'renderer at idx: " + idx);
1230
1231 StableXYDifferenceRenderer dRenderer =
1232 new StableXYDifferenceRenderer();
1233
1234 if (area.getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
1235 dRenderer.setPositivePaint(createTransparentPaint());
1236 }
1237
1238 plot.setRenderer(idx, dRenderer);
1239
1240 area.applyTheme(dRenderer);
1241
1242 LegendItem legendItem = dRenderer.getLegendItem(idx, 0);
1243 if (legendItem != null) {
1244 legendItem.setLabelFont(legendFont);
1245 lic.add(legendItem);
1246 }
1247 else {
1248 logger.warn("Could not get LegentItem for renderer: "
1249 + idx + ", series-idx " + 0);
1250 }
1251
1252 if (anno != null) {
1253 lic.addAll(anno);
1254 }
1255
1256 plot.setFixedLegendItems(lic);
1257 }
1258
1259
1260 /**
1261 * Expands a given range if it collapses into one point.
1262 *
1263 * @param Range to be expanded if upper == lower bound.
1264 */
1265 private Range expandPointRange(Range range) {
1266 if (range != null && range.getLowerBound() == range.getUpperBound()) {
1267 return ChartHelper.expandRange(range, 5);
1268 }
1269 return range;
1270 }
1271
1272
1273 /**
1274 * Creates a new instance of EnhancedLineAndShapeRenderer.
1275 *
1276 * @param plot The plot which is set for the new renderer.
1277 * @param idx This value is not used in the current implementation.
1278 *
1279 * @return a new instance of EnhancedLineAndShapeRenderer.
1280 */
1281 protected XYLineAndShapeRenderer createRenderer(XYPlot plot, int idx) {
1282 logger.debug("Create EnhancedLineAndShapeRenderer for idx: " + idx);
1283
1284 EnhancedLineAndShapeRenderer r =
1285 new EnhancedLineAndShapeRenderer(true, false);
1286
1287 r.setPlot(plot);
1288
1289 return r;
1290 }
1291
1292
1293 /**
1036 * Creates a new instance of <i>IdentifiableNumberAxis</i>. 1294 * Creates a new instance of <i>IdentifiableNumberAxis</i>.
1037 * 1295 *
1038 * @param idx The index of the new axis. 1296 * @param idx The index of the new axis.
1039 * @param label The label of the new axis. 1297 * @param label The label of the new axis.
1040 * 1298 *
1101 return new Font( 1359 return new Font(
1102 DEFAULT_FONT_NAME, 1360 DEFAULT_FONT_NAME,
1103 Font.PLAIN, 1361 Font.PLAIN,
1104 getLegendFontSize() 1362 getLegendFontSize()
1105 ); 1363 );
1364 }
1365
1366
1367 /**
1368 * Returns a transparently textured paint.
1369 *
1370 * @return a transparently textured paint.
1371 */
1372 protected static Paint createTransparentPaint() {
1373 // TODO why not use a transparent color?
1374 BufferedImage texture = new BufferedImage(
1375 1, 1, BufferedImage.TYPE_4BYTE_ABGR);
1376
1377 return new TexturePaint(
1378 texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
1106 } 1379 }
1107 1380
1108 1381
1109 protected void preparePDFContext(CallContext context) { 1382 protected void preparePDFContext(CallContext context) {
1110 int[] dimension = getExportDimension(); 1383 int[] dimension = getExportDimension();

http://dive4elements.wald.intevation.org