Frames | No Frames |
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: }