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 * DefaultStatisticalCategoryDataset.java
029 * --------------------------------------
030 * (C) Copyright 2002-2011, by Pascal Collet and Contributors.
031 *
032 * Original Author:  Pascal Collet;
033 * Contributor(s):   David Gilbert;
034 *
035 * Changes
036 * -------
037 * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG);
038 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
039 * 05-Feb-2003 : Revised implementation to use KeyedObjects2D (DG);
040 * 28-Aug-2003 : Moved from org.jfree.data --> org.jfree.data.statistics (DG);
041 * 06-Oct-2003 : Removed incorrect Javadoc text (DG);
042 * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
043 * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
044 *               release (DG);
045 * 01-Feb-2005 : Changed minimumRangeValue and maximumRangeValue from Double
046 *               to double (DG);
047 * 05-Feb-2005 : Implemented equals() method (DG);
048 * ------------- JFREECHART 1.0.x ---------------------------------------------
049 * 08-Aug-2006 : Reworked implementation of RangeInfo methods (DG);
050 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
051 * 28-Sep-2007 : Fixed cloning bug (DG);
052 * 02-Oct-2007 : Fixed bug updating cached range values (DG);
053 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
054 * 20-Oct-2011 : Fixed getRangeBounds() bug 3072674 (DG);
055 * 19-Jan-2019 : Added missing hashCode (TH);
056 *
057 */
058
059package org.jfree.data.statistics;
060
061import java.util.List;
062import java.util.Objects;
063import org.jfree.chart.internal.CloneUtils;
064import org.jfree.chart.api.PublicCloneable;
065
066import org.jfree.data.KeyedObjects2D;
067import org.jfree.data.Range;
068import org.jfree.data.RangeInfo;
069import org.jfree.data.general.AbstractDataset;
070import org.jfree.data.general.DatasetChangeEvent;
071
072/**
073 * A convenience class that provides a default implementation of the
074 * {@link StatisticalCategoryDataset} interface.
075 */
076public class DefaultStatisticalCategoryDataset<R extends Comparable<R>, 
077        C extends Comparable<C>>  extends AbstractDataset
078        implements StatisticalCategoryDataset<R, C>, RangeInfo, PublicCloneable {
079
080    /** Storage for the data. */
081    private KeyedObjects2D<R, C> data;
082
083    /** The minimum range value. */
084    private double minimumRangeValue;
085
086    /** The row index for the minimum range value. */
087    private int minimumRangeValueRow;
088
089    /** The column index for the minimum range value. */
090    private int minimumRangeValueColumn;
091
092    /** The minimum range value including the standard deviation. */
093    private double minimumRangeValueIncStdDev;
094
095    /**
096     * The row index for the minimum range value (including the standard
097     * deviation).
098     */
099    private int minimumRangeValueIncStdDevRow;
100
101    /**
102     * The column index for the minimum range value (including the standard
103     * deviation).
104     */
105    private int minimumRangeValueIncStdDevColumn;
106
107    /** The maximum range value. */
108    private double maximumRangeValue;
109
110    /** The row index for the maximum range value. */
111    private int maximumRangeValueRow;
112
113    /** The column index for the maximum range value. */
114    private int maximumRangeValueColumn;
115
116    /** The maximum range value including the standard deviation. */
117    private double maximumRangeValueIncStdDev;
118
119    /**
120     * The row index for the maximum range value (including the standard
121     * deviation).
122     */
123    private int maximumRangeValueIncStdDevRow;
124
125    /**
126     * The column index for the maximum range value (including the standard
127     * deviation).
128     */
129    private int maximumRangeValueIncStdDevColumn;
130
131    /**
132     * Creates a new dataset.
133     */
134    public DefaultStatisticalCategoryDataset() {
135        this.data = new KeyedObjects2D<>();
136        this.minimumRangeValue = Double.NaN;
137        this.minimumRangeValueRow = -1;
138        this.minimumRangeValueColumn = -1;
139        this.maximumRangeValue = Double.NaN;
140        this.maximumRangeValueRow = -1;
141        this.maximumRangeValueColumn = -1;
142        this.minimumRangeValueIncStdDev = Double.NaN;
143        this.minimumRangeValueIncStdDevRow = -1;
144        this.minimumRangeValueIncStdDevColumn = -1;
145        this.maximumRangeValueIncStdDev = Double.NaN;
146        this.maximumRangeValueIncStdDevRow = -1;
147        this.maximumRangeValueIncStdDevColumn = -1;
148    }
149
150    /**
151     * Returns the mean value for an item.
152     *
153     * @param row  the row index (zero-based).
154     * @param column  the column index (zero-based).
155     *
156     * @return The mean value (possibly {@code null}).
157     */
158    @Override
159    public Number getMeanValue(int row, int column) {
160        Number result = null;
161        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
162                this.data.getObject(row, column);
163        if (masd != null) {
164            result = masd.getMean();
165        }
166        return result;
167    }
168
169    /**
170     * Returns the value for an item (for this dataset, the mean value is
171     * returned).
172     *
173     * @param row  the row index.
174     * @param column  the column index.
175     *
176     * @return The value (possibly {@code null}).
177     */
178    @Override
179    public Number getValue(int row, int column) {
180        return getMeanValue(row, column);
181    }
182
183    /**
184     * Returns the value for an item (for this dataset, the mean value is
185     * returned).
186     *
187     * @param rowKey  the row key.
188     * @param columnKey  the columnKey.
189     *
190     * @return The value (possibly {@code null}).
191     */
192    @Override
193    public Number getValue(R rowKey, C columnKey) {
194        return getMeanValue(rowKey, columnKey);
195    }
196
197    /**
198     * Returns the mean value for an item.
199     *
200     * @param rowKey  the row key.
201     * @param columnKey  the columnKey.
202     *
203     * @return The mean value (possibly {@code null}).
204     */
205    @Override
206    public Number getMeanValue(R rowKey, C columnKey) {
207        Number result = null;
208        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
209                this.data.getObject(rowKey, columnKey);
210        if (masd != null) {
211            result = masd.getMean();
212        }
213        return result;
214    }
215
216    /**
217     * Returns the standard deviation value for an item.
218     *
219     * @param row  the row index (zero-based).
220     * @param column  the column index (zero-based).
221     *
222     * @return The standard deviation (possibly {@code null}).
223     */
224    @Override
225    public Number getStdDevValue(int row, int column) {
226        Number result = null;
227        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
228                this.data.getObject(row, column);
229        if (masd != null) {
230            result = masd.getStandardDeviation();
231        }
232        return result;
233    }
234
235    /**
236     * Returns the standard deviation value for an item.
237     *
238     * @param rowKey  the row key.
239     * @param columnKey  the columnKey.
240     *
241     * @return The standard deviation (possibly {@code null}).
242     */
243    @Override
244    public Number getStdDevValue(R rowKey, C columnKey) {
245        Number result = null;
246        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
247                this.data.getObject(rowKey, columnKey);
248        if (masd != null) {
249            result = masd.getStandardDeviation();
250        }
251        return result;
252    }
253
254    /**
255     * Returns the column index for a given key.
256     *
257     * @param key  the column key ({@code null} not permitted).
258     *
259     * @return The column index.
260     */
261    @Override
262    public int getColumnIndex(C key) {
263        // defer null argument check
264        return this.data.getColumnIndex(key);
265    }
266
267    /**
268     * Returns a column key.
269     *
270     * @param column  the column index (zero-based).
271     *
272     * @return The column key.
273     */
274    @Override
275    public C getColumnKey(int column) {
276        return this.data.getColumnKey(column);
277    }
278
279    /**
280     * Returns the column keys.
281     *
282     * @return The keys.
283     */
284    @Override
285    public List<C> getColumnKeys() {
286        return this.data.getColumnKeys();
287    }
288
289    /**
290     * Returns the row index for a given key.
291     *
292     * @param key  the row key ({@code null} not permitted).
293     *
294     * @return The row index.
295     */
296    @Override
297    public int getRowIndex(R key) {
298        // defer null argument check
299        return this.data.getRowIndex(key);
300    }
301
302    /**
303     * Returns a row key.
304     *
305     * @param row  the row index (zero-based).
306     *
307     * @return The row key.
308     */
309    @Override
310    public R getRowKey(int row) {
311        return this.data.getRowKey(row);
312    }
313
314    /**
315     * Returns the row keys.
316     *
317     * @return The keys.
318     */
319    @Override
320    public List<R> getRowKeys() {
321        return this.data.getRowKeys();
322    }
323
324    /**
325     * Returns the number of rows in the table.
326     *
327     * @return The row count.
328     *
329     * @see #getColumnCount()
330     */
331    @Override
332    public int getRowCount() {
333        return this.data.getRowCount();
334    }
335
336    /**
337     * Returns the number of columns in the table.
338     *
339     * @return The column count.
340     *
341     * @see #getRowCount()
342     */
343    @Override
344    public int getColumnCount() {
345        return this.data.getColumnCount();
346    }
347
348    /**
349     * Adds a mean and standard deviation to the table.
350     *
351     * @param mean  the mean.
352     * @param standardDeviation  the standard deviation.
353     * @param rowKey  the row key.
354     * @param columnKey  the column key.
355     */
356    public void add(double mean, double standardDeviation, R rowKey, 
357            C columnKey) {
358        add(Double.valueOf(mean), Double.valueOf(standardDeviation), rowKey, 
359                columnKey);
360    }
361
362    /**
363     * Adds a mean and standard deviation to the table.
364     *
365     * @param mean  the mean.
366     * @param standardDeviation  the standard deviation.
367     * @param rowKey  the row key.
368     * @param columnKey  the column key.
369     */
370    public void add(Number mean, Number standardDeviation,
371                    R rowKey, C columnKey) {
372        MeanAndStandardDeviation item = new MeanAndStandardDeviation(
373                mean, standardDeviation);
374        this.data.addObject(item, rowKey, columnKey);
375
376        double m = Double.NaN;
377        double sd = Double.NaN;
378        if (mean != null) {
379            m = mean.doubleValue();
380        }
381        if (standardDeviation != null) {
382            sd = standardDeviation.doubleValue();
383        }
384
385        // update cached range values
386        int r = this.data.getColumnIndex(columnKey);
387        int c = this.data.getRowIndex(rowKey);
388        if ((r == this.maximumRangeValueRow && c
389                == this.maximumRangeValueColumn) || (r
390                == this.maximumRangeValueIncStdDevRow && c
391                == this.maximumRangeValueIncStdDevColumn) || (r
392                == this.minimumRangeValueRow && c
393                == this.minimumRangeValueColumn) || (r
394                == this.minimumRangeValueIncStdDevRow && c
395                == this.minimumRangeValueIncStdDevColumn)) {
396
397            // iterate over all data items and update mins and maxes
398            updateBounds();
399        }
400        else {
401            if (!Double.isNaN(m)) {
402                if (Double.isNaN(this.maximumRangeValue)
403                        || m > this.maximumRangeValue) {
404                    this.maximumRangeValue = m;
405                    this.maximumRangeValueRow = r;
406                    this.maximumRangeValueColumn = c;
407                }
408            }
409
410            if (!Double.isNaN(m + sd)) {
411                if (Double.isNaN(this.maximumRangeValueIncStdDev)
412                        || (m + sd) > this.maximumRangeValueIncStdDev) {
413                    this.maximumRangeValueIncStdDev = m + sd;
414                    this.maximumRangeValueIncStdDevRow = r;
415                    this.maximumRangeValueIncStdDevColumn = c;
416                }
417            }
418
419            if (!Double.isNaN(m)) {
420                if (Double.isNaN(this.minimumRangeValue)
421                        || m < this.minimumRangeValue) {
422                    this.minimumRangeValue = m;
423                    this.minimumRangeValueRow = r;
424                    this.minimumRangeValueColumn = c;
425                }
426            }
427
428            if (!Double.isNaN(m - sd)) {
429                if (Double.isNaN(this.minimumRangeValueIncStdDev)
430                        || (m - sd) < this.minimumRangeValueIncStdDev) {
431                    this.minimumRangeValueIncStdDev = m - sd;
432                    this.minimumRangeValueIncStdDevRow = r;
433                    this.minimumRangeValueIncStdDevColumn = c;
434                }
435            }
436        }
437        fireDatasetChanged();
438    }
439
440    /**
441     * Removes an item from the dataset and sends a {@link DatasetChangeEvent}
442     * to all registered listeners.
443     *
444     * @param rowKey  the row key ({@code null} not permitted).
445     * @param columnKey  the column key ({@code null} not permitted).
446     *
447     * @see #add(double, double, Comparable, Comparable)
448     *
449     * @since 1.0.7
450     */
451    public void remove(R rowKey, C columnKey) {
452        // defer null argument checks
453        int r = getRowIndex(rowKey);
454        int c = getColumnIndex(columnKey);
455        this.data.removeObject(rowKey, columnKey);
456
457        // if this cell held a maximum and/or minimum value, we'll need to
458        // update the cached bounds...
459        if ((r == this.maximumRangeValueRow && c
460                == this.maximumRangeValueColumn) || (r
461                == this.maximumRangeValueIncStdDevRow && c
462                == this.maximumRangeValueIncStdDevColumn) || (r
463                == this.minimumRangeValueRow && c
464                == this.minimumRangeValueColumn) || (r
465                == this.minimumRangeValueIncStdDevRow && c
466                == this.minimumRangeValueIncStdDevColumn)) {
467
468            // iterate over all data items and update mins and maxes
469            updateBounds();
470        }
471
472        fireDatasetChanged();
473    }
474
475
476    /**
477     * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
478     * to all registered listeners.
479     *
480     * @param rowIndex  the row index.
481     *
482     * @see #removeColumn(int)
483     *
484     * @since 1.0.7
485     */
486    public void removeRow(int rowIndex) {
487        this.data.removeRow(rowIndex);
488        updateBounds();
489        fireDatasetChanged();
490    }
491
492    /**
493     * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
494     * to all registered listeners.
495     *
496     * @param rowKey  the row key ({@code null} not permitted).
497     *
498     * @see #removeColumn(Comparable)
499     *
500     * @since 1.0.7
501     */
502    public void removeRow(R rowKey) {
503        this.data.removeRow(rowKey);
504        updateBounds();
505        fireDatasetChanged();
506    }
507
508    /**
509     * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
510     * to all registered listeners.
511     *
512     * @param columnIndex  the column index.
513     *
514     * @see #removeRow(int)
515     *
516     * @since 1.0.7
517     */
518    public void removeColumn(int columnIndex) {
519        this.data.removeColumn(columnIndex);
520        updateBounds();
521        fireDatasetChanged();
522    }
523
524    /**
525     * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
526     * to all registered listeners.
527     *
528     * @param columnKey  the column key ({@code null} not permitted).
529     *
530     * @see #removeRow(Comparable)
531     *
532     * @since 1.0.7
533     */
534    public void removeColumn(C columnKey) {
535        this.data.removeColumn(columnKey);
536        updateBounds();
537        fireDatasetChanged();
538    }
539
540    /**
541     * Clears all data from the dataset and sends a {@link DatasetChangeEvent}
542     * to all registered listeners.
543     *
544     * @since 1.0.7
545     */
546    public void clear() {
547        this.data.clear();
548        updateBounds();
549        fireDatasetChanged();
550    }
551
552    /**
553     * Iterate over all the data items and update the cached bound values.
554     */
555    private void updateBounds() {
556        this.maximumRangeValue = Double.NaN;
557        this.maximumRangeValueRow = -1;
558        this.maximumRangeValueColumn = -1;
559        this.minimumRangeValue = Double.NaN;
560        this.minimumRangeValueRow = -1;
561        this.minimumRangeValueColumn = -1;
562        this.maximumRangeValueIncStdDev = Double.NaN;
563        this.maximumRangeValueIncStdDevRow = -1;
564        this.maximumRangeValueIncStdDevColumn = -1;
565        this.minimumRangeValueIncStdDev = Double.NaN;
566        this.minimumRangeValueIncStdDevRow = -1;
567        this.minimumRangeValueIncStdDevColumn = -1;
568
569        int rowCount = this.data.getRowCount();
570        int columnCount = this.data.getColumnCount();
571        for (int r = 0; r < rowCount; r++) {
572            for (int c = 0; c < columnCount; c++) {
573                MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
574                        this.data.getObject(r, c);
575                if (masd == null) {
576                    continue;
577                }
578                double m = masd.getMeanValue();
579                double sd = masd.getStandardDeviationValue();
580
581                if (!Double.isNaN(m)) {
582
583                    // update the max value
584                    if (Double.isNaN(this.maximumRangeValue)) {
585                        this.maximumRangeValue = m;
586                        this.maximumRangeValueRow = r;
587                        this.maximumRangeValueColumn = c;
588                    }
589                    else {
590                        if (m > this.maximumRangeValue) {
591                            this.maximumRangeValue = m;
592                            this.maximumRangeValueRow = r;
593                            this.maximumRangeValueColumn = c;
594                        }
595                    }
596
597                    // update the min value
598                    if (Double.isNaN(this.minimumRangeValue)) {
599                        this.minimumRangeValue = m;
600                        this.minimumRangeValueRow = r;
601                        this.minimumRangeValueColumn = c;
602                    }
603                    else {
604                        if (m < this.minimumRangeValue) {
605                            this.minimumRangeValue = m;
606                            this.minimumRangeValueRow = r;
607                            this.minimumRangeValueColumn = c;
608                        }
609                    }
610
611                    if (!Double.isNaN(sd)) {
612                        // update the max value
613                        if (Double.isNaN(this.maximumRangeValueIncStdDev)) {
614                            this.maximumRangeValueIncStdDev = m + sd;
615                            this.maximumRangeValueIncStdDevRow = r;
616                            this.maximumRangeValueIncStdDevColumn = c;
617                        }
618                        else {
619                            if (m + sd > this.maximumRangeValueIncStdDev) {
620                                this.maximumRangeValueIncStdDev = m + sd;
621                                this.maximumRangeValueIncStdDevRow = r;
622                                this.maximumRangeValueIncStdDevColumn = c;
623                            }
624                        }
625
626                        // update the min value
627                        if (Double.isNaN(this.minimumRangeValueIncStdDev)) {
628                            this.minimumRangeValueIncStdDev = m - sd;
629                            this.minimumRangeValueIncStdDevRow = r;
630                            this.minimumRangeValueIncStdDevColumn = c;
631                        }
632                        else {
633                            if (m - sd < this.minimumRangeValueIncStdDev) {
634                                this.minimumRangeValueIncStdDev = m - sd;
635                                this.minimumRangeValueIncStdDevRow = r;
636                                this.minimumRangeValueIncStdDevColumn = c;
637                            }
638                        }
639                    }
640                }
641            }
642        }
643    }
644
645    /**
646     * Returns the minimum y-value in the dataset.
647     *
648     * @param includeInterval  a flag that determines whether or not the
649     *                         y-interval is taken into account.
650     *
651     * @return The minimum value.
652     *
653     * @see #getRangeUpperBound(boolean)
654     */
655    @Override
656    public double getRangeLowerBound(boolean includeInterval) {
657        if (includeInterval && !Double.isNaN(this.minimumRangeValueIncStdDev)) {
658            return this.minimumRangeValueIncStdDev;
659        }
660        else {
661            return this.minimumRangeValue;
662        }
663    }
664
665    /**
666     * Returns the maximum y-value in the dataset.
667     *
668     * @param includeInterval  a flag that determines whether or not the
669     *                         y-interval is taken into account.
670     *
671     * @return The maximum value.
672     *
673     * @see #getRangeLowerBound(boolean)
674     */
675    @Override
676    public double getRangeUpperBound(boolean includeInterval) {
677        if (includeInterval && !Double.isNaN(this.maximumRangeValueIncStdDev)) {
678            return this.maximumRangeValueIncStdDev;
679        }
680        else {
681            return this.maximumRangeValue;
682        }
683    }
684
685    /**
686     * Returns the bounds of the values in this dataset's y-values.
687     *
688     * @param includeInterval  a flag that determines whether or not the
689     *                         y-interval is taken into account.
690     *
691     * @return The range.
692     */
693    @Override
694    public Range getRangeBounds(boolean includeInterval) {
695        double lower = getRangeLowerBound(includeInterval);
696        double upper = getRangeUpperBound(includeInterval);
697        if (Double.isNaN(lower) && Double.isNaN(upper)) {
698            return null;
699        }
700        return new Range(lower, upper);
701    }
702
703    /**
704     * Tests this instance for equality with an arbitrary object.
705     *
706     * @param obj  the object ({@code null} permitted).
707     *
708     * @return A boolean.
709     */
710    @Override
711    public boolean equals(Object obj) {
712        if (obj == this) {
713            return true;
714        }
715        if (!(obj instanceof DefaultStatisticalCategoryDataset)) {
716            return false;
717        }
718        DefaultStatisticalCategoryDataset that
719                = (DefaultStatisticalCategoryDataset) obj;
720        if (!this.data.equals(that.data)) {
721            return false;
722        }
723        return true;
724    }
725
726    @Override
727    public int hashCode(){
728        int hash = 7;
729        hash = 47 * hash + Objects.hashCode(this.data);
730        return hash;
731    }
732
733    /**
734     * Returns a clone of this dataset.
735     *
736     * @return A clone of this dataset.
737     *
738     * @throws CloneNotSupportedException if cloning cannot be completed.
739     */
740    @Override
741    public Object clone() throws CloneNotSupportedException {
742        DefaultStatisticalCategoryDataset<R, C> clone
743                = (DefaultStatisticalCategoryDataset<R, C>) super.clone();
744        clone.data = CloneUtils.clone(this.data);
745        return clone;
746    }
747}