001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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    @Override
320    public Number getValue(int row, int column) {
321        return getMedianValue(row, column);
322    }
323
324    /**
325     * Returns the value for an item.
326     *
327     * @param rowKey  the row key.
328     * @param columnKey  the columnKey.
329     *
330     * @return The value.
331     *
332     * @see #getMedianValue(Comparable, Comparable)
333     * @see #getValue(int, int)
334     */
335    @Override
336    public Number getValue(Comparable rowKey, Comparable columnKey) {
337        return getMedianValue(rowKey, columnKey);
338    }
339
340    /**
341     * Returns the mean value for an item.
342     *
343     * @param row  the row index (zero-based).
344     * @param column  the column index (zero-based).
345     *
346     * @return The mean value.
347     *
348     * @see #getItem(int, int)
349     */
350    @Override
351    public Number getMeanValue(int row, int column) {
352
353        Number result = null;
354        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(row,
355                column);
356        if (item != null) {
357            result = item.getMean();
358        }
359        return result;
360
361    }
362
363    /**
364     * Returns the mean value for an item.
365     *
366     * @param rowKey  the row key.
367     * @param columnKey  the column key.
368     *
369     * @return The mean value.
370     *
371     * @see #getItem(int, int)
372     */
373    @Override
374    public Number getMeanValue(Comparable rowKey, Comparable columnKey) {
375        Number result = null;
376        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
377                rowKey, columnKey);
378        if (item != null) {
379            result = item.getMean();
380        }
381        return result;
382    }
383
384    /**
385     * Returns the median value for an item.
386     *
387     * @param row  the row index (zero-based).
388     * @param column  the column index (zero-based).
389     *
390     * @return The median value.
391     *
392     * @see #getItem(int, int)
393     */
394    @Override
395    public Number getMedianValue(int row, int column) {
396        Number result = null;
397        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(row,
398                column);
399        if (item != null) {
400            result = item.getMedian();
401        }
402        return result;
403    }
404
405    /**
406     * Returns the median value for an item.
407     *
408     * @param rowKey  the row key.
409     * @param columnKey  the columnKey.
410     *
411     * @return The median value.
412     *
413     * @see #getItem(int, int)
414     */
415    @Override
416    public Number getMedianValue(Comparable rowKey, Comparable columnKey) {
417        Number result = null;
418        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
419                rowKey, columnKey);
420        if (item != null) {
421            result = item.getMedian();
422        }
423        return result;
424    }
425
426    /**
427     * Returns the first quartile value.
428     *
429     * @param row  the row index (zero-based).
430     * @param column  the column index (zero-based).
431     *
432     * @return The first quartile value.
433     *
434     * @see #getItem(int, int)
435     */
436    @Override
437    public Number getQ1Value(int row, int column) {
438        Number result = null;
439        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
440                row, column);
441        if (item != null) {
442            result = item.getQ1();
443        }
444        return result;
445    }
446
447    /**
448     * Returns the first quartile value.
449     *
450     * @param rowKey  the row key.
451     * @param columnKey  the column key.
452     *
453     * @return The first quartile value.
454     *
455     * @see #getItem(int, int)
456     */
457    @Override
458    public Number getQ1Value(Comparable rowKey, Comparable columnKey) {
459        Number result = null;
460        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
461                rowKey, columnKey);
462        if (item != null) {
463            result = item.getQ1();
464        }
465        return result;
466    }
467
468    /**
469     * Returns the third quartile value.
470     *
471     * @param row  the row index (zero-based).
472     * @param column  the column index (zero-based).
473     *
474     * @return The third quartile value.
475     *
476     * @see #getItem(int, int)
477     */
478    @Override
479    public Number getQ3Value(int row, int column) {
480        Number result = null;
481        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
482                row, column);
483        if (item != null) {
484            result = item.getQ3();
485        }
486        return result;
487    }
488
489    /**
490     * Returns the third quartile value.
491     *
492     * @param rowKey  the row key.
493     * @param columnKey  the column key.
494     *
495     * @return The third quartile value.
496     *
497     * @see #getItem(int, int)
498     */
499    @Override
500    public Number getQ3Value(Comparable rowKey, Comparable columnKey) {
501        Number result = null;
502        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
503                rowKey, columnKey);
504        if (item != null) {
505            result = item.getQ3();
506        }
507        return result;
508    }
509
510    /**
511     * Returns the column index for a given key.
512     *
513     * @param key  the column key (<code>null</code> not permitted).
514     *
515     * @return The column index.
516     *
517     * @see #getColumnKey(int)
518     */
519    @Override
520    public int getColumnIndex(Comparable key) {
521        return this.data.getColumnIndex(key);
522    }
523
524    /**
525     * Returns a column key.
526     *
527     * @param column  the column index (zero-based).
528     *
529     * @return The column key.
530     *
531     * @see #getColumnIndex(Comparable)
532     */
533    @Override
534    public Comparable getColumnKey(int column) {
535        return this.data.getColumnKey(column);
536    }
537
538    /**
539     * Returns the column keys.
540     *
541     * @return The keys.
542     *
543     * @see #getRowKeys()
544     */
545    @Override
546    public List getColumnKeys() {
547        return this.data.getColumnKeys();
548    }
549
550    /**
551     * Returns the row index for a given key.
552     *
553     * @param key  the row key (<code>null</code> not permitted).
554     *
555     * @return The row index.
556     *
557     * @see #getRowKey(int)
558     */
559    @Override
560    public int getRowIndex(Comparable key) {
561        // defer null argument check
562        return this.data.getRowIndex(key);
563    }
564
565    /**
566     * Returns a row key.
567     *
568     * @param row  the row index (zero-based).
569     *
570     * @return The row key.
571     *
572     * @see #getRowIndex(Comparable)
573     */
574    @Override
575    public Comparable getRowKey(int row) {
576        return this.data.getRowKey(row);
577    }
578
579    /**
580     * Returns the row keys.
581     *
582     * @return The keys.
583     *
584     * @see #getColumnKeys()
585     */
586    @Override
587    public List getRowKeys() {
588        return this.data.getRowKeys();
589    }
590
591    /**
592     * Returns the number of rows in the table.
593     *
594     * @return The row count.
595     *
596     * @see #getColumnCount()
597     */
598    @Override
599    public int getRowCount() {
600        return this.data.getRowCount();
601    }
602
603    /**
604     * Returns the number of columns in the table.
605     *
606     * @return The column count.
607     *
608     * @see #getRowCount()
609     */
610    @Override
611    public int getColumnCount() {
612        return this.data.getColumnCount();
613    }
614
615    /**
616     * Returns the minimum y-value in the dataset.
617     *
618     * @param includeInterval  a flag that determines whether or not the
619     *                         y-interval is taken into account.
620     *
621     * @return The minimum value.
622     *
623     * @see #getRangeUpperBound(boolean)
624     */
625    @Override
626    public double getRangeLowerBound(boolean includeInterval) {
627        return this.minimumRangeValue;
628    }
629
630    /**
631     * Returns the maximum y-value in the dataset.
632     *
633     * @param includeInterval  a flag that determines whether or not the
634     *                         y-interval is taken into account.
635     *
636     * @return The maximum value.
637     *
638     * @see #getRangeLowerBound(boolean)
639     */
640    @Override
641    public double getRangeUpperBound(boolean includeInterval) {
642        return this.maximumRangeValue;
643    }
644
645    /**
646     * Returns the range of the values in this dataset's range.
647     *
648     * @param includeInterval  a flag that determines whether or not the
649     *                         y-interval is taken into account.
650     *
651     * @return The range.
652     */
653    @Override
654    public Range getRangeBounds(boolean includeInterval) {
655        return new Range(this.minimumRangeValue, this.maximumRangeValue);
656    }
657
658    /**
659     * Returns the minimum regular (non outlier) value for an item.
660     *
661     * @param row  the row index (zero-based).
662     * @param column  the column index (zero-based).
663     *
664     * @return The minimum regular value.
665     *
666     * @see #getItem(int, int)
667     */
668    @Override
669    public Number getMinRegularValue(int row, int column) {
670        Number result = null;
671        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
672                row, column);
673        if (item != null) {
674            result = item.getMinRegularValue();
675        }
676        return result;
677    }
678
679    /**
680     * Returns the minimum regular (non outlier) value for an item.
681     *
682     * @param rowKey  the row key.
683     * @param columnKey  the column key.
684     *
685     * @return The minimum regular value.
686     *
687     * @see #getItem(int, int)
688     */
689    @Override
690    public Number getMinRegularValue(Comparable rowKey, Comparable columnKey) {
691        Number result = null;
692        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
693                rowKey, columnKey);
694        if (item != null) {
695            result = item.getMinRegularValue();
696        }
697        return result;
698    }
699
700    /**
701     * Returns the maximum regular (non outlier) value for an item.
702     *
703     * @param row  the row index (zero-based).
704     * @param column  the column index (zero-based).
705     *
706     * @return The maximum regular value.
707     *
708     * @see #getItem(int, int)
709     */
710    @Override
711    public Number getMaxRegularValue(int row, int column) {
712        Number result = null;
713        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
714                row, column);
715        if (item != null) {
716            result = item.getMaxRegularValue();
717        }
718        return result;
719    }
720
721    /**
722     * Returns the maximum regular (non outlier) value for an item.
723     *
724     * @param rowKey  the row key.
725     * @param columnKey  the column key.
726     *
727     * @return The maximum regular value.
728     *
729     * @see #getItem(int, int)
730     */
731    @Override
732    public Number getMaxRegularValue(Comparable rowKey, Comparable columnKey) {
733        Number result = null;
734        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
735                rowKey, columnKey);
736        if (item != null) {
737            result = item.getMaxRegularValue();
738        }
739        return result;
740    }
741
742    /**
743     * Returns the minimum outlier (non farout) value for an item.
744     *
745     * @param row  the row index (zero-based).
746     * @param column  the column index (zero-based).
747     *
748     * @return The minimum outlier.
749     *
750     * @see #getItem(int, int)
751     */
752    @Override
753    public Number getMinOutlier(int row, int column) {
754        Number result = null;
755        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
756                row, column);
757        if (item != null) {
758            result = item.getMinOutlier();
759        }
760        return result;
761    }
762
763    /**
764     * Returns the minimum outlier (non farout) value for an item.
765     *
766     * @param rowKey  the row key.
767     * @param columnKey  the column key.
768     *
769     * @return The minimum outlier.
770     *
771     * @see #getItem(int, int)
772     */
773    @Override
774    public Number getMinOutlier(Comparable rowKey, Comparable columnKey) {
775        Number result = null;
776        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
777                rowKey, columnKey);
778        if (item != null) {
779            result = item.getMinOutlier();
780        }
781        return result;
782    }
783
784    /**
785     * Returns the maximum outlier (non farout) value for an item.
786     *
787     * @param row  the row index (zero-based).
788     * @param column  the column index (zero-based).
789     *
790     * @return The maximum outlier.
791     *
792     * @see #getItem(int, int)
793     */
794    @Override
795    public Number getMaxOutlier(int row, int column) {
796        Number result = null;
797        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
798                row, column);
799        if (item != null) {
800            result = item.getMaxOutlier();
801        }
802        return result;
803    }
804
805    /**
806     * Returns the maximum outlier (non farout) value for an item.
807     *
808     * @param rowKey  the row key.
809     * @param columnKey  the column key.
810     *
811     * @return The maximum outlier.
812     *
813     * @see #getItem(int, int)
814     */
815    @Override
816    public Number getMaxOutlier(Comparable rowKey, Comparable columnKey) {
817        Number result = null;
818        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
819                rowKey, columnKey);
820        if (item != null) {
821            result = item.getMaxOutlier();
822        }
823        return result;
824    }
825
826    /**
827     * Returns a list of outlier values for an item.
828     *
829     * @param row  the row index (zero-based).
830     * @param column  the column index (zero-based).
831     *
832     * @return A list of outlier values.
833     *
834     * @see #getItem(int, int)
835     */
836    @Override
837    public List getOutliers(int row, int column) {
838        List result = null;
839        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
840                row, column);
841        if (item != null) {
842            result = item.getOutliers();
843        }
844        return result;
845    }
846
847    /**
848     * Returns a list of outlier values for an item.
849     *
850     * @param rowKey  the row key.
851     * @param columnKey  the column key.
852     *
853     * @return A list of outlier values.
854     *
855     * @see #getItem(int, int)
856     */
857    @Override
858    public List getOutliers(Comparable rowKey, Comparable columnKey) {
859        List result = null;
860        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
861                rowKey, columnKey);
862        if (item != null) {
863            result = item.getOutliers();
864        }
865        return result;
866    }
867
868    /**
869     * Resets the cached bounds, by iterating over the entire dataset to find
870     * the current bounds.
871     */
872    private void updateBounds() {
873        this.minimumRangeValue = Double.NaN;
874        this.minimumRangeValueRow = -1;
875        this.minimumRangeValueColumn = -1;
876        this.maximumRangeValue = Double.NaN;
877        this.maximumRangeValueRow = -1;
878        this.maximumRangeValueColumn = -1;
879        int rowCount = getRowCount();
880        int columnCount = getColumnCount();
881        for (int r = 0; r < rowCount; r++) {
882            for (int c = 0; c < columnCount; c++) {
883                BoxAndWhiskerItem item = getItem(r, c);
884                if (item != null) {
885                    Number min = item.getMinOutlier();
886                    if (min != null) {
887                        double minv = min.doubleValue();
888                        if (!Double.isNaN(minv)) {
889                            if (minv < this.minimumRangeValue || Double.isNaN(
890                                    this.minimumRangeValue)) {
891                                this.minimumRangeValue = minv;
892                                this.minimumRangeValueRow = r;
893                                this.minimumRangeValueColumn = c;
894                            }
895                        }
896                    }
897                    Number max = item.getMaxOutlier();
898                    if (max != null) {
899                        double maxv = max.doubleValue();
900                        if (!Double.isNaN(maxv)) {
901                            if (maxv > this.maximumRangeValue || Double.isNaN(
902                                    this.maximumRangeValue)) {
903                                this.maximumRangeValue = maxv;
904                                this.maximumRangeValueRow = r;
905                                this.maximumRangeValueColumn = c;
906                            }
907                        }
908                    }
909                }
910            }
911        }
912    }
913
914    /**
915     * Tests this dataset for equality with an arbitrary object.
916     *
917     * @param obj  the object to test against (<code>null</code> permitted).
918     *
919     * @return A boolean.
920     */
921    @Override
922    public boolean equals(Object obj) {
923        if (obj == this) {
924            return true;
925        }
926        if (obj instanceof DefaultBoxAndWhiskerCategoryDataset) {
927            DefaultBoxAndWhiskerCategoryDataset dataset
928                    = (DefaultBoxAndWhiskerCategoryDataset) obj;
929            return ObjectUtilities.equal(this.data, dataset.data);
930        }
931        return false;
932    }
933
934    /**
935     * Returns a clone of this dataset.
936     *
937     * @return A clone.
938     *
939     * @throws CloneNotSupportedException if cloning is not possible.
940     */
941    @Override
942    public Object clone() throws CloneNotSupportedException {
943        DefaultBoxAndWhiskerCategoryDataset clone
944                = (DefaultBoxAndWhiskerCategoryDataset) super.clone();
945        clone.data = (KeyedObjects2D) this.data.clone();
946        return clone;
947    }
948
949}