    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, 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     * $Id: DefaultBoxAndWhiskerXYDataset.java,v 1.10.2.1 2005/10/25 21:34:46 mungady Exp $
    037     *
    038     * Changes
    039     * -------
    040     * 05-Aug-2003 : Version 1, contributed by David Browning (DG);
    041     * 08-Aug-2003 : Minor changes to comments (DB)
    042     *               Allow average to be null  - average is a perculiar AIMS
    043     *               requirement which probably should be stripped out and overlaid
    044     *               if required...
    045     *               Added a number of methods to allow the max and min non-outlier
    046     *               and non-farout values to be calculated
    047     * 12-Aug-2003   Changed the getYValue to return the highest outlier value
    048     *               Added getters and setters for outlier and farout coefficients
    049     * 27-Aug-2003 : Renamed DefaultBoxAndWhiskerDataset
    050     *               --> DefaultBoxAndWhiskerXYDataset (DG);
    051     * 06-May-2004 : Now extends AbstractXYDataset (DG);
    052     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    053     *               getYValue() (DG);
    054     * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
    055     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
    056     *               release (DG);
    057     *
    058     */
    059
    060    package org.jfree.data.statistics;
    061
    062    import java.util.ArrayList;
    063    import java.util.Date;
    064    import java.util.List;
    065
    066    import org.jfree.data.Range;
    067    import org.jfree.data.RangeInfo;
    068    import org.jfree.data.xy.AbstractXYDataset;
    069
    070    /**
    071     * A simple implementation of the {@link BoxAndWhiskerXYDataset}.  The dataset
    072     * can hold only one series.
    073     *
    074     * @author David Browning
    075     */
    076    public class DefaultBoxAndWhiskerXYDataset extends AbstractXYDataset
    077                                               implements BoxAndWhiskerXYDataset,
    078                                                          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         * Adds an item to the dataset.
    133         *
    134         * @param date  the date.
    135         * @param item  the item.
    136         */
    137        public void add(Date date, BoxAndWhiskerItem item) {
    138            this.dates.add(date);
    139            this.items.add(item);
    140            if (this.minimumRangeValue == null) {
    141                this.minimumRangeValue = item.getMinRegularValue();
    142            }
    143            else {
    144                if (item.getMinRegularValue().doubleValue()
    145                        < this.minimumRangeValue.doubleValue()) {
    146                    this.minimumRangeValue = item.getMinRegularValue();
    147                }
    148            }
    149            if (this.maximumRangeValue == null) {
    150                this.maximumRangeValue = item.getMaxRegularValue();
    151            }
    152            else {
    153                if (item.getMaxRegularValue().doubleValue()
    154                        > this.maximumRangeValue.doubleValue()) {
    155                    this.maximumRangeValue = item.getMaxRegularValue();
    156                }
    157            }
    158            this.rangeBounds = new Range(
    159                this.minimumRangeValue.doubleValue(),
    160                this.maximumRangeValue.doubleValue()
    161            );
    162        }
    163
    164        /**
    165         * Returns the name of the series stored in this dataset.
    166         *
    167         * @param i  the index of the series. Currently ignored.
    168         *
    169         * @return The name of this series.
    170         */
    171        public Comparable getSeriesKey(int i) {
    172            return this.seriesKey;
    173        }
    174
    175        /**
    176         * Return an item from within the dataset.
    177         *
    178         * @param series  the series index (ignored, since this dataset contains
    179         *                only one series).
    180         * @param item  the item within the series (zero-based index)
    181         *
    182         * @return The item.
    183         */
    184        public BoxAndWhiskerItem getItem(int series, int item) {
    185            return (BoxAndWhiskerItem) this.items.get(item);
    186        }
    187
    188        /**
    189         * Returns the x-value for one item in a series.
    190         * <p>
    191         * The value returned is a Long object generated from the underlying Date
    192         * object.
    193         *
    194         * @param series  the series (zero-based index).
    195         * @param item  the item (zero-based index).
    196         *
    197         * @return The x-value.
    198         */
    199        public Number getX(int series, int item) {
    200            return new Long(((Date) this.dates.get(item)).getTime());
    201        }
    202
    203        /**
    204         * Returns the x-value for one item in a series, as a Date.
    205         * <p>
    206         * This method is provided for convenience only.
    207         *
    208         * @param series  the series (zero-based index).
    209         * @param item  the item (zero-based index).
    210         *
    211         * @return The x-value as a Date.
    212         */
    213        public Date getXDate(int series, int item) {
    214            return (Date) this.dates.get(item);
    215        }
    216
    217        /**
    218         * Returns the y-value for one item in a series.
    219         * <p>
    220         * This method (from the XYDataset interface) is mapped to the
    221         * getMaxNonOutlierValue() method.
    222         *
    223         * @param series  the series (zero-based index).
    224         * @param item  the item (zero-based index).
    225         *
    226         * @return The y-value.
    227         */
    228        public Number getY(int series, int item) {
    229            return new Double(getMeanValue(series, item).doubleValue());
    230        }
    231
    232        /**
    233         * Returns the mean for the specified series and item.
    234         *
    235         * @param series  the series (zero-based index).
    236         * @param item  the item (zero-based index).
    237         *
    238         * @return The mean for the specified series and item.
    239         */
    240        public Number getMeanValue(int series, int item) {
    241            Number result = null;
    242            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    243            if (stats != null) {
    244                result = stats.getMean();
    245            }
    246            return result;
    247        }
    248
    249        /**
    250         * Returns the median-value for the specified series and item.
    251         *
    252         * @param series  the series (zero-based index).
    253         * @param item  the item (zero-based index).
    254         *
    255         * @return The median-value for the specified series and item.
    256         */
    257        public Number getMedianValue(int series, int item) {
    258            Number result = null;
    259            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    260            if (stats != null) {
    261                result = stats.getMedian();
    262            }
    263            return result;
    264        }
    265
    266        /**
    267         * Returns the Q1 median-value for the specified series and item.
    268         *
    269         * @param series  the series (zero-based index).
    270         * @param item  the item (zero-based index).
    271         *
    272         * @return The Q1 median-value for the specified series and item.
    273         */
    274        public Number getQ1Value(int series, int item) {
    275            Number result = null;
    276            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    277            if (stats != null) {
    278                result = stats.getQ1();
    279            }
    280            return result;
    281        }
    282
    283        /**
    284         * Returns the Q3 median-value for the specified series and item.
    285         *
    286         * @param series  the series (zero-based index).
    287         * @param item  the item (zero-based index).
    288         *
    289         * @return The Q3 median-value for the specified series and item.
    290         */
    291        public Number getQ3Value(int series, int item) {
    292            Number result = null;
    293            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    294            if (stats != null) {
    295                result = stats.getQ3();
    296            }
    297            return result;
    298        }
    299
    300        /**
    301         * Returns the min-value for the specified series and item.
    302         *
    303         * @param series  the series (zero-based index).
    304         * @param item  the item (zero-based index).
    305         *
    306         * @return The min-value for the specified series and item.
    307         */
    308        public Number getMinRegularValue(int series, int item) {
    309            Number result = null;
    310            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    311            if (stats != null) {
    312                result = stats.getMinRegularValue();
    313            }
    314            return result;
    315        }
    316
    317        /**
    318         * Returns the max-value for the specified series and item.
    319         *
    320         * @param series  the series (zero-based index).
    321         * @param item  the item (zero-based index).
    322         *
    323         * @return The max-value for the specified series and item.
    324         */
    325        public Number getMaxRegularValue(int series, int item) {
    326            Number result = null;
    327            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    328            if (stats != null) {
    329                result = stats.getMaxRegularValue();
    330            }
    331            return result;
    332        }
    333
    334        /**
    335         * Returns the minimum value which is not a farout.
    336         * @param series  the series (zero-based index).
    337         * @param item  the item (zero-based index).
    338         *
    339         * @return A <code>Number</code> representing the maximum non-farout value.
    340         */
    341        public Number getMinOutlier(int series, int item) {
    342            Number result = null;
    343            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    344            if (stats != null) {
    345                result = stats.getMinOutlier();
    346            }
    347            return result;
    348        }
    349
    350        /**
    351         * Returns the maximum value which is not a farout, ie Q3 + (interquartile
    352         * range * farout coefficient).
    353         *
    354         * @param series  the series (zero-based index).
    355         * @param item  the item (zero-based index).
    356         *
    357         * @return A <code>Number</code> representing the maximum non-farout value.
    358         */
    359        public Number getMaxOutlier(int series, int item) {
    360            Number result = null;
    361            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    362            if (stats != null) {
    363                result = stats.getMaxOutlier();
    364            }
    365            return result;
    366        }
    367
    368        /**
    369         * Returns an array of outliers for the specified series and item.
    370         *
    371         * @param series  the series (zero-based index).
    372         * @param item  the item (zero-based index).
    373         *
    374         * @return The array of outliers for the specified series and item.
    375         */
    376        public List getOutliers(int series, int item) {
    377            List result = null;
    378            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
    379            if (stats != null) {
    380                result = stats.getOutliers();
    381            }
    382            return result;
    383        }
    384
    385        /**
    386         * Returns the value used as the outlier coefficient. The outlier
    387         * coefficient gives an indication of the degree of certainty in an
    388         * unskewed distribution.  Increasing the coefficient increases the number
    389         * of values included. Currently only used to ensure farout coefficient is
    390         * greater than the outlier coefficient
    391         *
    392         * @return A <code>double</code> representing the value used to calculate
    393         *         outliers.
    394         */
    395        public double getOutlierCoefficient() {
    396            return this.outlierCoefficient;
    397        }
    398
    399        /**
    400         * Returns the value used as the farout coefficient. The farout coefficient
    401         * allows the calculation of which values will be off the graph.
    402         *
    403         * @return A <code>double</code> representing the value used to calculate
    404         *         farouts.
    405         */
    406        public double getFaroutCoefficient() {
    407            return this.faroutCoefficient;
    408        }
    409
    410        /**
    411         * Returns the number of series in the dataset.
    412         * <p>
    413         * This implementation only allows one series.
    414         *
    415         * @return The number of series.
    416         */
    417        public int getSeriesCount() {
    418            return 1;
    419        }
    420
    421        /**
    422         * Returns the number of items in the specified series.
    423         *
    424         * @param series  the index (zero-based) of the series.
    425         *
    426         * @return The number of items in the specified series.
    427         */
    428        public int getItemCount(int series) {
    429            return this.dates.size();
    430        }
    431
    432        /**
    433         * Sets the value used as the outlier coefficient
    434         *
    435         * @param outlierCoefficient  being a <code>double</code> representing the
    436         *                            value used to calculate outliers.
    437         */
    438        public void setOutlierCoefficient(double outlierCoefficient) {
    439            this.outlierCoefficient = outlierCoefficient;
    440        }
    441
    442        /**
    443         * Sets the value used as the farouts coefficient. The farout coefficient
    444         * must b greater than the outlier coefficient.
    445         *
    446         * @param faroutCoefficient being a <code>double</code> representing the
    447         *                          value used to calculate farouts.
    448         */
    449        public void setFaroutCoefficient(double faroutCoefficient) {
    450
    451            if (faroutCoefficient > getOutlierCoefficient()) {
    452                this.faroutCoefficient = faroutCoefficient;
    453            }
    454            else {
    455                throw new IllegalArgumentException("Farout value must be greater "
    456                    + "than the outlier value, which is currently set at: ("
    457                    + getOutlierCoefficient() + ")");
    458            }
    459        }
    460
    461        /**
    462         * Returns the minimum y-value in the dataset.
    463         *
    464         * @param includeInterval  a flag that determines whether or not the
    465         *                         y-interval is taken into account.
    466         *
    467         * @return The minimum value.
    468         */
    469        public double getRangeLowerBound(boolean includeInterval) {
    470            double result = Double.NaN;
    471            if (this.minimumRangeValue != null) {
    472                result = this.minimumRangeValue.doubleValue();
    473            }
    474            return result;
    475        }
    476
    477        /**
    478         * Returns the maximum y-value in the dataset.
    479         *
    480         * @param includeInterval  a flag that determines whether or not the
    481         *                         y-interval is taken into account.
    482         *
    483         * @return The maximum value.
    484         */
    485        public double getRangeUpperBound(boolean includeInterval) {
    486            double result = Double.NaN;
    487            if (this.maximumRangeValue != null) {
    488                result = this.maximumRangeValue.doubleValue();
    489            }
    490            return result;
    491        }
    492
    493        /**
    494         * Returns the range of the values in this dataset's range.
    495         *
    496         * @param includeInterval  a flag that determines whether or not the
    497         *                         y-interval is taken into account.
    498         *
    499         * @return The range.
    500         */
    501        public Range getRangeBounds(boolean includeInterval) {
    502            return this.rangeBounds;
    503        }
    504
    505    }