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    
058    package org.jfree.data.statistics;
059    
060    import java.util.List;
061    
062    import org.jfree.data.KeyedObjects2D;
063    import org.jfree.data.Range;
064    import org.jfree.data.RangeInfo;
065    import org.jfree.data.general.AbstractDataset;
066    import org.jfree.data.general.DatasetChangeEvent;
067    import org.jfree.util.PublicCloneable;
068    
069    /**
070     * A convenience class that provides a default implementation of the
071     * {@link StatisticalCategoryDataset} interface.
072     */
073    public 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    }