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 }