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