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     * DefaultBoxAndWhiskerXYDataset.java
029     * ----------------------------------
030     * (C) Copyright 2003-2008, by David Browning and Contributors.
031     *
032     * Original Author:  David Browning (for Australian Institute of Marine
033     *                   Science);
034     * Contributor(s):   David Gilbert (for Object Refinery Limited);
035     *
036     * Changes
037     * -------
038     * 05-Aug-2003 : Version 1, contributed by David Browning (DG);
039     * 08-Aug-2003 : Minor changes to comments (DB)
040     *               Allow average to be null  - average is a perculiar AIMS
041     *               requirement which probably should be stripped out and overlaid
042     *               if required...
043     *               Added a number of methods to allow the max and min non-outlier
044     *               and non-farout values to be calculated
045     * 12-Aug-2003   Changed the getYValue to return the highest outlier value
046     *               Added getters and setters for outlier and farout coefficients
047     * 27-Aug-2003 : Renamed DefaultBoxAndWhiskerDataset
048     *               --> DefaultBoxAndWhiskerXYDataset (DG);
049     * 06-May-2004 : Now extends AbstractXYDataset (DG);
050     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
051     *               getYValue() (DG);
052     * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
053     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
054     *               release (DG);
055     * ------------- JFREECHART 1.0.x ---------------------------------------------
056     * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
057     * 12-Nov-2007 : Implemented equals() and clone() (DG);
058     *
059     */
060    
061    package org.jfree.data.statistics;
062    
063    import java.util.ArrayList;
064    import java.util.Date;
065    import java.util.List;
066    
067    import org.jfree.data.Range;
068    import org.jfree.data.RangeInfo;
069    import org.jfree.data.general.DatasetChangeEvent;
070    import org.jfree.data.xy.AbstractXYDataset;
071    import org.jfree.util.ObjectUtilities;
072    
073    /**
074     * A simple implementation of the {@link BoxAndWhiskerXYDataset} interface.
075     * This dataset implementation can hold only one series.
076     */
077    public class DefaultBoxAndWhiskerXYDataset extends AbstractXYDataset
078                implements BoxAndWhiskerXYDataset, RangeInfo {
079    
080        /** The series key. */
081        private Comparable seriesKey;
082    
083        /** Storage for the dates. */
084        private List dates;
085    
086        /** Storage for the box and whisker statistics. */
087        private List items;
088    
089        /** The minimum range value. */
090        private Number minimumRangeValue;
091    
092        /** The maximum range value. */
093        private Number maximumRangeValue;
094    
095        /** The range of values. */
096        private Range rangeBounds;
097    
098        /**
099         * The coefficient used to calculate outliers. Tukey's default value is
100         * 1.5 (see EDA) Any value which is greater than Q3 + (interquartile range
101         * * outlier coefficient) is considered to be an outlier.  Can be altered
102         * if the data is particularly skewed.
103         */
104        private double outlierCoefficient = 1.5;
105    
106        /**
107         * The coefficient used to calculate farouts. Tukey's default value is 2
108         * (see EDA) Any value which is greater than Q3 + (interquartile range *
109         * farout coefficient) is considered to be a farout.  Can be altered if the
110         * data is particularly skewed.
111         */
112        private double faroutCoefficient = 2.0;
113    
114        /**
115         * Constructs a new box and whisker dataset.
116         * <p>
117         * The current implementation allows only one series in the dataset.
118         * This may be extended in a future version.
119         *
120         * @param seriesKey  the key for the series.
121         */
122        public DefaultBoxAndWhiskerXYDataset(Comparable seriesKey) {
123            this.seriesKey = seriesKey;
124            this.dates = new ArrayList();
125            this.items = new ArrayList();
126            this.minimumRangeValue = null;
127            this.maximumRangeValue = null;
128            this.rangeBounds = null;
129        }
130    
131        /**
132         * Returns the value used as the outlier coefficient. The outlier
133         * coefficient gives an indication of the degree of certainty in an
134         * unskewed distribution.  Increasing the coefficient increases the number
135         * of values included. Currently only used to ensure farout coefficient is
136         * greater than the outlier coefficient
137         *
138         * @return A <code>double</code> representing the value used to calculate
139         *         outliers.
140         *
141         * @see #setOutlierCoefficient(double)
142         */
143        public double getOutlierCoefficient() {
144            return this.outlierCoefficient;
145        }
146    
147        /**
148         * Sets the value used as the outlier coefficient
149         *
150         * @param outlierCoefficient  being a <code>double</code> representing the
151         *                            value used to calculate outliers.
152         *
153         * @see #getOutlierCoefficient()
154         */
155        public void setOutlierCoefficient(double outlierCoefficient) {
156            this.outlierCoefficient = outlierCoefficient;
157        }
158    
159        /**
160         * Returns the value used as the farout coefficient. The farout coefficient
161         * allows the calculation of which values will be off the graph.
162         *
163         * @return A <code>double</code> representing the value used to calculate
164         *         farouts.
165         *
166         * @see #setFaroutCoefficient(double)
167         */
168        public double getFaroutCoefficient() {
169            return this.faroutCoefficient;
170        }
171    
172        /**
173         * Sets the value used as the farouts coefficient. The farout coefficient
174         * must b greater than the outlier coefficient.
175         *
176         * @param faroutCoefficient being a <code>double</code> representing the
177         *                          value used to calculate farouts.
178         *
179         * @see #getFaroutCoefficient()
180         */
181        public void setFaroutCoefficient(double faroutCoefficient) {
182    
183            if (faroutCoefficient > getOutlierCoefficient()) {
184                this.faroutCoefficient = faroutCoefficient;
185            }
186            else {
187                throw new IllegalArgumentException("Farout value must be greater "
188                    + "than the outlier value, which is currently set at: ("
189                    + getOutlierCoefficient() + ")");
190            }
191        }
192    
193        /**
194         * Returns the number of series in the dataset.
195         * <p>
196         * This implementation only allows one series.
197         *
198         * @return The number of series.
199         */
200        public int getSeriesCount() {
201            return 1;
202        }
203    
204        /**
205         * Returns the number of items in the specified series.
206         *
207         * @param series  the index (zero-based) of the series.
208         *
209         * @return The number of items in the specified series.
210         */
211        public int getItemCount(int series) {
212            return this.dates.size();
213        }
214    
215        /**
216         * Adds an item to the dataset and sends a {@link DatasetChangeEvent} to
217         * all registered listeners.
218         *
219         * @param date  the date (<code>null</code> not permitted).
220         * @param item  the item (<code>null</code> not permitted).
221         */
222        public void add(Date date, BoxAndWhiskerItem item) {
223            this.dates.add(date);
224            this.items.add(item);
225            if (this.minimumRangeValue == null) {
226                this.minimumRangeValue = item.getMinRegularValue();
227            }
228            else {
229                if (item.getMinRegularValue().doubleValue()
230                        < this.minimumRangeValue.doubleValue()) {
231                    this.minimumRangeValue = item.getMinRegularValue();
232                }
233            }
234            if (this.maximumRangeValue == null) {
235                this.maximumRangeValue = item.getMaxRegularValue();
236            }
237            else {
238                if (item.getMaxRegularValue().doubleValue()
239                        > this.maximumRangeValue.doubleValue()) {
240                    this.maximumRangeValue = item.getMaxRegularValue();
241                }
242            }
243            this.rangeBounds = new Range(this.minimumRangeValue.doubleValue(),
244                    this.maximumRangeValue.doubleValue());
245            fireDatasetChanged();
246        }
247    
248        /**
249         * Returns the name of the series stored in this dataset.
250         *
251         * @param i  the index of the series. Currently ignored.
252         *
253         * @return The name of this series.
254         */
255        public Comparable getSeriesKey(int i) {
256            return this.seriesKey;
257        }
258    
259        /**
260         * Return an item from within the dataset.
261         *
262         * @param series  the series index (ignored, since this dataset contains
263         *                only one series).
264         * @param item  the item within the series (zero-based index)
265         *
266         * @return The item.
267         */
268        public BoxAndWhiskerItem getItem(int series, int item) {
269            return (BoxAndWhiskerItem) this.items.get(item);
270        }
271    
272        /**
273         * Returns the x-value for one item in a series.
274         * <p>
275         * The value returned is a Long object generated from the underlying Date
276         * object.
277         *
278         * @param series  the series (zero-based index).
279         * @param item  the item (zero-based index).
280         *
281         * @return The x-value.
282         */
283        public Number getX(int series, int item) {
284            return new Long(((Date) this.dates.get(item)).getTime());
285        }
286    
287        /**
288         * Returns the x-value for one item in a series, as a Date.
289         * <p>
290         * This method is provided for convenience only.
291         *
292         * @param series  the series (zero-based index).
293         * @param item  the item (zero-based index).
294         *
295         * @return The x-value as a Date.
296         */
297        public Date getXDate(int series, int item) {
298            return (Date) this.dates.get(item);
299        }
300    
301        /**
302         * Returns the y-value for one item in a series.
303         * <p>
304         * This method (from the XYDataset interface) is mapped to the
305         * getMeanValue() method.
306         *
307         * @param series  the series (zero-based index).
308         * @param item  the item (zero-based index).
309         *
310         * @return The y-value.
311         */
312        public Number getY(int series, int item) {
313            return getMeanValue(series, item);
314        }
315    
316        /**
317         * Returns the mean for the specified series and item.
318         *
319         * @param series  the series (zero-based index).
320         * @param item  the item (zero-based index).
321         *
322         * @return The mean for the specified series and item.
323         */
324        public Number getMeanValue(int series, int item) {
325            Number result = null;
326            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
327            if (stats != null) {
328                result = stats.getMean();
329            }
330            return result;
331        }
332    
333        /**
334         * Returns the median-value for the specified series and item.
335         *
336         * @param series  the series (zero-based index).
337         * @param item  the item (zero-based index).
338         *
339         * @return The median-value for the specified series and item.
340         */
341        public Number getMedianValue(int series, int item) {
342            Number result = null;
343            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
344            if (stats != null) {
345                result = stats.getMedian();
346            }
347            return result;
348        }
349    
350        /**
351         * Returns the Q1 median-value for the specified series and item.
352         *
353         * @param series  the series (zero-based index).
354         * @param item  the item (zero-based index).
355         *
356         * @return The Q1 median-value for the specified series and item.
357         */
358        public Number getQ1Value(int series, int item) {
359            Number result = null;
360            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
361            if (stats != null) {
362                result = stats.getQ1();
363            }
364            return result;
365        }
366    
367        /**
368         * Returns the Q3 median-value for the specified series and item.
369         *
370         * @param series  the series (zero-based index).
371         * @param item  the item (zero-based index).
372         *
373         * @return The Q3 median-value for the specified series and item.
374         */
375        public Number getQ3Value(int series, int item) {
376            Number result = null;
377            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
378            if (stats != null) {
379                result = stats.getQ3();
380            }
381            return result;
382        }
383    
384        /**
385         * Returns the min-value for the specified series and item.
386         *
387         * @param series  the series (zero-based index).
388         * @param item  the item (zero-based index).
389         *
390         * @return The min-value for the specified series and item.
391         */
392        public Number getMinRegularValue(int series, int item) {
393            Number result = null;
394            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
395            if (stats != null) {
396                result = stats.getMinRegularValue();
397            }
398            return result;
399        }
400    
401        /**
402         * Returns the max-value for the specified series and item.
403         *
404         * @param series  the series (zero-based index).
405         * @param item  the item (zero-based index).
406         *
407         * @return The max-value for the specified series and item.
408         */
409        public Number getMaxRegularValue(int series, int item) {
410            Number result = null;
411            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
412            if (stats != null) {
413                result = stats.getMaxRegularValue();
414            }
415            return result;
416        }
417    
418        /**
419         * Returns the minimum value which is not a farout.
420         * @param series  the series (zero-based index).
421         * @param item  the item (zero-based index).
422         *
423         * @return A <code>Number</code> representing the maximum non-farout value.
424         */
425        public Number getMinOutlier(int series, int item) {
426            Number result = null;
427            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
428            if (stats != null) {
429                result = stats.getMinOutlier();
430            }
431            return result;
432        }
433    
434        /**
435         * Returns the maximum value which is not a farout, ie Q3 + (interquartile
436         * range * farout coefficient).
437         *
438         * @param series  the series (zero-based index).
439         * @param item  the item (zero-based index).
440         *
441         * @return A <code>Number</code> representing the maximum non-farout value.
442         */
443        public Number getMaxOutlier(int series, int item) {
444            Number result = null;
445            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
446            if (stats != null) {
447                result = stats.getMaxOutlier();
448            }
449            return result;
450        }
451    
452        /**
453         * Returns an array of outliers for the specified series and item.
454         *
455         * @param series  the series (zero-based index).
456         * @param item  the item (zero-based index).
457         *
458         * @return The array of outliers for the specified series and item.
459         */
460        public List getOutliers(int series, int item) {
461            List result = null;
462            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
463            if (stats != null) {
464                result = stats.getOutliers();
465            }
466            return result;
467        }
468    
469        /**
470         * Returns the minimum y-value in the dataset.
471         *
472         * @param includeInterval  a flag that determines whether or not the
473         *                         y-interval is taken into account.
474         *
475         * @return The minimum value.
476         */
477        public double getRangeLowerBound(boolean includeInterval) {
478            double result = Double.NaN;
479            if (this.minimumRangeValue != null) {
480                result = this.minimumRangeValue.doubleValue();
481            }
482            return result;
483        }
484    
485        /**
486         * Returns the maximum y-value in the dataset.
487         *
488         * @param includeInterval  a flag that determines whether or not the
489         *                         y-interval is taken into account.
490         *
491         * @return The maximum value.
492         */
493        public double getRangeUpperBound(boolean includeInterval) {
494            double result = Double.NaN;
495            if (this.maximumRangeValue != null) {
496                result = this.maximumRangeValue.doubleValue();
497            }
498            return result;
499        }
500    
501        /**
502         * Returns the range of the values in this dataset's range.
503         *
504         * @param includeInterval  a flag that determines whether or not the
505         *                         y-interval is taken into account.
506         *
507         * @return The range.
508         */
509        public Range getRangeBounds(boolean includeInterval) {
510            return this.rangeBounds;
511        }
512    
513        /**
514         * Tests this dataset for equality with an arbitrary object.
515         *
516         * @param obj  the object (<code>null</code> permitted).
517         *
518         * @return A boolean.
519         */
520        public boolean equals(Object obj) {
521            if (obj == this) {
522                return true;
523            }
524            if (!(obj instanceof DefaultBoxAndWhiskerXYDataset)) {
525                return false;
526            }
527            DefaultBoxAndWhiskerXYDataset that
528                    = (DefaultBoxAndWhiskerXYDataset) obj;
529            if (!ObjectUtilities.equal(this.seriesKey, that.seriesKey)) {
530                return false;
531            }
532            if (!this.dates.equals(that.dates)) {
533                return false;
534            }
535            if (!this.items.equals(that.items)) {
536                return false;
537            }
538            return true;
539        }
540    
541        /**
542         * Returns a clone of the plot.
543         *
544         * @return A clone.
545         *
546         * @throws CloneNotSupportedException  if the cloning is not supported.
547         */
548        public Object clone() throws CloneNotSupportedException {
549            DefaultBoxAndWhiskerXYDataset clone
550                    = (DefaultBoxAndWhiskerXYDataset) super.clone();
551            clone.dates = new java.util.ArrayList(this.dates);
552            clone.items = new java.util.ArrayList(this.items);
553            return clone;
554        }
555    
556    }