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