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