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