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