    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 * 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
    057package org.jfree.data.statistics;
    058
    059import java.util.List;
    060
    061import org.jfree.data.KeyedObjects2D;
    062import org.jfree.data.Range;
    063import org.jfree.data.RangeInfo;
    064import org.jfree.data.general.AbstractDataset;
    065import org.jfree.data.general.DatasetChangeEvent;
    066import org.jfree.util.ObjectUtilities;
    067import org.jfree.util.PublicCloneable;
    068
    069/**
    070 * A convenience class that provides a default implementation of the
    071 * {@link BoxAndWhiskerCategoryDataset} interface.
    072 */
    073public 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}