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