001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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-2021, 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 java.util.Objects;
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    public void remove(Comparable rowKey, Comparable columnKey) {
183        // defer null argument checks
184        int r = getRowIndex(rowKey);
185        int c = getColumnIndex(columnKey);
186        this.data.removeObject(rowKey, columnKey);
187
188        // if this cell held a maximum and/or minimum value, we'll need to
189        // update the cached bounds...
190        if ((this.maximumRangeValueRow == r && this.maximumRangeValueColumn
191                == c) || (this.minimumRangeValueRow == r
192                && this.minimumRangeValueColumn == c))  {
193            updateBounds();
194        }
195
196        fireDatasetChanged();
197    }
198
199    /**
200     * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
201     * to all registered listeners.
202     *
203     * @param rowIndex  the row index.
204     *
205     * @see #removeColumn(int)
206     */
207    public void removeRow(int rowIndex) {
208        this.data.removeRow(rowIndex);
209        updateBounds();
210        fireDatasetChanged();
211    }
212
213    /**
214     * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
215     * to all registered listeners.
216     *
217     * @param rowKey  the row key.
218     *
219     * @see #removeColumn(Comparable)
220     */
221    public void removeRow(Comparable rowKey) {
222        this.data.removeRow(rowKey);
223        updateBounds();
224        fireDatasetChanged();
225    }
226
227    /**
228     * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
229     * to all registered listeners.
230     *
231     * @param columnIndex  the column index.
232     *
233     * @see #removeRow(int)
234     */
235    public void removeColumn(int columnIndex) {
236        this.data.removeColumn(columnIndex);
237        updateBounds();
238        fireDatasetChanged();
239    }
240
241    /**
242     * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
243     * to all registered listeners.
244     *
245     * @param columnKey  the column key.
246     *
247     * @see #removeRow(Comparable)
248     */
249    public void removeColumn(Comparable columnKey) {
250        this.data.removeColumn(columnKey);
251        updateBounds();
252        fireDatasetChanged();
253    }
254
255    /**
256     * Clears all data from the dataset and sends a {@link DatasetChangeEvent}
257     * to all registered listeners.
258     */
259    public void clear() {
260        this.data.clear();
261        updateBounds();
262        fireDatasetChanged();
263    }
264
265    /**
266     * Return an item from within the dataset.
267     *
268     * @param row  the row index.
269     * @param column  the column index.
270     *
271     * @return The item.
272     */
273    public BoxAndWhiskerItem getItem(int row, int column) {
274        return (BoxAndWhiskerItem) this.data.getObject(row, column);
275    }
276
277    /**
278     * Returns the value for an item.
279     *
280     * @param row  the row index.
281     * @param column  the column index.
282     *
283     * @return The value.
284     *
285     * @see #getMedianValue(int, int)
286     * @see #getValue(Comparable, Comparable)
287     */
288    @Override
289    public Number getValue(int row, int column) {
290        return getMedianValue(row, column);
291    }
292
293    /**
294     * Returns the value for an item.
295     *
296     * @param rowKey  the row key.
297     * @param columnKey  the columnKey.
298     *
299     * @return The value.
300     *
301     * @see #getMedianValue(Comparable, Comparable)
302     * @see #getValue(int, int)
303     */
304    @Override
305    public Number getValue(Comparable rowKey, Comparable columnKey) {
306        return getMedianValue(rowKey, columnKey);
307    }
308
309    /**
310     * Returns the mean value for an item.
311     *
312     * @param row  the row index (zero-based).
313     * @param column  the column index (zero-based).
314     *
315     * @return The mean value.
316     *
317     * @see #getItem(int, int)
318     */
319    @Override
320    public Number getMeanValue(int row, int column) {
321
322        Number result = null;
323        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(row,
324                column);
325        if (item != null) {
326            result = item.getMean();
327        }
328        return result;
329
330    }
331
332    /**
333     * Returns the mean value for an item.
334     *
335     * @param rowKey  the row key.
336     * @param columnKey  the column key.
337     *
338     * @return The mean value.
339     *
340     * @see #getItem(int, int)
341     */
342    @Override
343    public Number getMeanValue(Comparable rowKey, Comparable columnKey) {
344        Number result = null;
345        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
346                rowKey, columnKey);
347        if (item != null) {
348            result = item.getMean();
349        }
350        return result;
351    }
352
353    /**
354     * Returns the median value for an item.
355     *
356     * @param row  the row index (zero-based).
357     * @param column  the column index (zero-based).
358     *
359     * @return The median value.
360     *
361     * @see #getItem(int, int)
362     */
363    @Override
364    public Number getMedianValue(int row, int column) {
365        Number result = null;
366        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(row,
367                column);
368        if (item != null) {
369            result = item.getMedian();
370        }
371        return result;
372    }
373
374    /**
375     * Returns the median value for an item.
376     *
377     * @param rowKey  the row key.
378     * @param columnKey  the columnKey.
379     *
380     * @return The median value.
381     *
382     * @see #getItem(int, int)
383     */
384    @Override
385    public Number getMedianValue(Comparable rowKey, Comparable columnKey) {
386        Number result = null;
387        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
388                rowKey, columnKey);
389        if (item != null) {
390            result = item.getMedian();
391        }
392        return result;
393    }
394
395    /**
396     * Returns the first quartile value.
397     *
398     * @param row  the row index (zero-based).
399     * @param column  the column index (zero-based).
400     *
401     * @return The first quartile value.
402     *
403     * @see #getItem(int, int)
404     */
405    @Override
406    public Number getQ1Value(int row, int column) {
407        Number result = null;
408        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
409                row, column);
410        if (item != null) {
411            result = item.getQ1();
412        }
413        return result;
414    }
415
416    /**
417     * Returns the first quartile value.
418     *
419     * @param rowKey  the row key.
420     * @param columnKey  the column key.
421     *
422     * @return The first quartile value.
423     *
424     * @see #getItem(int, int)
425     */
426    @Override
427    public Number getQ1Value(Comparable rowKey, Comparable columnKey) {
428        Number result = null;
429        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
430                rowKey, columnKey);
431        if (item != null) {
432            result = item.getQ1();
433        }
434        return result;
435    }
436
437    /**
438     * Returns the third quartile value.
439     *
440     * @param row  the row index (zero-based).
441     * @param column  the column index (zero-based).
442     *
443     * @return The third quartile value.
444     *
445     * @see #getItem(int, int)
446     */
447    @Override
448    public Number getQ3Value(int row, int column) {
449        Number result = null;
450        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
451                row, column);
452        if (item != null) {
453            result = item.getQ3();
454        }
455        return result;
456    }
457
458    /**
459     * Returns the third quartile value.
460     *
461     * @param rowKey  the row key.
462     * @param columnKey  the column key.
463     *
464     * @return The third quartile value.
465     *
466     * @see #getItem(int, int)
467     */
468    @Override
469    public Number getQ3Value(Comparable rowKey, Comparable columnKey) {
470        Number result = null;
471        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
472                rowKey, columnKey);
473        if (item != null) {
474            result = item.getQ3();
475        }
476        return result;
477    }
478
479    /**
480     * Returns the column index for a given key.
481     *
482     * @param key  the column key ({@code null} not permitted).
483     *
484     * @return The column index.
485     *
486     * @see #getColumnKey(int)
487     */
488    @Override
489    public int getColumnIndex(Comparable key) {
490        return this.data.getColumnIndex(key);
491    }
492
493    /**
494     * Returns a column key.
495     *
496     * @param column  the column index (zero-based).
497     *
498     * @return The column key.
499     *
500     * @see #getColumnIndex(Comparable)
501     */
502    @Override
503    public Comparable getColumnKey(int column) {
504        return this.data.getColumnKey(column);
505    }
506
507    /**
508     * Returns the column keys.
509     *
510     * @return The keys.
511     *
512     * @see #getRowKeys()
513     */
514    @Override
515    public List getColumnKeys() {
516        return this.data.getColumnKeys();
517    }
518
519    /**
520     * Returns the row index for a given key.
521     *
522     * @param key  the row key ({@code null} not permitted).
523     *
524     * @return The row index.
525     *
526     * @see #getRowKey(int)
527     */
528    @Override
529    public int getRowIndex(Comparable key) {
530        // defer null argument check
531        return this.data.getRowIndex(key);
532    }
533
534    /**
535     * Returns a row key.
536     *
537     * @param row  the row index (zero-based).
538     *
539     * @return The row key.
540     *
541     * @see #getRowIndex(Comparable)
542     */
543    @Override
544    public Comparable getRowKey(int row) {
545        return this.data.getRowKey(row);
546    }
547
548    /**
549     * Returns the row keys.
550     *
551     * @return The keys.
552     *
553     * @see #getColumnKeys()
554     */
555    @Override
556    public List getRowKeys() {
557        return this.data.getRowKeys();
558    }
559
560    /**
561     * Returns the number of rows in the table.
562     *
563     * @return The row count.
564     *
565     * @see #getColumnCount()
566     */
567    @Override
568    public int getRowCount() {
569        return this.data.getRowCount();
570    }
571
572    /**
573     * Returns the number of columns in the table.
574     *
575     * @return The column count.
576     *
577     * @see #getRowCount()
578     */
579    @Override
580    public int getColumnCount() {
581        return this.data.getColumnCount();
582    }
583
584    /**
585     * Returns the minimum y-value in the dataset.
586     *
587     * @param includeInterval  a flag that determines whether or not the
588     *                         y-interval is taken into account.
589     *
590     * @return The minimum value.
591     *
592     * @see #getRangeUpperBound(boolean)
593     */
594    @Override
595    public double getRangeLowerBound(boolean includeInterval) {
596        return this.minimumRangeValue;
597    }
598
599    /**
600     * Returns the maximum y-value in the dataset.
601     *
602     * @param includeInterval  a flag that determines whether or not the
603     *                         y-interval is taken into account.
604     *
605     * @return The maximum value.
606     *
607     * @see #getRangeLowerBound(boolean)
608     */
609    @Override
610    public double getRangeUpperBound(boolean includeInterval) {
611        return this.maximumRangeValue;
612    }
613
614    /**
615     * Returns the range of the values in this dataset's range.
616     *
617     * @param includeInterval  a flag that determines whether or not the
618     *                         y-interval is taken into account.
619     *
620     * @return The range.
621     */
622    @Override
623    public Range getRangeBounds(boolean includeInterval) {
624        return new Range(this.minimumRangeValue, this.maximumRangeValue);
625    }
626
627    /**
628     * Returns the minimum regular (non outlier) value for an item.
629     *
630     * @param row  the row index (zero-based).
631     * @param column  the column index (zero-based).
632     *
633     * @return The minimum regular value.
634     *
635     * @see #getItem(int, int)
636     */
637    @Override
638    public Number getMinRegularValue(int row, int column) {
639        Number result = null;
640        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
641                row, column);
642        if (item != null) {
643            result = item.getMinRegularValue();
644        }
645        return result;
646    }
647
648    /**
649     * Returns the minimum regular (non outlier) value for an item.
650     *
651     * @param rowKey  the row key.
652     * @param columnKey  the column key.
653     *
654     * @return The minimum regular value.
655     *
656     * @see #getItem(int, int)
657     */
658    @Override
659    public Number getMinRegularValue(Comparable rowKey, Comparable columnKey) {
660        Number result = null;
661        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
662                rowKey, columnKey);
663        if (item != null) {
664            result = item.getMinRegularValue();
665        }
666        return result;
667    }
668
669    /**
670     * Returns the maximum regular (non outlier) value for an item.
671     *
672     * @param row  the row index (zero-based).
673     * @param column  the column index (zero-based).
674     *
675     * @return The maximum regular value.
676     *
677     * @see #getItem(int, int)
678     */
679    @Override
680    public Number getMaxRegularValue(int row, int column) {
681        Number result = null;
682        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
683                row, column);
684        if (item != null) {
685            result = item.getMaxRegularValue();
686        }
687        return result;
688    }
689
690    /**
691     * Returns the maximum regular (non outlier) value for an item.
692     *
693     * @param rowKey  the row key.
694     * @param columnKey  the column key.
695     *
696     * @return The maximum regular value.
697     *
698     * @see #getItem(int, int)
699     */
700    @Override
701    public Number getMaxRegularValue(Comparable rowKey, Comparable columnKey) {
702        Number result = null;
703        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
704                rowKey, columnKey);
705        if (item != null) {
706            result = item.getMaxRegularValue();
707        }
708        return result;
709    }
710
711    /**
712     * Returns the minimum outlier (non farout) value for an item.
713     *
714     * @param row  the row index (zero-based).
715     * @param column  the column index (zero-based).
716     *
717     * @return The minimum outlier.
718     *
719     * @see #getItem(int, int)
720     */
721    @Override
722    public Number getMinOutlier(int row, int column) {
723        Number result = null;
724        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
725                row, column);
726        if (item != null) {
727            result = item.getMinOutlier();
728        }
729        return result;
730    }
731
732    /**
733     * Returns the minimum outlier (non farout) value for an item.
734     *
735     * @param rowKey  the row key.
736     * @param columnKey  the column key.
737     *
738     * @return The minimum outlier.
739     *
740     * @see #getItem(int, int)
741     */
742    @Override
743    public Number getMinOutlier(Comparable rowKey, Comparable columnKey) {
744        Number result = null;
745        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
746                rowKey, columnKey);
747        if (item != null) {
748            result = item.getMinOutlier();
749        }
750        return result;
751    }
752
753    /**
754     * Returns the maximum outlier (non farout) value for an item.
755     *
756     * @param row  the row index (zero-based).
757     * @param column  the column index (zero-based).
758     *
759     * @return The maximum outlier.
760     *
761     * @see #getItem(int, int)
762     */
763    @Override
764    public Number getMaxOutlier(int row, int column) {
765        Number result = null;
766        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
767                row, column);
768        if (item != null) {
769            result = item.getMaxOutlier();
770        }
771        return result;
772    }
773
774    /**
775     * Returns the maximum outlier (non farout) value for an item.
776     *
777     * @param rowKey  the row key.
778     * @param columnKey  the column key.
779     *
780     * @return The maximum outlier.
781     *
782     * @see #getItem(int, int)
783     */
784    @Override
785    public Number getMaxOutlier(Comparable rowKey, Comparable columnKey) {
786        Number result = null;
787        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
788                rowKey, columnKey);
789        if (item != null) {
790            result = item.getMaxOutlier();
791        }
792        return result;
793    }
794
795    /**
796     * Returns a list of outlier values for an item.
797     *
798     * @param row  the row index (zero-based).
799     * @param column  the column index (zero-based).
800     *
801     * @return A list of outlier values.
802     *
803     * @see #getItem(int, int)
804     */
805    @Override
806    public List getOutliers(int row, int column) {
807        List result = null;
808        BoxAndWhiskerItem item = (BoxAndWhiskerItem) this.data.getObject(
809                row, column);
810        if (item != null) {
811            result = item.getOutliers();
812        }
813        return result;
814    }
815
816    /**
817     * Returns a list of outlier values for an item.
818     *
819     * @param rowKey  the row key.
820     * @param columnKey  the column key.
821     *
822     * @return A list of outlier values.
823     *
824     * @see #getItem(int, int)
825     */
826    @Override
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} permitted).
887     *
888     * @return A boolean.
889     */
890    @Override
891    public boolean equals(Object obj) {
892        if (obj == this) {
893            return true;
894        }
895        if (obj instanceof DefaultBoxAndWhiskerCategoryDataset) {
896            DefaultBoxAndWhiskerCategoryDataset dataset
897                    = (DefaultBoxAndWhiskerCategoryDataset) obj;
898            return Objects.equals(this.data, dataset.data);
899        }
900        return false;
901    }
902
903    /**
904     * Returns a clone of this dataset.
905     *
906     * @return A clone.
907     *
908     * @throws CloneNotSupportedException if cloning is not possible.
909     */
910    @Override
911    public Object clone() throws CloneNotSupportedException {
912        DefaultBoxAndWhiskerCategoryDataset clone
913                = (DefaultBoxAndWhiskerCategoryDataset) super.clone();
914        clone.data = (KeyedObjects2D) this.data.clone();
915        return clone;
916    }
917
918}