001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-2008, 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
061package org.jfree.data.statistics;
062
063import java.util.ArrayList;
064import java.util.Date;
065import java.util.List;
066
067import org.jfree.data.Range;
068import org.jfree.data.RangeInfo;
069import org.jfree.data.general.DatasetChangeEvent;
070import org.jfree.data.xy.AbstractXYDataset;
071import 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 */
077public 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}