    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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 a list 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 list of outliers for the specified series and item
    459     *         (possibly <code>null</code>).
    460     */
    461    public List getOutliers(int series, int item) {
    462        List result = null;
    463        BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    464        if (stats != null) {
    465            result = stats.getOutliers();
    466        }
    467        return result;
    468    }
    469
    470    /**
    471     * Returns the minimum y-value in the dataset.
    472     *
    473     * @param includeInterval  a flag that determines whether or not the
    474     *                         y-interval is taken into account.
    475     *
    476     * @return The minimum value.
    477     */
    478    public double getRangeLowerBound(boolean includeInterval) {
    479        double result = Double.NaN;
    480        if (this.minimumRangeValue != null) {
    481            result = this.minimumRangeValue.doubleValue();
    482        }
    483        return result;
    484    }
    485
    486    /**
    487     * Returns the maximum y-value in the dataset.
    488     *
    489     * @param includeInterval  a flag that determines whether or not the
    490     *                         y-interval is taken into account.
    491     *
    492     * @return The maximum value.
    493     */
    494    public double getRangeUpperBound(boolean includeInterval) {
    495        double result = Double.NaN;
    496        if (this.maximumRangeValue != null) {
    497            result = this.maximumRangeValue.doubleValue();
    498        }
    499        return result;
    500    }
    501
    502    /**
    503     * Returns the range of the values in this dataset's range.
    504     *
    505     * @param includeInterval  a flag that determines whether or not the
    506     *                         y-interval is taken into account.
    507     *
    508     * @return The range.
    509     */
    510    public Range getRangeBounds(boolean includeInterval) {
    511        return this.rangeBounds;
    512    }
    513
    514    /**
    515     * Tests this dataset for equality with an arbitrary object.
    516     *
    517     * @param obj  the object (<code>null</code> permitted).
    518     *
    519     * @return A boolean.
    520     */
    521    public boolean equals(Object obj) {
    522        if (obj == this) {
    523            return true;
    524        }
    525        if (!(obj instanceof DefaultBoxAndWhiskerXYDataset)) {
    526            return false;
    527        }
    528        DefaultBoxAndWhiskerXYDataset that
    529                = (DefaultBoxAndWhiskerXYDataset) obj;
    530        if (!ObjectUtilities.equal(this.seriesKey, that.seriesKey)) {
    531            return false;
    532        }
    533        if (!this.dates.equals(that.dates)) {
    534            return false;
    535        }
    536        if (!this.items.equals(that.items)) {
    537            return false;
    538        }
    539        return true;
    540    }
    541
    542    /**
    543     * Returns a clone of the plot.
    544     *
    545     * @return A clone.
    546     *
    547     * @throws CloneNotSupportedException  if the cloning is not supported.
    548     */
    549    public Object clone() throws CloneNotSupportedException {
    550        DefaultBoxAndWhiskerXYDataset clone
    551                = (DefaultBoxAndWhiskerXYDataset) super.clone();
    552        clone.dates = new java.util.ArrayList(this.dates);
    553        clone.items = new java.util.ArrayList(this.items);
    554        return clone;
    555    }
    556
    557}