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