Source for org.jfree.data.statistics.DefaultBoxAndWhiskerXYDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------------------------------
  28:  * DefaultBoxAndWhiskerXYDataset.java
  29:  * ----------------------------------
  30:  * (C) Copyright 2003-2007, by David Browning and Contributors.
  31:  *
  32:  * Original Author:  David Browning (for Australian Institute of Marine 
  33:  *                   Science);
  34:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  35:  *
  36:  * Changes
  37:  * -------
  38:  * 05-Aug-2003 : Version 1, contributed by David Browning (DG);
  39:  * 08-Aug-2003 : Minor changes to comments (DB)
  40:  *               Allow average to be null  - average is a perculiar AIMS 
  41:  *               requirement which probably should be stripped out and overlaid
  42:  *               if required...
  43:  *               Added a number of methods to allow the max and min non-outlier
  44:  *               and non-farout values to be calculated
  45:  * 12-Aug-2003   Changed the getYValue to return the highest outlier value
  46:  *               Added getters and setters for outlier and farout coefficients
  47:  * 27-Aug-2003 : Renamed DefaultBoxAndWhiskerDataset 
  48:  *               --> DefaultBoxAndWhiskerXYDataset (DG);
  49:  * 06-May-2004 : Now extends AbstractXYDataset (DG);
  50:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  51:  *               getYValue() (DG);
  52:  * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
  53:  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
  54:  *               release (DG);
  55:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  56:  * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
  57:  * 12-Nov-2007 : Implemented equals() and clone() (DG);
  58:  *
  59:  */
  60: 
  61: package org.jfree.data.statistics;
  62: 
  63: import java.util.ArrayList;
  64: import java.util.Date;
  65: import java.util.List;
  66: 
  67: import org.jfree.data.Range;
  68: import org.jfree.data.RangeInfo;
  69: import org.jfree.data.general.DatasetChangeEvent;
  70: import org.jfree.data.xy.AbstractXYDataset;
  71: import org.jfree.util.ObjectUtilities;
  72: 
  73: /**
  74:  * A simple implementation of the {@link BoxAndWhiskerXYDataset} interface.  
  75:  * This dataset implementation can hold only one series.
  76:  */
  77: public class DefaultBoxAndWhiskerXYDataset extends AbstractXYDataset 
  78:             implements BoxAndWhiskerXYDataset, RangeInfo {
  79: 
  80:     /** The series key. */
  81:     private Comparable seriesKey;
  82: 
  83:     /** Storage for the dates. */
  84:     private List dates;
  85: 
  86:     /** Storage for the box and whisker statistics. */
  87:     private List items;
  88: 
  89:     /** The minimum range value. */
  90:     private Number minimumRangeValue;
  91: 
  92:     /** The maximum range value. */
  93:     private Number maximumRangeValue;
  94: 
  95:     /** The range of values. */
  96:     private Range rangeBounds;
  97: 
  98:     /** 
  99:      * 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: }