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