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