001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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-2007, 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}