    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     * DefaultBoxAndWhiskerCategoryDataset.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     * 27-Aug-2003 : Moved from org.jfree.data --> org.jfree.data.statistics (DG);
    040     * 12-Nov-2003 : Changed 'data' from private to protected and added a new 'add'
    041     *               method as proposed by Tim Bardzil.  Also removed old code (DG);
    042     * 01-Mar-2004 : Added equals() method (DG);
    043     * 18-Nov-2004 : Updates for changes in RangeInfo interface (DG);
    044     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
    045     *               release (DG);
    046     * ------------- JFREECHART 1.0.x ---------------------------------------------
    047     * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
    048     * 17-Apr-2007 : Fixed bug 1701822 (DG);
    049     * 13-Jun-2007 : Fixed error in previous patch (DG);
    050     * 28-Sep-2007 : Fixed cloning bug (DG);
    051     * 02-Oct-2007 : Fixed bug in updating cached bounds (DG);
    052     * 03-Oct-2007 : Fixed another bug in updating cached bounds, added removal
    053     *               methods (DG);
    054     *
    055     */
    056
    057    package org.jfree.data.statistics;
    058
    059    import java.util.List;
    060
    061    import org.jfree.data.KeyedObjects2D;
    062    import org.jfree.data.Range;
    063    import org.jfree.data.RangeInfo;
    064    import org.jfree.data.general.AbstractDataset;
    065    import org.jfree.data.general.DatasetChangeEvent;
    066    import org.jfree.util.ObjectUtilities;
    067    import org.jfree.util.PublicCloneable;
    068
    069    /**
    070     * A convenience class that provides a default implementation of the
    071     * {@link BoxAndWhiskerCategoryDataset} interface.
    072     */
    073    public class DefaultBoxAndWhiskerCategoryDataset extends AbstractDataset
    074            implements BoxAndWhiskerCategoryDataset, RangeInfo, PublicCloneable {
    075
    076        /** Storage for the data. */
    077        protected KeyedObjects2D data;
    078
    079        /** The minimum range value. */
    080        private double minimumRangeValue;
    081
    082        /** The row index for the cell that the minimum range value comes from. */
    083        private int minimumRangeValueRow;
    084
    085        /**
    086         * The column index for the cell that the minimum range value comes from.
    087         */
    088        private int minimumRangeValueColumn;
    089
    090        /** The maximum range value. */
    091        private double maximumRangeValue;
    092
    093        /** The row index for the cell that the maximum range value comes from. */
    094        private int maximumRangeValueRow;
    095
    096        /**
    097         * The column index for the cell that the maximum range value comes from.
    098         */
    099        private int maximumRangeValueColumn;
    100
    101        /**
    102         * Creates a new dataset.
    103         */
    104        public DefaultBoxAndWhiskerCategoryDataset() {
    105            this.data = new KeyedObjects2D();
    106            this.minimumRangeValue = Double.NaN;
    107            this.minimumRangeValueRow = -1;
    108            this.minimumRangeValueColumn = -1;
    109            this.maximumRangeValue = Double.NaN;
    110            this.maximumRangeValueRow = -1;
    111            this.maximumRangeValueColumn = -1;
    112        }
    113
    114        /**
    115         * Adds a list of values relating to one box-and-whisker entity to the
    116         * table.  The various median values are calculated.
    117         *
    118         * @param list  a collection of values from which the various medians will
    119         *              be calculated.
    120         * @param rowKey  the row key (<code>null</code> not permitted).
    121         * @param columnKey  the column key (<code>null</code> not permitted).
    122         *
    123         * @see #add(BoxAndWhiskerItem, Comparable, Comparable)
    124         */
    125        public void add(List list, Comparable rowKey, Comparable columnKey) {
    126            BoxAndWhiskerItem item = BoxAndWhiskerCalculator
    127                    .calculateBoxAndWhiskerStatistics(list);
    128            add(item, rowKey, columnKey);
    129        }
    130
    131        /**
    132         * Adds a list of values relating to one Box and Whisker entity to the
    133         * table.  The various median values are calculated.
    134         *
    135         * @param item  a box and whisker item (<code>null</code> not permitted).
    136         * @param rowKey  the row key (<code>null</code> not permitted).
    137         * @param columnKey  the column key (<code>null</code> not permitted).
    138         *
    139         * @see #add(List, Comparable, Comparable)
    140         */
    141        public void add(BoxAndWhiskerItem item, Comparable rowKey,
    142                Comparable columnKey) {
    143
    144            this.data.addObject(item, rowKey, columnKey);
    145
    146            // update cached min and max values
    147            int r = this.data.getRowIndex(rowKey);
    148            int c = this.data.getColumnIndex(columnKey);
    149            if ((this.maximumRangeValueRow == r && this.maximumRangeValueColumn
    150                    == c) || (this.minimumRangeValueRow == r
    151                    && this.minimumRangeValueColumn == c))  {
    152                updateBounds();
    153            }
    154            else {
    155
    156                double minval = Double.NaN;
    157                if (item.getMinOutlier() != null) {
    158                    minval = item.getMinOutlier().doubleValue();
    159                }
    160                double maxval = Double.NaN;
    161                if (item.getMaxOutlier() != null) {
    162                    maxval = item.getMaxOutlier().doubleValue();
    163                }
    164
    165                if (Double.isNaN(this.maximumRangeValue)) {
    166                    this.maximumRangeValue = maxval;
    167                    this.maximumRangeValueRow = r;
    168                    this.maximumRangeValueColumn = c;
    169                }
    170                else if (maxval > this.maximumRangeValue) {
    171                    this.maximumRangeValue = maxval;
    172                    this.maximumRangeValueRow = r;
    173                    this.maximumRangeValueColumn = c;
    174                }
    175
    176                if (Double.isNaN(this.minimumRangeValue)) {
    177                    this.minimumRangeValue = minval;
    178                    this.minimumRangeValueRow = r;
    179                    this.minimumRangeValueColumn = c;
    180                }
    181                else if (minval < this.minimumRangeValue) {
    182                    this.minimumRangeValue = minval;
    183                    this.minimumRangeValueRow = r;
    184                    this.minimumRangeValueColumn = c;
    185                }
    186            }
    187
    188            fireDatasetChanged();
    189
    190        }
    191
    192        /**
    193         * Removes an item from the dataset and sends a {@link DatasetChangeEvent}
    194         * to all registered listeners.
    195         *
    196         * @param rowKey  the row key (<code>null</code> not permitted).
    197         * @param columnKey  the column key (<code>null</code> not permitted).
    198         *
    199         * @see #add(BoxAndWhiskerItem, Comparable, Comparable)
    200         *
    201         * @since 1.0.7
    202         */
    203        public void remove(Comparable rowKey, Comparable columnKey) {
    204            // defer null argument checks
    205            int r = getRowIndex(rowKey);
    206            int c = getColumnIndex(columnKey);
    207            this.data.removeObject(rowKey, columnKey);
    208
    209            // if this cell held a maximum and/or minimum value, we'll need to
    210            // update the cached bounds...
    211            if ((this.maximumRangeValueRow == r && this.maximumRangeValueColumn
    212                    == c) || (this.minimumRangeValueRow == r
    213                    && this.minimumRangeValueColumn == c))  {
    214                updateBounds();
    215            }
    216
    217            fireDatasetChanged();
    218        }
    219
    220        /**
    221         * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
    222         * to all registered listeners.
    223         *
    224         * @param rowIndex  the row index.
    225         *
    226         * @see #removeColumn(int)
    227         *
    228         * @since 1.0.7
    229         */
    230        public void removeRow(int rowIndex) {
    231            this.data.removeRow(rowIndex);
    232            updateBounds();
    233            fireDatasetChanged();
    234        }
    235
    236        /**
    237         * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
    238         * to all registered listeners.
    239         *
    240         * @param rowKey  the row key.
    241         *
    242         * @see #removeColumn(Comparable)
    243         *
    244         * @since 1.0.7
    245         */
    246        public void removeRow(Comparable rowKey) {
    247            this.data.removeRow(rowKey);
    248            updateBounds();
    249            fireDatasetChanged();
    250        }
    251
    252        /**
    253         * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
    254         * to all registered listeners.
    255         *
    256         * @param columnIndex  the column index.
    257         *
    258         * @see #removeRow(int)
    259         *
    260         * @since 1.0.7
    261         */
    262        public void removeColumn(int columnIndex) {
    263            this.data.removeColumn(columnIndex);
    264            updateBounds();
    265            fireDatasetChanged();
    266        }
    267
    268        /**
    269         * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
    270         * to all registered listeners.
    271         *
    272         * @param columnKey  the column key.
    273         *
    274         * @see #removeRow(Comparable)
    275         *
    276         * @since 1.0.7
    277         */
    278        public void removeColumn(Comparable columnKey) {
    279            this.data.removeColumn(columnKey);
    280            updateBounds();
    281            fireDatasetChanged();
    282        }
    283
    284        /**
    285         * Clears all data from the dataset and sends a {@link DatasetChangeEvent}
    286         * to all registered listeners.
    287         *
    288         * @since 1.0.7
    289         */
    290        public void clear() {
    291            this.data.clear();
    292            updateBounds();
    293            fireDatasetChanged();
    294        }
    295
    296        /**
    297         * Return an item from within the dataset.
    298         *
    299         * @param row  the row index.
    300         * @param column  the column index.
    301         *
    302         * @return The item.
    303         */
    304        public BoxAndWhiskerItem getItem(int row, int column) {
    305            return (BoxAndWhiskerItem) this.data.getObject(row, column);
    306        }
    307
    308        /**
    309         * Returns the value for an item.
    310         *
    311         * @param row  the row index.
    312         * @param column  the column index.
    313         *
    314         * @return The value.
    315         *
    316         * @see #getMedianValue(int, int)
    317         * @see #getValue(Comparable, Comparable)
    318         */
    319        public Number getValue(int row, int column) {
    320            return getMedianValue(row, column);
    321        }
    322
    323        /**
    324         * Returns the value for an item.
    325         *
    326         * @param rowKey  the row key.
    327         * @param columnKey  the columnKey.
    328         *
    329         * @return The value.
    330         *
    331         * @see #getMedianValue(Comparable, Comparable)
    332         * @see #getValue(int, int)
    333         */
    334        public Number getValue(Comparable rowKey, Comparable columnKey) {
    335            return getMedianValue(rowKey, columnKey);
    336        }
    337
    338        /**
    339         * Returns the mean value for an item.
    340         *
    341         * @param row  the row index (zero-based).
    342         * @param column  the column index (zero-based).
    343         *
    344         * @return The mean value.
    345         *
    346         * @see #getItem(int, int)
    347         */
    348        public Number getMeanValue(int row, int column) {
    349
    350            Number result = null;
    351            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(row,
    352                    column);
    353            if (item != null) {
    354                result = item.getMean();
    355            }
    356            return result;
    357
    358        }
    359
    360        /**
    361         * Returns the mean value for an item.
    362         *
    363         * @param rowKey  the row key.
    364         * @param columnKey  the column key.
    365         *
    366         * @return The mean value.
    367         *
    368         * @see #getItem(int, int)
    369         */
    370        public Number getMeanValue(Comparable rowKey, Comparable columnKey) {
    371            Number result = null;
    372            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    373                    rowKey, columnKey);
    374            if (item != null) {
    375                result = item.getMean();
    376            }
    377            return result;
    378        }
    379
    380        /**
    381         * Returns the median value for an item.
    382         *
    383         * @param row  the row index (zero-based).
    384         * @param column  the column index (zero-based).
    385         *
    386         * @return The median value.
    387         *
    388         * @see #getItem(int, int)
    389         */
    390        public Number getMedianValue(int row, int column) {
    391            Number result = null;
    392            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(row,
    393                    column);
    394            if (item != null) {
    395                result = item.getMedian();
    396            }
    397            return result;
    398        }
    399
    400        /**
    401         * Returns the median value for an item.
    402         *
    403         * @param rowKey  the row key.
    404         * @param columnKey  the columnKey.
    405         *
    406         * @return The median value.
    407         *
    408         * @see #getItem(int, int)
    409         */
    410        public Number getMedianValue(Comparable rowKey, Comparable columnKey) {
    411            Number result = null;
    412            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    413                    rowKey, columnKey);
    414            if (item != null) {
    415                result = item.getMedian();
    416            }
    417            return result;
    418        }
    419
    420        /**
    421         * Returns the first quartile value.
    422         *
    423         * @param row  the row index (zero-based).
    424         * @param column  the column index (zero-based).
    425         *
    426         * @return The first quartile value.
    427         *
    428         * @see #getItem(int, int)
    429         */
    430        public Number getQ1Value(int row, int column) {
    431            Number result = null;
    432            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    433                    row, column);
    434            if (item != null) {
    435                result = item.getQ1();
    436            }
    437            return result;
    438        }
    439
    440        /**
    441         * Returns the first quartile value.
    442         *
    443         * @param rowKey  the row key.
    444         * @param columnKey  the column key.
    445         *
    446         * @return The first quartile value.
    447         *
    448         * @see #getItem(int, int)
    449         */
    450        public Number getQ1Value(Comparable rowKey, Comparable columnKey) {
    451            Number result = null;
    452            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    453                    rowKey, columnKey);
    454            if (item != null) {
    455                result = item.getQ1();
    456            }
    457            return result;
    458        }
    459
    460        /**
    461         * Returns the third quartile value.
    462         *
    463         * @param row  the row index (zero-based).
    464         * @param column  the column index (zero-based).
    465         *
    466         * @return The third quartile value.
    467         *
    468         * @see #getItem(int, int)
    469         */
    470        public Number getQ3Value(int row, int column) {
    471            Number result = null;
    472            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    473                    row, column);
    474            if (item != null) {
    475                result = item.getQ3();
    476            }
    477            return result;
    478        }
    479
    480        /**
    481         * Returns the third quartile value.
    482         *
    483         * @param rowKey  the row key.
    484         * @param columnKey  the column key.
    485         *
    486         * @return The third quartile value.
    487         *
    488         * @see #getItem(int, int)
    489         */
    490        public Number getQ3Value(Comparable rowKey, Comparable columnKey) {
    491            Number result = null;
    492            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    493                    rowKey, columnKey);
    494            if (item != null) {
    495                result = item.getQ3();
    496            }
    497            return result;
    498        }
    499
    500        /**
    501         * Returns the column index for a given key.
    502         *
    503         * @param key  the column key (<code>null</code> not permitted).
    504         *
    505         * @return The column index.
    506         *
    507         * @see #getColumnKey(int)
    508         */
    509        public int getColumnIndex(Comparable key) {
    510            return this.data.getColumnIndex(key);
    511        }
    512
    513        /**
    514         * Returns a column key.
    515         *
    516         * @param column  the column index (zero-based).
    517         *
    518         * @return The column key.
    519         *
    520         * @see #getColumnIndex(Comparable)
    521         */
    522        public Comparable getColumnKey(int column) {
    523            return this.data.getColumnKey(column);
    524        }
    525
    526        /**
    527         * Returns the column keys.
    528         *
    529         * @return The keys.
    530         *
    531         * @see #getRowKeys()
    532         */
    533        public List getColumnKeys() {
    534            return this.data.getColumnKeys();
    535        }
    536
    537        /**
    538         * Returns the row index for a given key.
    539         *
    540         * @param key  the row key (<code>null</code> not permitted).
    541         *
    542         * @return The row index.
    543         *
    544         * @see #getRowKey(int)
    545         */
    546        public int getRowIndex(Comparable key) {
    547            // defer null argument check
    548            return this.data.getRowIndex(key);
    549        }
    550
    551        /**
    552         * Returns a row key.
    553         *
    554         * @param row  the row index (zero-based).
    555         *
    556         * @return The row key.
    557         *
    558         * @see #getRowIndex(Comparable)
    559         */
    560        public Comparable getRowKey(int row) {
    561            return this.data.getRowKey(row);
    562        }
    563
    564        /**
    565         * Returns the row keys.
    566         *
    567         * @return The keys.
    568         *
    569         * @see #getColumnKeys()
    570         */
    571        public List getRowKeys() {
    572            return this.data.getRowKeys();
    573        }
    574
    575        /**
    576         * Returns the number of rows in the table.
    577         *
    578         * @return The row count.
    579         *
    580         * @see #getColumnCount()
    581         */
    582        public int getRowCount() {
    583            return this.data.getRowCount();
    584        }
    585
    586        /**
    587         * Returns the number of columns in the table.
    588         *
    589         * @return The column count.
    590         *
    591         * @see #getRowCount()
    592         */
    593        public int getColumnCount() {
    594            return this.data.getColumnCount();
    595        }
    596
    597        /**
    598         * Returns the minimum y-value in the dataset.
    599         *
    600         * @param includeInterval  a flag that determines whether or not the
    601         *                         y-interval is taken into account.
    602         *
    603         * @return The minimum value.
    604         *
    605         * @see #getRangeUpperBound(boolean)
    606         */
    607        public double getRangeLowerBound(boolean includeInterval) {
    608            return this.minimumRangeValue;
    609        }
    610
    611        /**
    612         * Returns the maximum y-value in the dataset.
    613         *
    614         * @param includeInterval  a flag that determines whether or not the
    615         *                         y-interval is taken into account.
    616         *
    617         * @return The maximum value.
    618         *
    619         * @see #getRangeLowerBound(boolean)
    620         */
    621        public double getRangeUpperBound(boolean includeInterval) {
    622            return this.maximumRangeValue;
    623        }
    624
    625        /**
    626         * Returns the range of the values in this dataset's range.
    627         *
    628         * @param includeInterval  a flag that determines whether or not the
    629         *                         y-interval is taken into account.
    630         *
    631         * @return The range.
    632         */
    633        public Range getRangeBounds(boolean includeInterval) {
    634            return new Range(this.minimumRangeValue, this.maximumRangeValue);
    635        }
    636
    637        /**
    638         * Returns the minimum regular (non outlier) value for an item.
    639         *
    640         * @param row  the row index (zero-based).
    641         * @param column  the column index (zero-based).
    642         *
    643         * @return The minimum regular value.
    644         *
    645         * @see #getItem(int, int)
    646         */
    647        public Number getMinRegularValue(int row, int column) {
    648            Number result = null;
    649            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    650                    row, column);
    651            if (item != null) {
    652                result = item.getMinRegularValue();
    653            }
    654            return result;
    655        }
    656
    657        /**
    658         * Returns the minimum regular (non outlier) value for an item.
    659         *
    660         * @param rowKey  the row key.
    661         * @param columnKey  the column key.
    662         *
    663         * @return The minimum regular value.
    664         *
    665         * @see #getItem(int, int)
    666         */
    667        public Number getMinRegularValue(Comparable rowKey, Comparable columnKey) {
    668            Number result = null;
    669            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    670                    rowKey, columnKey);
    671            if (item != null) {
    672                result = item.getMinRegularValue();
    673            }
    674            return result;
    675        }
    676
    677        /**
    678         * Returns the maximum regular (non outlier) value for an item.
    679         *
    680         * @param row  the row index (zero-based).
    681         * @param column  the column index (zero-based).
    682         *
    683         * @return The maximum regular value.
    684         *
    685         * @see #getItem(int, int)
    686         */
    687        public Number getMaxRegularValue(int row, int column) {
    688            Number result = null;
    689            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    690                    row, column);
    691            if (item != null) {
    692                result = item.getMaxRegularValue();
    693            }
    694            return result;
    695        }
    696
    697        /**
    698         * Returns the maximum regular (non outlier) value for an item.
    699         *
    700         * @param rowKey  the row key.
    701         * @param columnKey  the column key.
    702         *
    703         * @return The maximum regular value.
    704         *
    705         * @see #getItem(int, int)
    706         */
    707        public Number getMaxRegularValue(Comparable rowKey, Comparable columnKey) {
    708            Number result = null;
    709            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    710                    rowKey, columnKey);
    711            if (item != null) {
    712                result = item.getMaxRegularValue();
    713            }
    714            return result;
    715        }
    716
    717        /**
    718         * Returns the minimum outlier (non farout) value for an item.
    719         *
    720         * @param row  the row index (zero-based).
    721         * @param column  the column index (zero-based).
    722         *
    723         * @return The minimum outlier.
    724         *
    725         * @see #getItem(int, int)
    726         */
    727        public Number getMinOutlier(int row, int column) {
    728            Number result = null;
    729            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    730                    row, column);
    731            if (item != null) {
    732                result = item.getMinOutlier();
    733            }
    734            return result;
    735        }
    736
    737        /**
    738         * Returns the minimum outlier (non farout) value for an item.
    739         *
    740         * @param rowKey  the row key.
    741         * @param columnKey  the column key.
    742         *
    743         * @return The minimum outlier.
    744         *
    745         * @see #getItem(int, int)
    746         */
    747        public Number getMinOutlier(Comparable rowKey, Comparable columnKey) {
    748            Number result = null;
    749            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    750                    rowKey, columnKey);
    751            if (item != null) {
    752                result = item.getMinOutlier();
    753            }
    754            return result;
    755        }
    756
    757        /**
    758         * Returns the maximum outlier (non farout) value for an item.
    759         *
    760         * @param row  the row index (zero-based).
    761         * @param column  the column index (zero-based).
    762         *
    763         * @return The maximum outlier.
    764         *
    765         * @see #getItem(int, int)
    766         */
    767        public Number getMaxOutlier(int row, int column) {
    768            Number result = null;
    769            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    770                    row, column);
    771            if (item != null) {
    772                result = item.getMaxOutlier();
    773            }
    774            return result;
    775        }
    776
    777        /**
    778         * Returns the maximum outlier (non farout) value for an item.
    779         *
    780         * @param rowKey  the row key.
    781         * @param columnKey  the column key.
    782         *
    783         * @return The maximum outlier.
    784         *
    785         * @see #getItem(int, int)
    786         */
    787        public Number getMaxOutlier(Comparable rowKey, Comparable columnKey) {
    788            Number result = null;
    789            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    790                    rowKey, columnKey);
    791            if (item != null) {
    792                result = item.getMaxOutlier();
    793            }
    794            return result;
    795        }
    796
    797        /**
    798         * Returns a list of outlier values for an item.
    799         *
    800         * @param row  the row index (zero-based).
    801         * @param column  the column index (zero-based).
    802         *
    803         * @return A list of outlier values.
    804         *
    805         * @see #getItem(int, int)
    806         */
    807        public List getOutliers(int row, int column) {
    808            List result = null;
    809            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    810                    row, column);
    811            if (item != null) {
    812                result = item.getOutliers();
    813            }
    814            return result;
    815        }
    816
    817        /**
    818         * Returns a list of outlier values for an item.
    819         *
    820         * @param rowKey  the row key.
    821         * @param columnKey  the column key.
    822         *
    823         * @return A list of outlier values.
    824         *
    825         * @see #getItem(int, int)
    826         */
    827        public List getOutliers(Comparable rowKey, Comparable columnKey) {
    828            List result = null;
    829            BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
    830                    rowKey, columnKey);
    831            if (item != null) {
    832                result = item.getOutliers();
    833            }
    834            return result;
    835        }
    836
    837        /**
    838         * Resets the cached bounds, by iterating over the entire dataset to find
    839         * the current bounds.
    840         */
    841        private void updateBounds() {
    842            this.minimumRangeValue = Double.NaN;
    843            this.minimumRangeValueRow = -1;
    844            this.minimumRangeValueColumn = -1;
    845            this.maximumRangeValue = Double.NaN;
    846            this.maximumRangeValueRow = -1;
    847            this.maximumRangeValueColumn = -1;
    848            int rowCount = getRowCount();
    849            int columnCount = getColumnCount();
    850            for (int r = 0; r < rowCount; r++) {
    851                for (int c = 0; c < columnCount; c++) {
    852                    BoxAndWhiskerItem item = getItem(r, c);
    853                    if (item != null) {
    854                        Number min = item.getMinOutlier();
    855                        if (min != null) {
    856                            double minv = min.doubleValue();
    857                            if (!Double.isNaN(minv)) {
    858                                if (minv < this.minimumRangeValue || Double.isNaN(
    859                                        this.minimumRangeValue)) {
    860                                    this.minimumRangeValue = minv;
    861                                    this.minimumRangeValueRow = r;
    862                                    this.minimumRangeValueColumn = c;
    863                                }
    864                            }
    865                        }
    866                        Number max = item.getMaxOutlier();
    867                        if (max != null) {
    868                            double maxv = max.doubleValue();
    869                            if (!Double.isNaN(maxv)) {
    870                                if (maxv > this.maximumRangeValue || Double.isNaN(
    871                                        this.maximumRangeValue)) {
    872                                    this.maximumRangeValue = maxv;
    873                                    this.maximumRangeValueRow = r;
    874                                    this.maximumRangeValueColumn = c;
    875                                }
    876                            }
    877                        }
    878                    }
    879                }
    880            }
    881        }
    882
    883        /**
    884         * Tests this dataset for equality with an arbitrary object.
    885         *
    886         * @param obj  the object to test against (<code>null</code> permitted).
    887         *
    888         * @return A boolean.
    889         */
    890        public boolean equals(Object obj) {
    891            if (obj == this) {
    892                return true;
    893            }
    894            if (obj instanceof DefaultBoxAndWhiskerCategoryDataset) {
    895                DefaultBoxAndWhiskerCategoryDataset dataset
    896                        = (DefaultBoxAndWhiskerCategoryDataset) obj;
    897                return ObjectUtilities.equal(this.data, dataset.data);
    898            }
    899            return false;
    900        }
    901
    902        /**
    903         * Returns a clone of this dataset.
    904         *
    905         * @return A clone.
    906         *
    907         * @throws CloneNotSupportedException if cloning is not possible.
    908         */
    909        public Object clone() throws CloneNotSupportedException {
    910            DefaultBoxAndWhiskerCategoryDataset clone
    911                    = (DefaultBoxAndWhiskerCategoryDataset) super.clone();
    912            clone.data = (KeyedObjects2D) this.data.clone();
    913            return clone;
    914        }
    915
    916    }