comparison gnv-artifacts/src/main/java/de/intevation/gnv/chart/AdvancedHistogramDataset.java @ 1119:7c4f81f74c47

merged gnv-artifacts
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:00 +0200
parents f953c9a559d8
children
comparison
equal deleted inserted replaced
1027:fca4b5eb8d2f 1119:7c4f81f74c47
1 /*
2 * Copyright (c) 2010 by Intevation GmbH
3 *
4 * This program is free software under the LGPL (>=v2.1)
5 * Read the file LGPL.txt coming with the software for details
6 * or visit http://www.gnu.org/licenses/ if it does not exist.
7 */
8
9 package de.intevation.gnv.chart;
10
11 import java.io.Serializable;
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16
17 import org.apache.log4j.Logger;
18
19 import org.jfree.data.general.DatasetChangeEvent;
20 import org.jfree.data.xy.AbstractIntervalXYDataset;
21 import org.jfree.data.xy.IntervalXYDataset;
22
23 import org.jfree.data.statistics.HistogramType;
24 import org.jfree.data.statistics.HistogramBin;
25
26 import org.jfree.util.ObjectUtilities;
27 import org.jfree.util.PublicCloneable;
28
29 /**
30 * This class extends the functionality of the internal JFreeChart class
31 * <code>HistogramDataset</code>. <code>AdvancedHistogramDataset</code> takes
32 * the number of bins OR a bin width. It is mainly a copy of this class. The
33 * reason why there is no inheritance from JFreeChart's internal class is,
34 * that basic attributes have private access there.
35 *
36 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
37 */
38 public class AdvancedHistogramDataset
39 extends AbstractIntervalXYDataset
40 implements IntervalXYDataset, Cloneable, PublicCloneable, Serializable
41 {
42 /** A list of maps. */
43 protected List list;
44
45 /** The histogram type. */
46 protected HistogramType type;
47
48 /** The width of a single bin. */
49 protected double binWidth = -1;
50
51 /** The number of bins. */
52 protected int bins = -1;
53
54 /** The logger */
55 private static Logger logger =
56 Logger.getLogger(AdvancedHistogramDataset.class);
57
58
59 /**
60 * Creates a new (empty) dataset with a default type of
61 * {@link HistogramType}.FREQUENCY.
62 */
63 public AdvancedHistogramDataset() {
64 this.list = new ArrayList();
65 this.type = HistogramType.FREQUENCY;
66 }
67
68 /**
69 * Creates a new (empty) dataset with a default type of
70 * {@link HistogramType}.FREQUENCY.
71 */
72 public AdvancedHistogramDataset(int bins, double binWidth) {
73 this.list = new ArrayList();
74 this.type = HistogramType.FREQUENCY;
75 this.bins = bins;
76 this.binWidth = binWidth;
77 }
78
79 /**
80 * Returns the histogram type.
81 *
82 * @return The type (never <code>null</code>).
83 */
84 public HistogramType getType() {
85 return this.type;
86 }
87
88 /**
89 * Sets the histogram type and sends a {@link DatasetChangeEvent} to all
90 * registered listeners.
91 *
92 * @param type the type (<code>null</code> not permitted).
93 */
94 public void setType(HistogramType type) {
95 if (type == null) {
96 throw new IllegalArgumentException("Null 'type' argument");
97 }
98 this.type = type;
99 notifyListeners(new DatasetChangeEvent(this, this));
100 }
101
102 /**
103 * Adds a series to the dataset, using the specified number of bins.
104 *
105 * @param key the series key (<code>null</code> not permitted).
106 * @param values the values (<code>null</code> not permitted).
107 */
108 public void addSeries(Comparable key, double[] values) {
109 double minimum = getMinimum(values);
110 double maximum = getMaximum(values);
111 addSeries(key, values, minimum, maximum);
112 }
113
114 /**
115 * Adds a series to the dataset. Any data value less than minimum will be
116 * assigned to the first bin, and any data value greater than maximum will
117 * be assigned to the last bin. Values falling on the boundary of
118 * adjacent bins will be assigned to the higher indexed bin.
119 *
120 * @param key the series key (<code>null</code> not permitted).
121 * @param values the raw observations.
122 * @param bins the number of bins (must be at least 1).
123 * @param minimum the lower bound of the bin range.
124 * @param maximum the upper bound of the bin range.
125 */
126 public void addSeries(Comparable key,
127 double[] values,
128 double minimum,
129 double maximum) {
130
131 if (key == null) {
132 throw new IllegalArgumentException("Null 'key' argument.");
133 }
134 if (values == null) {
135 throw new IllegalArgumentException("Null 'values' argument.");
136 }
137 if (bins <= 0 && binWidth <= 0) {
138 throw new IllegalArgumentException(
139 "We need at least a bin width or the number of bins.");
140 }
141
142 // There is a binWidth given to calculate the number of bins in this
143 // case
144 if (bins <= 0) {
145 double tmp = (maximum - minimum) / binWidth;
146 bins = (int) tmp;
147 bins = tmp % 1 > 0 ? bins + 1 : bins;
148
149 double overlap = minimum + bins * binWidth - maximum;
150 tmp = minimum;
151 minimum -= overlap / 2;
152
153 logger.debug("There is an overlap of " + overlap);
154 logger.info("The lower bound is moved left from "
155 + tmp + " to " + minimum);
156
157 tmp = maximum;
158 maximum += overlap / 2;
159 logger.info("The upper bound is moved right from "
160 + tmp + " to " + maximum);
161 }
162
163 if (bins <= 0) {
164 bins = 1;
165 }
166
167 // in this case, there is a number of bins given, so we need to
168 // calculate the width of a single bin
169 if (binWidth <= 0)
170 binWidth = (maximum - minimum) / bins;
171
172 logger.info("bin width: " + binWidth);
173 logger.info("number of bins: " + bins);
174
175 double lower = minimum;
176 double upper;
177 List binList = new ArrayList(bins);
178 for (int i = 0; i < bins; i++) {
179 HistogramBin bin;
180 // make sure bins[bins.length]'s upper boundary ends at maximum
181 // to avoid the rounding issue. the bins[0] lower boundary is
182 // guaranteed start from min
183 if (i == bins - 1) {
184 bin = new HistogramBin(lower, maximum);
185 }
186 else {
187 upper = minimum + (i + 1) * binWidth;
188 bin = new HistogramBin(lower, upper);
189 lower = upper;
190 }
191 binList.add(bin);
192 }
193 // fill the bins
194 for (int i = 0; i < values.length; i++) {
195 int binIndex = bins - 1;
196 if (values[i] < maximum) {
197 double fraction = (values[i] - minimum) / (maximum - minimum);
198 if (fraction < 0.0) {
199 fraction = 0.0;
200 }
201 binIndex = (int) (fraction * bins);
202 // rounding could result in binIndex being equal to bins
203 // which will cause an IndexOutOfBoundsException - see bug
204 // report 1553088
205 if (binIndex >= bins) {
206 binIndex = bins - 1;
207 }
208 }
209 HistogramBin bin = (HistogramBin) binList.get(binIndex);
210 bin.incrementCount();
211 }
212 // generic map for each series
213 Map map = new HashMap();
214 map.put("key", key);
215 map.put("bins", binList);
216 map.put("values.length", new Integer(values.length));
217 map.put("bin width", new Double(binWidth));
218 this.list.add(map);
219 }
220
221 /**
222 * Returns the minimum value in an array of values.
223 *
224 * @param values the values (<code>null</code> not permitted and
225 * zero-length array not permitted).
226 *
227 * @return The minimum value.
228 */
229 private double getMinimum(double[] values) {
230 if (values == null || values.length < 1) {
231 throw new IllegalArgumentException(
232 "Null or zero length 'values' argument.");
233 }
234 double min = Double.MAX_VALUE;
235 for (int i = 0; i < values.length; i++) {
236 if (values[i] < min) {
237 min = values[i];
238 }
239 }
240 return min;
241 }
242
243 /**
244 * Returns the maximum value in an array of values.
245 *
246 * @param values the values (<code>null</code> not permitted and
247 * zero-length array not permitted).
248 *
249 * @return The maximum value.
250 */
251 private double getMaximum(double[] values) {
252 if (values == null || values.length < 1) {
253 throw new IllegalArgumentException(
254 "Null or zero length 'values' argument.");
255 }
256 double max = -Double.MAX_VALUE;
257 for (int i = 0; i < values.length; i++) {
258 if (values[i] > max) {
259 max = values[i];
260 }
261 }
262 return max;
263 }
264
265 /**
266 * Returns the bins for a series.
267 *
268 * @param series the series index (in the range <code>0</code> to
269 * <code>getSeriesCount() - 1</code>).
270 *
271 * @return A list of bins.
272 *
273 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
274 * specified range.
275 */
276 List getBins(int series) {
277 Map map = (Map) this.list.get(series);
278 return (List) map.get("bins");
279 }
280
281 /**
282 * Returns the total number of observations for a series.
283 *
284 * @param series the series index.
285 *
286 * @return The total.
287 */
288 private int getTotal(int series) {
289 Map map = (Map) this.list.get(series);
290 return ((Integer) map.get("values.length")).intValue();
291 }
292
293 /**
294 * Returns the bin width for a series.
295 *
296 * @param series the series index (zero based).
297 *
298 * @return The bin width.
299 */
300 private double getBinWidth(int series) {
301 if (binWidth > 0)
302 return binWidth;
303
304 Map map = (Map) this.list.get(series);
305 return ((Double) map.get("bin width")).doubleValue();
306 }
307
308 /**
309 * Returns the number of series in the dataset.
310 *
311 * @return The series count.
312 */
313 public int getSeriesCount() {
314 return this.list.size();
315 }
316
317 /**
318 * Returns the key for a series.
319 *
320 * @param series the series index (in the range <code>0</code> to
321 * <code>getSeriesCount() - 1</code>).
322 *
323 * @return The series key.
324 *
325 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
326 * specified range.
327 */
328 public Comparable getSeriesKey(int series) {
329 Map map = (Map) this.list.get(series);
330 return (Comparable) map.get("key");
331 }
332
333 /**
334 * Returns the number of data items for a series.
335 *
336 * @param series the series index (in the range <code>0</code> to
337 * <code>getSeriesCount() - 1</code>).
338 *
339 * @return The item count.
340 *
341 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
342 * specified range.
343 */
344 public int getItemCount(int series) {
345 return getBins(series).size();
346 }
347
348 /**
349 * Returns the X value for a bin. This value won't be used for plotting
350 * histograms, since the renderer will ignore it. But other renderers can
351 * use it (for example, you could use the dataset to create a line
352 * chart).
353 *
354 * @param series the series index (in the range <code>0</code> to
355 * <code>getSeriesCount() - 1</code>).
356 * @param item the item index (zero based).
357 *
358 * @return The start value.
359 *
360 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
361 * specified range.
362 */
363 public Number getX(int series, int item) {
364 List bins = getBins(series);
365 HistogramBin bin = (HistogramBin) bins.get(item);
366 double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2.;
367 return new Double(x);
368 }
369
370 /**
371 * Returns the y-value for a bin (calculated to take into account the
372 * histogram type).
373 *
374 * @param series the series index (in the range <code>0</code> to
375 * <code>getSeriesCount() - 1</code>).
376 * @param item the item index (zero based).
377 *
378 * @return The y-value.
379 *
380 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
381 * specified range.
382 */
383 public Number getY(int series, int item) {
384 List bins = getBins(series);
385 HistogramBin bin = (HistogramBin) bins.get(item);
386 double total = getTotal(series);
387 double binWidth = getBinWidth(series);
388
389 if (this.type == HistogramType.FREQUENCY) {
390 return new Double(bin.getCount());
391 }
392 else if (this.type == HistogramType.RELATIVE_FREQUENCY) {
393 return new Double(bin.getCount() / total);
394 }
395 else if (this.type == HistogramType.SCALE_AREA_TO_1) {
396 return new Double(bin.getCount() / (binWidth * total));
397 }
398 else { // pretty sure this shouldn't ever happen
399 throw new IllegalStateException();
400 }
401 }
402
403 /**
404 * Returns the start value for a bin.
405 *
406 * @param series the series index (in the range <code>0</code> to
407 * <code>getSeriesCount() - 1</code>).
408 * @param item the item index (zero based).
409 *
410 * @return The start value.
411 *
412 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
413 * specified range.
414 */
415 public Number getStartX(int series, int item) {
416 List bins = getBins(series);
417 HistogramBin bin = (HistogramBin) bins.get(item);
418 return new Double(bin.getStartBoundary());
419 }
420
421 /**
422 * Returns the end value for a bin.
423 *
424 * @param series the series index (in the range <code>0</code> to
425 * <code>getSeriesCount() - 1</code>).
426 * @param item the item index (zero based).
427 *
428 * @return The end value.
429 *
430 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
431 * specified range.
432 */
433 public Number getEndX(int series, int item) {
434 List bins = getBins(series);
435 HistogramBin bin = (HistogramBin) bins.get(item);
436 return new Double(bin.getEndBoundary());
437 }
438
439 /**
440 * Returns the start y-value for a bin (which is the same as the y-value,
441 * this method exists only to support the general form of the
442 * {@link IntervalXYDataset} interface).
443 *
444 * @param series the series index (in the range <code>0</code> to
445 * <code>getSeriesCount() - 1</code>).
446 * @param item the item index (zero based).
447 *
448 * @return The y-value.
449 *
450 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
451 * specified range.
452 */
453 public Number getStartY(int series, int item) {
454 return getY(series, item);
455 }
456
457 /**
458 * Returns the end y-value for a bin (which is the same as the y-value,
459 * this method exists only to support the general form of the
460 * {@link IntervalXYDataset} interface).
461 *
462 * @param series the series index (in the range <code>0</code> to
463 * <code>getSeriesCount() - 1</code>).
464 * @param item the item index (zero based).
465 *
466 * @return The Y value.
467 *
468 * @throws IndexOutOfBoundsException if <code>series</code> is outside the
469 * specified range.
470 */
471 public Number getEndY(int series, int item) {
472 return getY(series, item);
473 }
474
475 /**
476 * Tests this dataset for equality with an arbitrary object.
477 *
478 * @param obj the object to test against (<code>null</code> permitted).
479 *
480 * @return A boolean.
481 */
482 public boolean equals(Object obj) {
483 if (obj == this) {
484 return true;
485 }
486 if (!(obj instanceof AdvancedHistogramDataset)) {
487 return false;
488 }
489 AdvancedHistogramDataset that = (AdvancedHistogramDataset) obj;
490 if (!ObjectUtilities.equal(this.type, that.type)) {
491 return false;
492 }
493 if (!ObjectUtilities.equal(this.list, that.list)) {
494 return false;
495 }
496 return true;
497 }
498
499 /**
500 * Returns a clone of the dataset.
501 *
502 * @return A clone of the dataset.
503 *
504 * @throws CloneNotSupportedException if the object cannot be cloned.
505 */
506 public Object clone() throws CloneNotSupportedException {
507 AdvancedHistogramDataset clone = (AdvancedHistogramDataset) super.clone();
508 int seriesCount = getSeriesCount();
509 clone.list = new java.util.ArrayList(seriesCount);
510 for (int i = 0; i < seriesCount; i++) {
511 clone.list.add(new HashMap((Map) this.list.get(i)));
512 }
513 return clone;
514 }
515 }
516 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :

http://dive4elements.wald.intevation.org