1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * --------------------------------------
28  * DefaultStatisticalCategoryDataset.java
29  * --------------------------------------
30  * (C) Copyright 2002-2011, by Pascal Collet and Contributors.
31  *
32  * Original Author:  Pascal Collet;
33  * Contributor(s):   David Gilbert (for Object Refinery Limited);
34  *
35  * Changes
36  * -------
37  * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG);
38  * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
39  * 05-Feb-2003 : Revised implementation to use KeyedObjects2D (DG);
40  * 28-Aug-2003 : Moved from org.jfree.data --> org.jfree.data.statistics (DG);
41  * 06-Oct-2003 : Removed incorrect Javadoc text (DG);
42  * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
43  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
44  *               release (DG);
45  * 01-Feb-2005 : Changed minimumRangeValue and maximumRangeValue from Double
46  *               to double (DG);
47  * 05-Feb-2005 : Implemented equals() method (DG);
48  * ------------- JFREECHART 1.0.x ---------------------------------------------
49  * 08-Aug-2006 : Reworked implementation of RangeInfo methods (DG);
50  * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
51  * 28-Sep-2007 : Fixed cloning bug (DG);
52  * 02-Oct-2007 : Fixed bug updating cached range values (DG);
53  * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
54  * 20-Oct-2011 : Fixed getRangeBounds() bug 3072674 (DG);
55  *
56  */
57 
58 package org.jfree.data.statistics;
59 
60 import java.util.List;
61 
62 import org.jfree.data.KeyedObjects2D;
63 import org.jfree.data.Range;
64 import org.jfree.data.RangeInfo;
65 import org.jfree.data.general.AbstractDataset;
66 import org.jfree.data.general.DatasetChangeEvent;
67 import org.jfree.util.PublicCloneable;
68 
69 /**
70  * A convenience class that provides a default implementation of the
71  * {@link StatisticalCategoryDataset} interface.
72  */
73 public class DefaultStatisticalCategoryDataset extends AbstractDataset
74         implements StatisticalCategoryDataset, RangeInfo, PublicCloneable {
75 
76     /** Storage for the data. */
77     private KeyedObjects2D data;
78 
79     /** The minimum range value. */
80     private double minimumRangeValue;
81 
82     /** The row index for the minimum range value. */
83     private int minimumRangeValueRow;
84 
85     /** The column index for the minimum range value. */
86     private int minimumRangeValueColumn;
87 
88     /** The minimum range value including the standard deviation. */
89     private double minimumRangeValueIncStdDev;
90 
91     /**
92      * The row index for the minimum range value (including the standard
93      * deviation).
94      */
95     private int minimumRangeValueIncStdDevRow;
96 
97     /**
98      * The column index for the minimum range value (including the standard
99      * 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      */
DefaultStatisticalCategoryDataset()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
getMeanValue(int row, int column)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
getValue(int row, int column)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
getValue(Comparable rowKey, Comparable columnKey)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
getMeanValue(Comparable rowKey, Comparable columnKey)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
getStdDevValue(int row, int column)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
getStdDevValue(Comparable rowKey, Comparable columnKey)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
getColumnIndex(Comparable key)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
getColumnKey(int column)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
getColumnKeys()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
getRowIndex(Comparable key)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
getRowKey(int row)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
getRowKeys()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
getRowCount()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
getColumnCount()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      */
add(double mean, double standardDeviation, Comparable rowKey, Comparable columnKey)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      */
add(Number mean, Number standardDeviation, Comparable rowKey, Comparable columnKey)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      */
remove(Comparable rowKey, Comparable columnKey)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      */
removeRow(int rowIndex)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      */
removeRow(Comparable rowKey)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      */
removeColumn(int columnIndex)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      */
removeColumn(Comparable columnKey)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      */
clear()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      */
updateBounds()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
getRangeLowerBound(boolean includeInterval)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
getRangeUpperBound(boolean includeInterval)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
getRangeBounds(boolean includeInterval)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
equals(Object obj)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
clone()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 }
736