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     * DefaultBoxAndWhiskerXYDataset.java
029     * ----------------------------------
030     * (C) Copyright 2003-2007, 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     * $Id: DefaultBoxAndWhiskerXYDataset.java,v 1.10.2.2 2007/02/02 15:50:24 mungady Exp $
037     *
038     * Changes
039     * -------
040     * 05-Aug-2003 : Version 1, contributed by David Browning (DG);
041     * 08-Aug-2003 : Minor changes to comments (DB)
042     *               Allow average to be null  - average is a perculiar AIMS 
043     *               requirement which probably should be stripped out and overlaid
044     *               if required...
045     *               Added a number of methods to allow the max and min non-outlier
046     *               and non-farout values to be calculated
047     * 12-Aug-2003   Changed the getYValue to return the highest outlier value
048     *               Added getters and setters for outlier and farout coefficients
049     * 27-Aug-2003 : Renamed DefaultBoxAndWhiskerDataset 
050     *               --> DefaultBoxAndWhiskerXYDataset (DG);
051     * 06-May-2004 : Now extends AbstractXYDataset (DG);
052     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
053     *               getYValue() (DG);
054     * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
055     * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
056     *               release (DG);
057     * ------------- JFREECHART 1.0.x ---------------------------------------------
058     * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
059     *
060     */
061    
062    package org.jfree.data.statistics;
063    
064    import java.util.ArrayList;
065    import java.util.Date;
066    import java.util.List;
067    
068    import org.jfree.data.Range;
069    import org.jfree.data.RangeInfo;
070    import org.jfree.data.xy.AbstractXYDataset;
071    
072    /**
073     * A simple implementation of the {@link BoxAndWhiskerXYDataset}.  The dataset
074     * can hold only one series.
075     */
076    public class DefaultBoxAndWhiskerXYDataset extends AbstractXYDataset 
077                                               implements BoxAndWhiskerXYDataset,
078                                                          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         * Adds an item to the dataset.
133         * 
134         * @param date  the date.
135         * @param item  the item.
136         */
137        public void add(Date date, BoxAndWhiskerItem item) {
138            this.dates.add(date);
139            this.items.add(item);
140            if (this.minimumRangeValue == null) {
141                this.minimumRangeValue = item.getMinRegularValue();
142            }
143            else {
144                if (item.getMinRegularValue().doubleValue() 
145                        < this.minimumRangeValue.doubleValue()) {
146                    this.minimumRangeValue = item.getMinRegularValue();
147                }
148            }
149            if (this.maximumRangeValue == null) {
150                this.maximumRangeValue = item.getMaxRegularValue();
151            }
152            else {
153                if (item.getMaxRegularValue().doubleValue() 
154                        > this.maximumRangeValue.doubleValue()) {
155                    this.maximumRangeValue = item.getMaxRegularValue();
156                }
157            }
158            this.rangeBounds = new Range(
159                this.minimumRangeValue.doubleValue(), 
160                this.maximumRangeValue.doubleValue()
161            );
162        }
163        
164        /**
165         * Returns the name of the series stored in this dataset.
166         *
167         * @param i  the index of the series. Currently ignored.
168         * 
169         * @return The name of this series.
170         */
171        public Comparable getSeriesKey(int i) {
172            return this.seriesKey;
173        }
174        
175        /**
176         * Return an item from within the dataset.
177         * 
178         * @param series  the series index (ignored, since this dataset contains
179         *                only one series).
180         * @param item  the item within the series (zero-based index)
181         * 
182         * @return The item.
183         */
184        public BoxAndWhiskerItem getItem(int series, int item) {
185            return (BoxAndWhiskerItem) this.items.get(item);  
186        }
187    
188        /**
189         * Returns the x-value for one item in a series.
190         * <p>
191         * The value returned is a Long object generated from the underlying Date 
192         * object.
193         *
194         * @param series  the series (zero-based index).
195         * @param item  the item (zero-based index).
196         *
197         * @return The x-value.
198         */
199        public Number getX(int series, int item) {
200            return new Long(((Date) this.dates.get(item)).getTime());
201        }
202    
203        /**
204         * Returns the x-value for one item in a series, as a Date.
205         * <p>
206         * This method is provided for convenience only.
207         *
208         * @param series  the series (zero-based index).
209         * @param item  the item (zero-based index).
210         *
211         * @return The x-value as a Date.
212         */
213        public Date getXDate(int series, int item) {
214            return (Date) this.dates.get(item);
215        }
216    
217        /**
218         * Returns the y-value for one item in a series.
219         * <p>
220         * This method (from the XYDataset interface) is mapped to the 
221         * getMaxNonOutlierValue() method.
222         *
223         * @param series  the series (zero-based index).
224         * @param item  the item (zero-based index).
225         *
226         * @return The y-value.
227         */
228        public Number getY(int series, int item) {
229            return new Double(getMeanValue(series, item).doubleValue());  
230        }
231    
232        /**
233         * Returns the mean for the specified series and item.
234         *
235         * @param series  the series (zero-based index).
236         * @param item  the item (zero-based index).
237         *
238         * @return The mean for the specified series and item.
239         */
240        public Number getMeanValue(int series, int item) {
241            Number result = null;
242            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
243            if (stats != null) {
244                result = stats.getMean();
245            }
246            return result;
247        }
248    
249        /**
250         * Returns the median-value for the specified series and item.
251         *
252         * @param series  the series (zero-based index).
253         * @param item  the item (zero-based index).
254         *
255         * @return The median-value for the specified series and item.
256         */
257        public Number getMedianValue(int series, int item) {
258            Number result = null;
259            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
260            if (stats != null) {
261                result = stats.getMedian();
262            }
263            return result;
264        }
265    
266        /**
267         * Returns the Q1 median-value for the specified series and item.
268         *
269         * @param series  the series (zero-based index).
270         * @param item  the item (zero-based index).
271         *
272         * @return The Q1 median-value for the specified series and item.
273         */
274        public Number getQ1Value(int series, int item) {
275            Number result = null;
276            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
277            if (stats != null) {
278                result = stats.getQ1();
279            }
280            return result;
281        }
282    
283        /**
284         * Returns the Q3 median-value for the specified series and item.
285         *
286         * @param series  the series (zero-based index).
287         * @param item  the item (zero-based index).
288         *
289         * @return The Q3 median-value for the specified series and item.
290         */
291        public Number getQ3Value(int series, int item) {
292            Number result = null;
293            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
294            if (stats != null) {
295                result = stats.getQ3();
296            }
297            return result;
298        }
299    
300        /**
301         * Returns the min-value for the specified series and item.
302         *
303         * @param series  the series (zero-based index).
304         * @param item  the item (zero-based index).
305         *
306         * @return The min-value for the specified series and item.
307         */
308        public Number getMinRegularValue(int series, int item) {
309            Number result = null;
310            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
311            if (stats != null) {
312                result = stats.getMinRegularValue();
313            }
314            return result;
315        }
316    
317        /**
318         * Returns the max-value for the specified series and item.
319         *
320         * @param series  the series (zero-based index).
321         * @param item  the item (zero-based index).
322         *
323         * @return The max-value for the specified series and item.
324         */
325        public Number getMaxRegularValue(int series, int item) {
326            Number result = null;
327            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
328            if (stats != null) {
329                result = stats.getMaxRegularValue();
330            }
331            return result;
332        }
333    
334        /**
335         * Returns the minimum value which is not a farout.
336         * @param series  the series (zero-based index).
337         * @param item  the item (zero-based index).
338         *
339         * @return A <code>Number</code> representing the maximum non-farout value.
340         */
341        public Number getMinOutlier(int series, int item) {
342            Number result = null;
343            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
344            if (stats != null) {
345                result = stats.getMinOutlier();
346            }
347            return result;
348        }
349     
350        /**
351         * Returns the maximum value which is not a farout, ie Q3 + (interquartile 
352         * range * farout coefficient).
353         * 
354         * @param series  the series (zero-based index).
355         * @param item  the item (zero-based index).
356         *
357         * @return A <code>Number</code> representing the maximum non-farout value.
358         */
359        public Number getMaxOutlier(int series, int item) {
360            Number result = null;
361            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
362            if (stats != null) {
363                result = stats.getMaxOutlier();
364            }
365            return result;
366        }
367    
368        /**
369         * Returns an array of outliers for the specified series and item.
370         *
371         * @param series  the series (zero-based index).
372         * @param item  the item (zero-based index).
373         *
374         * @return The array of outliers for the specified series and item.
375         */
376        public List getOutliers(int series, int item) {
377            List result = null;
378            BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item);
379            if (stats != null) {
380                result = stats.getOutliers();
381            }
382            return result;
383        }
384    
385        /**
386         * Returns the value used as the outlier coefficient. The outlier 
387         * coefficient gives an indication of the degree of certainty in an 
388         * unskewed distribution.  Increasing the coefficient increases the number 
389         * of values included. Currently only used to ensure farout coefficient is 
390         * greater than the outlier coefficient
391         *
392         * @return A <code>double</code> representing the value used to calculate 
393         *         outliers.
394         */
395        public double getOutlierCoefficient() {
396            return this.outlierCoefficient;
397        }
398    
399        /**
400         * Returns the value used as the farout coefficient. The farout coefficient
401         * allows the calculation of which values will be off the graph.
402         *
403         * @return A <code>double</code> representing the value used to calculate 
404         *         farouts.
405         */
406        public double getFaroutCoefficient() {
407            return this.faroutCoefficient;
408        }
409    
410        /**
411         * Returns the number of series in the dataset.
412         * <p>
413         * This implementation only allows one series.
414         *
415         * @return The number of series.
416         */
417        public int getSeriesCount() {
418            return 1;
419        }
420    
421        /**
422         * Returns the number of items in the specified series.
423         *
424         * @param series  the index (zero-based) of the series.
425         *
426         * @return The number of items in the specified series.
427         */
428        public int getItemCount(int series) {
429            return this.dates.size();
430        }
431    
432        /**
433         * Sets the value used as the outlier coefficient
434         *
435         * @param outlierCoefficient  being a <code>double</code> representing the 
436         *                            value used to calculate outliers.
437         */
438        public void setOutlierCoefficient(double outlierCoefficient) {
439            this.outlierCoefficient = outlierCoefficient;
440        }
441    
442        /**
443         * Sets the value used as the farouts coefficient. The farout coefficient 
444         * must b greater than the outlier coefficient.
445         * 
446         * @param faroutCoefficient being a <code>double</code> representing the 
447         *                          value used to calculate farouts.
448         */
449        public void setFaroutCoefficient(double faroutCoefficient) {
450    
451            if (faroutCoefficient > getOutlierCoefficient()) {
452                this.faroutCoefficient = faroutCoefficient;
453            } 
454            else {
455                throw new IllegalArgumentException("Farout value must be greater " 
456                    + "than the outlier value, which is currently set at: (" 
457                    + getOutlierCoefficient() + ")");
458            }
459        }
460    
461        /**
462         * Returns the minimum y-value in the dataset.
463         *
464         * @param includeInterval  a flag that determines whether or not the
465         *                         y-interval is taken into account.
466         * 
467         * @return The minimum value.
468         */
469        public double getRangeLowerBound(boolean includeInterval) {
470            double result = Double.NaN;
471            if (this.minimumRangeValue != null) {
472                result = this.minimumRangeValue.doubleValue();
473            }
474            return result;        
475        }
476    
477        /**
478         * Returns the maximum y-value in the dataset.
479         *
480         * @param includeInterval  a flag that determines whether or not the
481         *                         y-interval is taken into account.
482         * 
483         * @return The maximum value.
484         */
485        public double getRangeUpperBound(boolean includeInterval) {
486            double result = Double.NaN;
487            if (this.maximumRangeValue != null) {
488                result = this.maximumRangeValue.doubleValue();
489            }
490            return result;        
491        }
492    
493        /**
494         * Returns the range of the values in this dataset's range.
495         *
496         * @param includeInterval  a flag that determines whether or not the
497         *                         y-interval is taken into account.
498         * 
499         * @return The range.
500         */
501        public Range getRangeBounds(boolean includeInterval) {
502            return this.rangeBounds;
503        }
504    
505    }