Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, 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: * HistogramDataset.java 29: * --------------------- 30: * (C) Copyright 2003-2006, by Jelai Wang and Contributors. 31: * 32: * Original Author: Jelai Wang (jelaiw AT mindspring.com); 33: * Contributor(s): David Gilbert (for Object Refinery Limited); 34: * Cameron Hayne; 35: * Rikard Bj?rklind; 36: * 37: * $Id: HistogramDataset.java,v 1.9.2.7 2006/09/07 15:26:49 mungady Exp $ 38: * 39: * Changes 40: * ------- 41: * 06-Jul-2003 : Version 1, contributed by Jelai Wang (DG); 42: * 07-Jul-2003 : Changed package and added Javadocs (DG); 43: * 15-Oct-2003 : Updated Javadocs and removed array sorting (JW); 44: * 09-Jan-2004 : Added fix by "Z." posted in the JFreeChart forum (DG); 45: * 01-Mar-2004 : Added equals() and clone() methods and implemented 46: * Serializable. Also added new addSeries() method (DG); 47: * 06-May-2004 : Now extends AbstractIntervalXYDataset (DG); 48: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 49: * getYValue() (DG); 50: * 20-May-2005 : Speed up binning - see patch 1026151 contributed by Cameron 51: * Hayne (DG); 52: * 08-Jun-2005 : Fixed bug in getSeriesKey() method (DG); 53: * 22-Nov-2005 : Fixed cast in getSeriesKey() method - see patch 1329287 (DG); 54: * ------------- JFREECHART 1.0.0 --------------------------------------------- 55: * 03-Aug-2006 : Improved precision of bin boundary calculation (DG); 56: * 07-Sep-2006 : Fixed bug 1553088 (DG); 57: * 58: */ 59: 60: package org.jfree.data.statistics; 61: 62: import java.io.Serializable; 63: import java.util.ArrayList; 64: import java.util.HashMap; 65: import java.util.List; 66: import java.util.Map; 67: 68: import org.jfree.data.general.DatasetChangeEvent; 69: import org.jfree.data.xy.AbstractIntervalXYDataset; 70: import org.jfree.data.xy.IntervalXYDataset; 71: import org.jfree.util.ObjectUtilities; 72: import org.jfree.util.PublicCloneable; 73: 74: /** 75: * A dataset that can be used for creating histograms. 76: * 77: * @see SimpleHistogramDataset 78: */ 79: public class HistogramDataset extends AbstractIntervalXYDataset 80: implements IntervalXYDataset, 81: Cloneable, PublicCloneable, 82: Serializable { 83: 84: /** For serialization. */ 85: private static final long serialVersionUID = -6341668077370231153L; 86: 87: /** A list of maps. */ 88: private List list; 89: 90: /** The histogram type. */ 91: private HistogramType type; 92: 93: /** 94: * Creates a new (empty) dataset with a default type of 95: * {@link HistogramType}.FREQUENCY. 96: */ 97: public HistogramDataset() { 98: this.list = new ArrayList(); 99: this.type = HistogramType.FREQUENCY; 100: } 101: 102: /** 103: * Returns the histogram type. 104: * 105: * @return The type (never <code>null</code>). 106: */ 107: public HistogramType getType() { 108: return this.type; 109: } 110: 111: /** 112: * Sets the histogram type and sends a {@link DatasetChangeEvent} to all 113: * registered listeners. 114: * 115: * @param type the type (<code>null</code> not permitted). 116: */ 117: public void setType(HistogramType type) { 118: if (type == null) { 119: throw new IllegalArgumentException("Null 'type' argument"); 120: } 121: this.type = type; 122: notifyListeners(new DatasetChangeEvent(this, this)); 123: } 124: 125: /** 126: * Adds a series to the dataset, using the specified number of bins. 127: * 128: * @param key the series key (<code>null</code> not permitted). 129: * @param values the values (<code>null</code> not permitted). 130: * @param bins the number of bins (must be at least 1). 131: */ 132: public void addSeries(Comparable key, double[] values, int bins) { 133: // defer argument checking... 134: double minimum = getMinimum(values); 135: double maximum = getMaximum(values); 136: addSeries(key, values, bins, minimum, maximum); 137: } 138: 139: /** 140: * Adds a series to the dataset. Any data value less than minimum will be 141: * assigned to the first bin, and any data value greater than maximum will 142: * be assigned to the last bin. Values falling on the boundary of 143: * adjacent bins will be assigned to the higher indexed bin. 144: * 145: * @param key the series key (<code>null</code> not permitted). 146: * @param values the raw observations. 147: * @param bins the number of bins (must be at least 1). 148: * @param minimum the lower bound of the bin range. 149: * @param maximum the upper bound of the bin range. 150: */ 151: public void addSeries(Comparable key, 152: double[] values, 153: int bins, 154: double minimum, 155: double maximum) { 156: 157: if (key == null) { 158: throw new IllegalArgumentException("Null 'key' argument."); 159: } 160: if (values == null) { 161: throw new IllegalArgumentException("Null 'values' argument."); 162: } 163: else if (bins < 1) { 164: throw new IllegalArgumentException( 165: "The 'bins' value must be at least 1."); 166: } 167: double binWidth = (maximum - minimum) / bins; 168: 169: double lower = minimum; 170: double upper; 171: List binList = new ArrayList(bins); 172: for (int i = 0; i < bins; i++) { 173: HistogramBin bin; 174: // make sure bins[bins.length]'s upper boundary ends at maximum 175: // to avoid the rounding issue. the bins[0] lower boundary is 176: // guaranteed start from min 177: if (i == bins - 1) { 178: bin = new HistogramBin(lower, maximum); 179: } 180: else { 181: upper = minimum + (i + 1) * binWidth; 182: bin = new HistogramBin(lower, upper); 183: lower = upper; 184: } 185: binList.add(bin); 186: } 187: // fill the bins 188: for (int i = 0; i < values.length; i++) { 189: int binIndex = bins - 1; 190: if (values[i] < maximum) { 191: double fraction = (values[i] - minimum) / (maximum - minimum); 192: if (fraction < 0.0) { 193: fraction = 0.0; 194: } 195: binIndex = (int) (fraction * bins); 196: // rounding could result in binIndex being equal to bins 197: // which will cause an IndexOutOfBoundsException - see bug 198: // report 1553088 199: if (binIndex >= bins) { 200: binIndex = bins - 1; 201: } 202: } 203: HistogramBin bin = (HistogramBin) binList.get(binIndex); 204: bin.incrementCount(); 205: } 206: // generic map for each series 207: Map map = new HashMap(); 208: map.put("key", key); 209: map.put("bins", binList); 210: map.put("values.length", new Integer(values.length)); 211: map.put("bin width", new Double(binWidth)); 212: this.list.add(map); 213: } 214: 215: /** 216: * Returns the minimum value in an array of values. 217: * 218: * @param values the values (<code>null</code> not permitted and 219: * zero-length array not permitted). 220: * 221: * @return The minimum value. 222: */ 223: private double getMinimum(double[] values) { 224: if (values == null || values.length < 1) { 225: throw new IllegalArgumentException( 226: "Null or zero length 'values' argument."); 227: } 228: double min = Double.MAX_VALUE; 229: for (int i = 0; i < values.length; i++) { 230: if (values[i] < min) { 231: min = values[i]; 232: } 233: } 234: return min; 235: } 236: 237: /** 238: * Returns the maximum value in an array of values. 239: * 240: * @param values the values (<code>null</code> not permitted and 241: * zero-length array not permitted). 242: * 243: * @return The maximum value. 244: */ 245: private double getMaximum(double[] values) { 246: if (values == null || values.length < 1) { 247: throw new IllegalArgumentException( 248: "Null or zero length 'values' argument."); 249: } 250: double max = -Double.MAX_VALUE; 251: for (int i = 0; i < values.length; i++) { 252: if (values[i] > max) { 253: max = values[i]; 254: } 255: } 256: return max; 257: } 258: 259: /** 260: * Returns the bins for a series. 261: * 262: * @param series the series index (in the range <code>0</code> to 263: * <code>getSeriesCount() - 1</code>). 264: * 265: * @return A list of bins. 266: * 267: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 268: * specified range. 269: */ 270: List getBins(int series) { 271: Map map = (Map) this.list.get(series); 272: return (List) map.get("bins"); 273: } 274: 275: /** 276: * Returns the total number of observations for a series. 277: * 278: * @param series the series index. 279: * 280: * @return The total. 281: */ 282: private int getTotal(int series) { 283: Map map = (Map) this.list.get(series); 284: return ((Integer) map.get("values.length")).intValue(); 285: } 286: 287: /** 288: * Returns the bin width for a series. 289: * 290: * @param series the series index (zero based). 291: * 292: * @return The bin width. 293: */ 294: private double getBinWidth(int series) { 295: Map map = (Map) this.list.get(series); 296: return ((Double) map.get("bin width")).doubleValue(); 297: } 298: 299: /** 300: * Returns the number of series in the dataset. 301: * 302: * @return The series count. 303: */ 304: public int getSeriesCount() { 305: return this.list.size(); 306: } 307: 308: /** 309: * Returns the key for a series. 310: * 311: * @param series the series index (in the range <code>0</code> to 312: * <code>getSeriesCount() - 1</code>). 313: * 314: * @return The series key. 315: * 316: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 317: * specified range. 318: */ 319: public Comparable getSeriesKey(int series) { 320: Map map = (Map) this.list.get(series); 321: return (Comparable) map.get("key"); 322: } 323: 324: /** 325: * Returns the number of data items for a series. 326: * 327: * @param series the series index (in the range <code>0</code> to 328: * <code>getSeriesCount() - 1</code>). 329: * 330: * @return The item count. 331: * 332: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 333: * specified range. 334: */ 335: public int getItemCount(int series) { 336: return getBins(series).size(); 337: } 338: 339: /** 340: * Returns the X value for a bin. This value won't be used for plotting 341: * histograms, since the renderer will ignore it. But other renderers can 342: * use it (for example, you could use the dataset to create a line 343: * chart). 344: * 345: * @param series the series index (in the range <code>0</code> to 346: * <code>getSeriesCount() - 1</code>). 347: * @param item the item index (zero based). 348: * 349: * @return The start value. 350: * 351: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 352: * specified range. 353: */ 354: public Number getX(int series, int item) { 355: List bins = getBins(series); 356: HistogramBin bin = (HistogramBin) bins.get(item); 357: double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2.; 358: return new Double(x); 359: } 360: 361: /** 362: * Returns the y-value for a bin (calculated to take into account the 363: * histogram type). 364: * 365: * @param series the series index (in the range <code>0</code> to 366: * <code>getSeriesCount() - 1</code>). 367: * @param item the item index (zero based). 368: * 369: * @return The y-value. 370: * 371: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 372: * specified range. 373: */ 374: public Number getY(int series, int item) { 375: List bins = getBins(series); 376: HistogramBin bin = (HistogramBin) bins.get(item); 377: double total = getTotal(series); 378: double binWidth = getBinWidth(series); 379: 380: if (this.type == HistogramType.FREQUENCY) { 381: return new Double(bin.getCount()); 382: } 383: else if (this.type == HistogramType.RELATIVE_FREQUENCY) { 384: return new Double(bin.getCount() / total); 385: } 386: else if (this.type == HistogramType.SCALE_AREA_TO_1) { 387: return new Double(bin.getCount() / (binWidth * total)); 388: } 389: else { // pretty sure this shouldn't ever happen 390: throw new IllegalStateException(); 391: } 392: } 393: 394: /** 395: * Returns the start value for a bin. 396: * 397: * @param series the series index (in the range <code>0</code> to 398: * <code>getSeriesCount() - 1</code>). 399: * @param item the item index (zero based). 400: * 401: * @return The start value. 402: * 403: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 404: * specified range. 405: */ 406: public Number getStartX(int series, int item) { 407: List bins = getBins(series); 408: HistogramBin bin = (HistogramBin) bins.get(item); 409: return new Double(bin.getStartBoundary()); 410: } 411: 412: /** 413: * Returns the end value for a bin. 414: * 415: * @param series the series index (in the range <code>0</code> to 416: * <code>getSeriesCount() - 1</code>). 417: * @param item the item index (zero based). 418: * 419: * @return The end value. 420: * 421: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 422: * specified range. 423: */ 424: public Number getEndX(int series, int item) { 425: List bins = getBins(series); 426: HistogramBin bin = (HistogramBin) bins.get(item); 427: return new Double(bin.getEndBoundary()); 428: } 429: 430: /** 431: * Returns the start y-value for a bin (which is the same as the y-value, 432: * this method exists only to support the general form of the 433: * {@link IntervalXYDataset} interface). 434: * 435: * @param series the series index (in the range <code>0</code> to 436: * <code>getSeriesCount() - 1</code>). 437: * @param item the item index (zero based). 438: * 439: * @return The y-value. 440: * 441: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 442: * specified range. 443: */ 444: public Number getStartY(int series, int item) { 445: return getY(series, item); 446: } 447: 448: /** 449: * Returns the end y-value for a bin (which is the same as the y-value, 450: * this method exists only to support the general form of the 451: * {@link IntervalXYDataset} interface). 452: * 453: * @param series the series index (in the range <code>0</code> to 454: * <code>getSeriesCount() - 1</code>). 455: * @param item the item index (zero based). 456: * 457: * @return The Y value. 458: * 459: * @throws IndexOutOfBoundsException if <code>series</code> is outside the 460: * specified range. 461: */ 462: public Number getEndY(int series, int item) { 463: return getY(series, item); 464: } 465: 466: /** 467: * Tests this dataset for equality with an arbitrary object. 468: * 469: * @param obj the object to test against (<code>null</code> permitted). 470: * 471: * @return A boolean. 472: */ 473: public boolean equals(Object obj) { 474: if (obj == this) { 475: return true; 476: } 477: if (!(obj instanceof HistogramDataset)) { 478: return false; 479: } 480: HistogramDataset that = (HistogramDataset) obj; 481: if (!ObjectUtilities.equal(this.type, that.type)) { 482: return false; 483: } 484: if (!ObjectUtilities.equal(this.list, that.list)) { 485: return false; 486: } 487: return true; 488: } 489: 490: /** 491: * Returns a clone of the dataset. 492: * 493: * @return A clone of the dataset. 494: * 495: * @throws CloneNotSupportedException if the object cannot be cloned. 496: */ 497: public Object clone() throws CloneNotSupportedException { 498: return super.clone(); 499: } 500: 501: }