001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 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 061package org.jfree.data.statistics; 062 063import java.util.ArrayList; 064import java.util.Date; 065import java.util.List; 066 067import org.jfree.data.Range; 068import org.jfree.data.RangeInfo; 069import org.jfree.data.general.DatasetChangeEvent; 070import org.jfree.data.xy.AbstractXYDataset; 071import 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 */ 077public 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 @Override 144 public double getOutlierCoefficient() { 145 return this.outlierCoefficient; 146 } 147 148 /** 149 * Sets the value used as the outlier coefficient 150 * 151 * @param outlierCoefficient being a <code>double</code> representing the 152 * value used to calculate outliers. 153 * 154 * @see #getOutlierCoefficient() 155 */ 156 public void setOutlierCoefficient(double outlierCoefficient) { 157 this.outlierCoefficient = outlierCoefficient; 158 } 159 160 /** 161 * Returns the value used as the farout coefficient. The farout coefficient 162 * allows the calculation of which values will be off the graph. 163 * 164 * @return A <code>double</code> representing the value used to calculate 165 * farouts. 166 * 167 * @see #setFaroutCoefficient(double) 168 */ 169 @Override 170 public double getFaroutCoefficient() { 171 return this.faroutCoefficient; 172 } 173 174 /** 175 * Sets the value used as the farouts coefficient. The farout coefficient 176 * must b greater than the outlier coefficient. 177 * 178 * @param faroutCoefficient being a <code>double</code> representing the 179 * value used to calculate farouts. 180 * 181 * @see #getFaroutCoefficient() 182 */ 183 public void setFaroutCoefficient(double faroutCoefficient) { 184 185 if (faroutCoefficient > getOutlierCoefficient()) { 186 this.faroutCoefficient = faroutCoefficient; 187 } 188 else { 189 throw new IllegalArgumentException("Farout value must be greater " 190 + "than the outlier value, which is currently set at: (" 191 + getOutlierCoefficient() + ")"); 192 } 193 } 194 195 /** 196 * Returns the number of series in the dataset. 197 * <p> 198 * This implementation only allows one series. 199 * 200 * @return The number of series. 201 */ 202 @Override 203 public int getSeriesCount() { 204 return 1; 205 } 206 207 /** 208 * Returns the number of items in the specified series. 209 * 210 * @param series the index (zero-based) of the series. 211 * 212 * @return The number of items in the specified series. 213 */ 214 @Override 215 public int getItemCount(int series) { 216 return this.dates.size(); 217 } 218 219 /** 220 * Adds an item to the dataset and sends a {@link DatasetChangeEvent} to 221 * all registered listeners. 222 * 223 * @param date the date (<code>null</code> not permitted). 224 * @param item the item (<code>null</code> not permitted). 225 */ 226 public void add(Date date, BoxAndWhiskerItem item) { 227 this.dates.add(date); 228 this.items.add(item); 229 if (this.minimumRangeValue == null) { 230 this.minimumRangeValue = item.getMinRegularValue(); 231 } 232 else { 233 if (item.getMinRegularValue().doubleValue() 234 < this.minimumRangeValue.doubleValue()) { 235 this.minimumRangeValue = item.getMinRegularValue(); 236 } 237 } 238 if (this.maximumRangeValue == null) { 239 this.maximumRangeValue = item.getMaxRegularValue(); 240 } 241 else { 242 if (item.getMaxRegularValue().doubleValue() 243 > this.maximumRangeValue.doubleValue()) { 244 this.maximumRangeValue = item.getMaxRegularValue(); 245 } 246 } 247 this.rangeBounds = new Range(this.minimumRangeValue.doubleValue(), 248 this.maximumRangeValue.doubleValue()); 249 fireDatasetChanged(); 250 } 251 252 /** 253 * Returns the name of the series stored in this dataset. 254 * 255 * @param i the index of the series. Currently ignored. 256 * 257 * @return The name of this series. 258 */ 259 @Override 260 public Comparable getSeriesKey(int i) { 261 return this.seriesKey; 262 } 263 264 /** 265 * Return an item from within the dataset. 266 * 267 * @param series the series index (ignored, since this dataset contains 268 * only one series). 269 * @param item the item within the series (zero-based index) 270 * 271 * @return The item. 272 */ 273 public BoxAndWhiskerItem getItem(int series, int item) { 274 return (BoxAndWhiskerItem) this.items.get(item); 275 } 276 277 /** 278 * Returns the x-value for one item in a series. 279 * <p> 280 * The value returned is a Long object generated from the underlying Date 281 * object. 282 * 283 * @param series the series (zero-based index). 284 * @param item the item (zero-based index). 285 * 286 * @return The x-value. 287 */ 288 @Override 289 public Number getX(int series, int item) { 290 return new Long(((Date) this.dates.get(item)).getTime()); 291 } 292 293 /** 294 * Returns the x-value for one item in a series, as a Date. 295 * <p> 296 * This method is provided for convenience only. 297 * 298 * @param series the series (zero-based index). 299 * @param item the item (zero-based index). 300 * 301 * @return The x-value as a Date. 302 */ 303 public Date getXDate(int series, int item) { 304 return (Date) this.dates.get(item); 305 } 306 307 /** 308 * Returns the y-value for one item in a series. 309 * <p> 310 * This method (from the XYDataset interface) is mapped to the 311 * getMeanValue() method. 312 * 313 * @param series the series (zero-based index). 314 * @param item the item (zero-based index). 315 * 316 * @return The y-value. 317 */ 318 @Override 319 public Number getY(int series, int item) { 320 return getMeanValue(series, item); 321 } 322 323 /** 324 * Returns the mean for the specified series and item. 325 * 326 * @param series the series (zero-based index). 327 * @param item the item (zero-based index). 328 * 329 * @return The mean for the specified series and item. 330 */ 331 @Override 332 public Number getMeanValue(int series, int item) { 333 Number result = null; 334 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 335 if (stats != null) { 336 result = stats.getMean(); 337 } 338 return result; 339 } 340 341 /** 342 * Returns the median-value for the specified series and item. 343 * 344 * @param series the series (zero-based index). 345 * @param item the item (zero-based index). 346 * 347 * @return The median-value for the specified series and item. 348 */ 349 @Override 350 public Number getMedianValue(int series, int item) { 351 Number result = null; 352 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 353 if (stats != null) { 354 result = stats.getMedian(); 355 } 356 return result; 357 } 358 359 /** 360 * Returns the Q1 median-value for the specified series and item. 361 * 362 * @param series the series (zero-based index). 363 * @param item the item (zero-based index). 364 * 365 * @return The Q1 median-value for the specified series and item. 366 */ 367 @Override 368 public Number getQ1Value(int series, int item) { 369 Number result = null; 370 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 371 if (stats != null) { 372 result = stats.getQ1(); 373 } 374 return result; 375 } 376 377 /** 378 * Returns the Q3 median-value for the specified series and item. 379 * 380 * @param series the series (zero-based index). 381 * @param item the item (zero-based index). 382 * 383 * @return The Q3 median-value for the specified series and item. 384 */ 385 @Override 386 public Number getQ3Value(int series, int item) { 387 Number result = null; 388 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 389 if (stats != null) { 390 result = stats.getQ3(); 391 } 392 return result; 393 } 394 395 /** 396 * Returns the min-value for the specified series and item. 397 * 398 * @param series the series (zero-based index). 399 * @param item the item (zero-based index). 400 * 401 * @return The min-value for the specified series and item. 402 */ 403 @Override 404 public Number getMinRegularValue(int series, int item) { 405 Number result = null; 406 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 407 if (stats != null) { 408 result = stats.getMinRegularValue(); 409 } 410 return result; 411 } 412 413 /** 414 * Returns the max-value for the specified series and item. 415 * 416 * @param series the series (zero-based index). 417 * @param item the item (zero-based index). 418 * 419 * @return The max-value for the specified series and item. 420 */ 421 @Override 422 public Number getMaxRegularValue(int series, int item) { 423 Number result = null; 424 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 425 if (stats != null) { 426 result = stats.getMaxRegularValue(); 427 } 428 return result; 429 } 430 431 /** 432 * Returns the minimum value which is not a farout. 433 * @param series the series (zero-based index). 434 * @param item the item (zero-based index). 435 * 436 * @return A <code>Number</code> representing the maximum non-farout value. 437 */ 438 @Override 439 public Number getMinOutlier(int series, int item) { 440 Number result = null; 441 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 442 if (stats != null) { 443 result = stats.getMinOutlier(); 444 } 445 return result; 446 } 447 448 /** 449 * Returns the maximum value which is not a farout, ie Q3 + (interquartile 450 * range * farout coefficient). 451 * 452 * @param series the series (zero-based index). 453 * @param item the item (zero-based index). 454 * 455 * @return A <code>Number</code> representing the maximum non-farout value. 456 */ 457 @Override 458 public Number getMaxOutlier(int series, int item) { 459 Number result = null; 460 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 461 if (stats != null) { 462 result = stats.getMaxOutlier(); 463 } 464 return result; 465 } 466 467 /** 468 * Returns a list of outliers for the specified series and item. 469 * 470 * @param series the series (zero-based index). 471 * @param item the item (zero-based index). 472 * 473 * @return The list of outliers for the specified series and item 474 * (possibly <code>null</code>). 475 */ 476 @Override 477 public List getOutliers(int series, int item) { 478 List result = null; 479 BoxAndWhiskerItem stats = (BoxAndWhiskerItem) this.items.get(item); 480 if (stats != null) { 481 result = stats.getOutliers(); 482 } 483 return result; 484 } 485 486 /** 487 * Returns the minimum y-value in the dataset. 488 * 489 * @param includeInterval a flag that determines whether or not the 490 * y-interval is taken into account. 491 * 492 * @return The minimum value. 493 */ 494 @Override 495 public double getRangeLowerBound(boolean includeInterval) { 496 double result = Double.NaN; 497 if (this.minimumRangeValue != null) { 498 result = this.minimumRangeValue.doubleValue(); 499 } 500 return result; 501 } 502 503 /** 504 * Returns the maximum y-value in the dataset. 505 * 506 * @param includeInterval a flag that determines whether or not the 507 * y-interval is taken into account. 508 * 509 * @return The maximum value. 510 */ 511 @Override 512 public double getRangeUpperBound(boolean includeInterval) { 513 double result = Double.NaN; 514 if (this.maximumRangeValue != null) { 515 result = this.maximumRangeValue.doubleValue(); 516 } 517 return result; 518 } 519 520 /** 521 * Returns the range of the values in this dataset's range. 522 * 523 * @param includeInterval a flag that determines whether or not the 524 * y-interval is taken into account. 525 * 526 * @return The range. 527 */ 528 @Override 529 public Range getRangeBounds(boolean includeInterval) { 530 return this.rangeBounds; 531 } 532 533 /** 534 * Tests this dataset for equality with an arbitrary object. 535 * 536 * @param obj the object (<code>null</code> permitted). 537 * 538 * @return A boolean. 539 */ 540 @Override 541 public boolean equals(Object obj) { 542 if (obj == this) { 543 return true; 544 } 545 if (!(obj instanceof DefaultBoxAndWhiskerXYDataset)) { 546 return false; 547 } 548 DefaultBoxAndWhiskerXYDataset that 549 = (DefaultBoxAndWhiskerXYDataset) obj; 550 if (!ObjectUtilities.equal(this.seriesKey, that.seriesKey)) { 551 return false; 552 } 553 if (!this.dates.equals(that.dates)) { 554 return false; 555 } 556 if (!this.items.equals(that.items)) { 557 return false; 558 } 559 return true; 560 } 561 562 /** 563 * Returns a clone of the plot. 564 * 565 * @return A clone. 566 * 567 * @throws CloneNotSupportedException if the cloning is not supported. 568 */ 569 @Override 570 public Object clone() throws CloneNotSupportedException { 571 DefaultBoxAndWhiskerXYDataset clone 572 = (DefaultBoxAndWhiskerXYDataset) super.clone(); 573 clone.dates = new java.util.ArrayList(this.dates); 574 clone.items = new java.util.ArrayList(this.items); 575 return clone; 576 } 577 578}