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