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