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 }