001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ----------------------------------
028 * DefaultBoxAndWhiskerXYDataset.java
029 * ----------------------------------
030 * (C) Copyright 2003-2007, by David Browning and Contributors.
031 *
032 * Original Author: David Browning (for Australian Institute of Marine
033 * Science);
034 * Contributor(s): David Gilbert (for Object Refinery Limited);
035 *
036 * Changes
037 * -------
038 * 05-Aug-2003 : Version 1, contributed by David Browning (DG);
039 * 08-Aug-2003 : Minor changes to comments (DB)
040 * Allow average to be null - average is a perculiar AIMS
041 * requirement which probably should be stripped out and overlaid
042 * if required...
043 * Added a number of methods to allow the max and min non-outlier
044 * and non-farout values to be calculated
045 * 12-Aug-2003 Changed the getYValue to return the highest outlier value
046 * Added getters and setters for outlier and farout coefficients
047 * 27-Aug-2003 : Renamed DefaultBoxAndWhiskerDataset
048 * --> DefaultBoxAndWhiskerXYDataset (DG);
049 * 06-May-2004 : Now extends AbstractXYDataset (DG);
050 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
051 * getYValue() (DG);
052 * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
053 * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
054 * release (DG);
055 * ------------- JFREECHART 1.0.x ---------------------------------------------
056 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
057 * 12-Nov-2007 : Implemented equals() and clone() (DG);
058 *
059 */
060
061 package org.jfree.data.statistics;
062
063 import java.util.ArrayList;
064 import java.util.Date;
065 import java.util.List;
066
067 import org.jfree.data.Range;
068 import org.jfree.data.RangeInfo;
069 import org.jfree.data.general.DatasetChangeEvent;
070 import org.jfree.data.xy.AbstractXYDataset;
071 import org.jfree.util.ObjectUtilities;
072
073 /**
074 * A simple implementation of the {@link BoxAndWhiskerXYDataset} interface.
075 * This dataset implementation can hold only one series.
076 */
077 public class DefaultBoxAndWhiskerXYDataset extends AbstractXYDataset
078 implements BoxAndWhiskerXYDataset, RangeInfo {
079
080 /** The series key. */
081 private Comparable seriesKey;
082
083 /** Storage for the dates. */
084 private List dates;
085
086 /** Storage for the box and whisker statistics. */
087 private List items;
088
089 /** The minimum range value. */
090 private Number minimumRangeValue;
091
092 /** The maximum range value. */
093 private Number maximumRangeValue;
094
095 /** The range of values. */
096 private Range rangeBounds;
097
098 /**
099 * The coefficient used to calculate outliers. Tukey's default value is
100 * 1.5 (see EDA) Any value which is greater than Q3 + (interquartile range
101 * * outlier coefficient) is considered to be an outlier. Can be altered
102 * if the data is particularly skewed.
103 */
104 private double outlierCoefficient = 1.5;
105
106 /**
107 * The coefficient used to calculate farouts. Tukey's default value is 2
108 * (see EDA) Any value which is greater than Q3 + (interquartile range *
109 * farout coefficient) is considered to be a farout. Can be altered if the
110 * data is particularly skewed.
111 */
112 private double faroutCoefficient = 2.0;
113
114 /**
115 * Constructs a new box and whisker dataset.
116 * <p>
117 * The current implementation allows only one series in the dataset.
118 * This may be extended in a future version.
119 *
120 * @param seriesKey the key for the series.
121 */
122 public DefaultBoxAndWhiskerXYDataset(Comparable seriesKey) {
123 this.seriesKey = seriesKey;
124 this.dates = new ArrayList();
125 this.items = new ArrayList();
126 this.minimumRangeValue = null;
127 this.maximumRangeValue = null;
128 this.rangeBounds = null;
129 }
130
131 /**
132 * Returns the value used as the outlier coefficient. The outlier
133 * coefficient gives an indication of the degree of certainty in an
134 * unskewed distribution. Increasing the coefficient increases the number
135 * of values included. Currently only used to ensure farout coefficient is
136 * greater than the outlier coefficient
137 *
138 * @return A <code>double</code> representing the value used to calculate
139 * outliers.
140 *
141 * @see #setOutlierCoefficient(double)
142 */
143 public double getOutlierCoefficient() {
144 return this.outlierCoefficient;
145 }
146
147 /**
148 * Sets the value used as the outlier coefficient
149 *
150 * @param outlierCoefficient being a <code>double</code> representing the
151 * value used to calculate outliers.
152 *
153 * @see #getOutlierCoefficient()
154 */
155 public void setOutlierCoefficient(double outlierCoefficient) {
156 this.outlierCoefficient = outlierCoefficient;
157 }
158
159 /**
160 * Returns the value used as the farout coefficient. The farout coefficient
161 * allows the calculation of which values will be off the graph.
162 *
163 * @return A <code>double</code> representing the value used to calculate
164 * farouts.
165 *
166 * @see #setFaroutCoefficient(double)
167 */
168 public double getFaroutCoefficient() {
169 return this.faroutCoefficient;
170 }
171
172 /**
173 * Sets the value used as the farouts coefficient. The farout coefficient
174 * must b greater than the outlier coefficient.
175 *
176 * @param faroutCoefficient being a <code>double</code> representing the
177 * value used to calculate farouts.
178 *
179 * @see #getFaroutCoefficient()
180 */
181 public void setFaroutCoefficient(double faroutCoefficient) {
182
183 if (faroutCoefficient > getOutlierCoefficient()) {
184 this.faroutCoefficient = faroutCoefficient;
185 }
186 else {
187 throw new IllegalArgumentException("Farout value must be greater "
188 + "than the outlier value, which is currently set at: ("
189 + getOutlierCoefficient() + ")");
190 }
191 }
192
193 /**
194 * Returns the number of series in the dataset.
195 * <p>
196 * This implementation only allows one series.
197 *
198 * @return The number of series.
199 */
200 public int getSeriesCount() {
201 return 1;
202 }
203
204 /**
205 * Returns the number of items in the specified series.
206 *
207 * @param series the index (zero-based) of the series.
208 *
209 * @return The number of items in the specified series.
210 */
211 public int getItemCount(int series) {
212 return this.dates.size();
213 }
214
215 /**
216 * Adds an item to the dataset and sends a {@link DatasetChangeEvent} to
217 * all registered listeners.
218 *
219 * @param date the date (<code>null</code> not permitted).
220 * @param item the item (<code>null</code> not permitted).
221 */
222 public void add(Date date, BoxAndWhiskerItem item) {
223 this.dates.add(date);
224 this.items.add(item);
225 if (this.minimumRangeValue == null) {
226 this.minimumRangeValue = item.getMinRegularValue();
227 }
228 else {
229 if (item.getMinRegularValue().doubleValue()
230 < this.minimumRangeValue.doubleValue()) {
231 this.minimumRangeValue = item.getMinRegularValue();
232 }
233 }
234 if (this.maximumRangeValue == null) {
235 this.maximumRangeValue = item.getMaxRegularValue();
236 }
237 else {
238 if (item.getMaxRegularValue().doubleValue()
239 > this.maximumRangeValue.doubleValue()) {
240 this.maximumRangeValue = item.getMaxRegularValue();
241 }
242 }
243 this.rangeBounds = new Range(this.minimumRangeValue.doubleValue(),
244 this.maximumRangeValue.doubleValue());
245 fireDatasetChanged();
246 }
247
248 /**
249 * Returns the name of the series stored in this dataset.
250 *
251 * @param i the index of the series. Currently ignored.
252 *
253 * @return The name of this series.
254 */
255 public Comparable getSeriesKey(int i) {
256 return this.seriesKey;
257 }
258
259 /**
260 * Return an item from within the dataset.
261 *
262 * @param series the series index (ignored, since this dataset contains
263 * only one series).
264 * @param item the item within the series (zero-based index)
265 *
266 * @return The item.
267 */
268 public BoxAndWhiskerItem getItem(int series, int item) {
269 return (BoxAndWhiskerItem) this.items.get(item);
270 }
271
272 /**
273 * Returns the x-value for one item in a series.
274 * <p>
275 * The value returned is a Long object generated from the underlying Date
276 * object.
277 *
278 * @param series the series (zero-based index).
279 * @param item the item (zero-based index).
280 *
281 * @return The x-value.
282 */
283 public Number getX(int series, int item) {
284 return new Long(((Date) this.dates.get(item)).getTime());
285 }
286
287 /**
288 * Returns the x-value for one item in a series, as a Date.
289 * <p>
290 * This method is provided for convenience only.
291 *
292 * @param series the series (zero-based index).
293 * @param item the item (zero-based index).
294 *
295 * @return The x-value as a Date.
296 */
297 public Date getXDate(int series, int item) {
298 return (Date) this.dates.get(item);
299 }
300
301 /**
302 * Returns the y-value for one item in a series.
303 * <p>
304 * This method (from the XYDataset interface) is mapped to the
305 * getMeanValue() method.
306 *
307 * @param series the series (zero-based index).
308 * @param item the item (zero-based index).
309 *
310 * @return The y-value.
311 */
312 public Number getY(int series, int item) {
313 return getMeanValue(series, item);
314 }
315
316 /**
317 * Returns the mean for the specified series and item.
318 *
319 * @param series the series (zero-based index).
320 * @param item the item (zero-based index).
321 *
322 * @return The mean for the specified series and item.
323 */
324 public Number getMeanValue(int series, int item) {
325 Number result = null;
326 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
327 if (stats != null) {
328 result = stats.getMean();
329 }
330 return result;
331 }
332
333 /**
334 * Returns the median-value for the specified series and item.
335 *
336 * @param series the series (zero-based index).
337 * @param item the item (zero-based index).
338 *
339 * @return The median-value for the specified series and item.
340 */
341 public Number getMedianValue(int series, int item) {
342 Number result = null;
343 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
344 if (stats != null) {
345 result = stats.getMedian();
346 }
347 return result;
348 }
349
350 /**
351 * Returns the Q1 median-value for the specified series and item.
352 *
353 * @param series the series (zero-based index).
354 * @param item the item (zero-based index).
355 *
356 * @return The Q1 median-value for the specified series and item.
357 */
358 public Number getQ1Value(int series, int item) {
359 Number result = null;
360 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
361 if (stats != null) {
362 result = stats.getQ1();
363 }
364 return result;
365 }
366
367 /**
368 * Returns the Q3 median-value for the specified series and item.
369 *
370 * @param series the series (zero-based index).
371 * @param item the item (zero-based index).
372 *
373 * @return The Q3 median-value for the specified series and item.
374 */
375 public Number getQ3Value(int series, int item) {
376 Number result = null;
377 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
378 if (stats != null) {
379 result = stats.getQ3();
380 }
381 return result;
382 }
383
384 /**
385 * Returns the min-value for the specified series and item.
386 *
387 * @param series the series (zero-based index).
388 * @param item the item (zero-based index).
389 *
390 * @return The min-value for the specified series and item.
391 */
392 public Number getMinRegularValue(int series, int item) {
393 Number result = null;
394 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
395 if (stats != null) {
396 result = stats.getMinRegularValue();
397 }
398 return result;
399 }
400
401 /**
402 * Returns the max-value for the specified series and item.
403 *
404 * @param series the series (zero-based index).
405 * @param item the item (zero-based index).
406 *
407 * @return The max-value for the specified series and item.
408 */
409 public Number getMaxRegularValue(int series, int item) {
410 Number result = null;
411 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
412 if (stats != null) {
413 result = stats.getMaxRegularValue();
414 }
415 return result;
416 }
417
418 /**
419 * Returns the minimum value which is not a farout.
420 * @param series the series (zero-based index).
421 * @param item the item (zero-based index).
422 *
423 * @return A <code>Number</code> representing the maximum non-farout value.
424 */
425 public Number getMinOutlier(int series, int item) {
426 Number result = null;
427 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
428 if (stats != null) {
429 result = stats.getMinOutlier();
430 }
431 return result;
432 }
433
434 /**
435 * Returns the maximum value which is not a farout, ie Q3 + (interquartile
436 * range * farout coefficient).
437 *
438 * @param series the series (zero-based index).
439 * @param item the item (zero-based index).
440 *
441 * @return A <code>Number</code> representing the maximum non-farout value.
442 */
443 public Number getMaxOutlier(int series, int item) {
444 Number result = null;
445 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
446 if (stats != null) {
447 result = stats.getMaxOutlier();
448 }
449 return result;
450 }
451
452 /**
453 * Returns an array of outliers for the specified series and item.
454 *
455 * @param series the series (zero-based index).
456 * @param item the item (zero-based index).
457 *
458 * @return The array of outliers for the specified series and item.
459 */
460 public List getOutliers(int series, int item) {
461 List result = null;
462 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
463 if (stats != null) {
464 result = stats.getOutliers();
465 }
466 return result;
467 }
468
469 /**
470 * Returns the minimum y-value in the dataset.
471 *
472 * @param includeInterval a flag that determines whether or not the
473 * y-interval is taken into account.
474 *
475 * @return The minimum value.
476 */
477 public double getRangeLowerBound(boolean includeInterval) {
478 double result = Double.NaN;
479 if (this.minimumRangeValue != null) {
480 result = this.minimumRangeValue.doubleValue();
481 }
482 return result;
483 }
484
485 /**
486 * Returns the maximum y-value in the dataset.
487 *
488 * @param includeInterval a flag that determines whether or not the
489 * y-interval is taken into account.
490 *
491 * @return The maximum value.
492 */
493 public double getRangeUpperBound(boolean includeInterval) {
494 double result = Double.NaN;
495 if (this.maximumRangeValue != null) {
496 result = this.maximumRangeValue.doubleValue();
497 }
498 return result;
499 }
500
501 /**
502 * Returns the range of the values in this dataset's range.
503 *
504 * @param includeInterval a flag that determines whether or not the
505 * y-interval is taken into account.
506 *
507 * @return The range.
508 */
509 public Range getRangeBounds(boolean includeInterval) {
510 return this.rangeBounds;
511 }
512
513 /**
514 * Tests this dataset for equality with an arbitrary object.
515 *
516 * @param obj the object (<code>null</code> permitted).
517 *
518 * @return A boolean.
519 */
520 public boolean equals(Object obj) {
521 if (obj == this) {
522 return true;
523 }
524 if (!(obj instanceof DefaultBoxAndWhiskerXYDataset)) {
525 return false;
526 }
527 DefaultBoxAndWhiskerXYDataset that
528 = (DefaultBoxAndWhiskerXYDataset) obj;
529 if (!ObjectUtilities.equal(this.seriesKey, that.seriesKey)) {
530 return false;
531 }
532 if (!this.dates.equals(that.dates)) {
533 return false;
534 }
535 if (!this.items.equals(that.items)) {
536 return false;
537 }
538 return true;
539 }
540
541 /**
542 * Returns a clone of the plot.
543 *
544 * @return A clone.
545 *
546 * @throws CloneNotSupportedException if the cloning is not supported.
547 */
548 public Object clone() throws CloneNotSupportedException {
549 DefaultBoxAndWhiskerXYDataset clone
550 = (DefaultBoxAndWhiskerXYDataset) super.clone();
551 clone.dates = new java.util.ArrayList(this.dates);
552 clone.items = new java.util.ArrayList(this.items);
553 return clone;
554 }
555
556 }