    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
    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    }