001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * HistogramDataset.java 029 * --------------------- 030 * (C) Copyright 2003-2005, by Jelai Wang and Contributors. 031 * 032 * Original Author: Jelai Wang (jelaiw AT mindspring.com); 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Cameron Hayne; 035 * Rikard Bj?rklind; 036 * 037 * $Id: HistogramDataset.java,v 1.9.2.4 2005/11/22 11:06:47 mungady Exp $ 038 * 039 * Changes 040 * ------- 041 * 06-Jul-2003 : Version 1, contributed by Jelai Wang (DG); 042 * 07-Jul-2003 : Changed package and added Javadocs (DG); 043 * 15-Oct-2003 : Updated Javadocs and removed array sorting (JW); 044 * 09-Jan-2004 : Added fix by "Z." posted in the JFreeChart forum (DG); 045 * 01-Mar-2004 : Added equals() and clone() methods and implemented 046 * Serializable. Also added new addSeries() method (DG); 047 * 06-May-2004 : Now extends AbstractIntervalXYDataset (DG); 048 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 049 * getYValue() (DG); 050 * 20-May-2005 : Speed up binning - see patch 1026151 contributed by Cameron 051 * Hayne (DG); 052 * 08-Jun-2005 : Fixed bug in getSeriesKey() method (DG); 053 * 22-Nov-2005 : Fixed cast in getSeriesKey() method - see patch 1329287 (DG); 054 * 055 */ 056 057 package org.jfree.data.statistics; 058 059 import java.io.Serializable; 060 import java.util.ArrayList; 061 import java.util.HashMap; 062 import java.util.List; 063 import java.util.Map; 064 065 import org.jfree.data.general.DatasetChangeEvent; 066 import org.jfree.data.xy.AbstractIntervalXYDataset; 067 import org.jfree.data.xy.IntervalXYDataset; 068 import org.jfree.util.ObjectUtilities; 069 import org.jfree.util.PublicCloneable; 070 071 /** 072 * A dataset that can be used for creating histograms. 073 * 074 * @see SimpleHistogramDataset 075 */ 076 public class HistogramDataset extends AbstractIntervalXYDataset 077 implements IntervalXYDataset, 078 Cloneable, PublicCloneable, 079 Serializable { 080 081 /** For serialization. */ 082 private static final long serialVersionUID = -6341668077370231153L; 083 084 /** A list of maps. */ 085 private List list; 086 087 /** The histogram type. */ 088 private HistogramType type; 089 090 /** 091 * Creates a new (empty) dataset with a default type of 092 * {@link HistogramType}.FREQUENCY. 093 */ 094 public HistogramDataset() { 095 this.list = new ArrayList(); 096 this.type = HistogramType.FREQUENCY; 097 } 098 099 /** 100 * Returns the histogram type. 101 * 102 * @return The type (never <code>null</code>). 103 */ 104 public HistogramType getType() { 105 return this.type; 106 } 107 108 /** 109 * Sets the histogram type and sends a {@link DatasetChangeEvent} to all 110 * registered listeners. 111 * 112 * @param type the type (<code>null</code> not permitted). 113 */ 114 public void setType(HistogramType type) { 115 if (type == null) { 116 throw new IllegalArgumentException("Null 'type' argument"); 117 } 118 this.type = type; 119 notifyListeners(new DatasetChangeEvent(this, this)); 120 } 121 122 /** 123 * Adds a series to the dataset, using the specified number of bins. 124 * 125 * @param key the series key (<code>null</code> not permitted). 126 * @param values the values (<code>null</code> not permitted). 127 * @param bins the number of bins (must be at least 1). 128 */ 129 public void addSeries(Comparable key, double[] values, int bins) { 130 // defer argument checking... 131 double minimum = getMinimum(values); 132 double maximum = getMaximum(values); 133 addSeries(key, values, bins, minimum, maximum); 134 } 135 136 /** 137 * Adds a series to the dataset. Any data value falling on a bin boundary 138 * will be assigned to the lower value bin, with the exception of the lower 139 * bound of the bin range which is always assigned to the first bin. 140 * 141 * @param key the series key (<code>null</code> not permitted). 142 * @param values the raw observations. 143 * @param bins the number of bins. 144 * @param minimum the lower bound of the bin range. 145 * @param maximum the upper bound of the bin range. 146 */ 147 public void addSeries(Comparable key, 148 double[] values, 149 int bins, 150 double minimum, 151 double maximum) { 152 153 if (key == null) { 154 throw new IllegalArgumentException("Null 'key' argument."); 155 } 156 if (values == null) { 157 throw new IllegalArgumentException("Null 'values' argument."); 158 } 159 else if (bins < 1) { 160 throw new IllegalArgumentException( 161 "The 'bins' value must be at least 1." 162 ); 163 } 164 double binWidth = (maximum - minimum) / bins; 165 166 double tmp = minimum; 167 List binList = new ArrayList(bins); 168 for (int i = 0; i < bins; i++) { 169 HistogramBin bin; 170 // make sure bins[bins.length]'s upper boundary ends at maximum 171 // to avoid the rounding issue. the bins[0] lower boundary is 172 // guaranteed start from min 173 if (i == bins - 1) { 174 bin = new HistogramBin(tmp, maximum); 175 } 176 else { 177 bin = new HistogramBin(tmp, tmp + binWidth); 178 } 179 tmp = tmp + binWidth; 180 binList.add(bin); 181 } 182 // fill the bins 183 for (int i = 0; i < values.length; i++) { 184 int binIndex = bins - 1; 185 if (values[i] < maximum) { 186 double fraction = (values[i] - minimum) / (maximum - minimum); 187 if (fraction < 0.0) { 188 fraction = 0.0; 189 } 190 binIndex = (int) (fraction * bins); 191 } 192 HistogramBin bin = (HistogramBin) binList.get(binIndex); 193 bin.incrementCount(); 194 } 195 // generic map for each series 196 Map map = new HashMap(); 197 map.put("key", key); 198 map.put("bins", binList); 199 map.put("values.length", new Integer(values.length)); 200 map.put("bin width", new Double(binWidth)); 201 this.list.add(map); 202 } 203 204 /** 205 * Returns the minimum value in an array of values. 206 * 207 * @param values the values (<code>null</code> not permitted and 208 * zero-length array not permitted). 209 * 210 * @return The minimum value. 211 */ 212 private double getMinimum(double[] values) { 213 if (values == null || values.length < 1) { 214 throw new IllegalArgumentException( 215 "Null or zero length 'values' argument." 216 ); 217 } 218 double min = Double.MAX_VALUE; 219 for (int i = 0; i < values.length; i++) { 220 if (values[i] < min) { 221 min = values[i]; 222 } 223 } 224 return min; 225 } 226 227 /** 228 * Returns the maximum value in an array of values. 229 * 230 * @param values the values (<code>null</code> not permitted and 231 * zero-length array not permitted). 232 * 233 * @return The maximum value. 234 */ 235 private double getMaximum(double[] values) { 236 if (values == null || values.length < 1) { 237 throw new IllegalArgumentException( 238 "Null or zero length 'values' argument." 239 ); 240 } 241 double max = -Double.MAX_VALUE; 242 for (int i = 0; i < values.length; i++) { 243 if (values[i] > max) { 244 max = values[i]; 245 } 246 } 247 return max; 248 } 249 250 /** 251 * Returns the bins for a series. 252 * 253 * @param series the series index. 254 * 255 * @return An array of bins. 256 */ 257 List getBins(int series) { 258 Map map = (Map) this.list.get(series); 259 return (List) map.get("bins"); 260 } 261 262 /** 263 * Returns the total number of observations for a series. 264 * 265 * @param series the series index. 266 * 267 * @return The total. 268 */ 269 private int getTotal(int series) { 270 Map map = (Map) this.list.get(series); 271 return ((Integer) map.get("values.length")).intValue(); 272 } 273 274 /** 275 * Returns the bin width for a series. 276 * 277 * @param series the series index (zero based). 278 * 279 * @return The bin width. 280 */ 281 private double getBinWidth(int series) { 282 Map map = (Map) this.list.get(series); 283 return ((Double) map.get("bin width")).doubleValue(); 284 } 285 286 /** 287 * Returns the number of series in the dataset. 288 * 289 * @return The series count. 290 */ 291 public int getSeriesCount() { 292 return this.list.size(); 293 } 294 295 /** 296 * Returns the key for a series. 297 * 298 * @param series the series index (zero based). 299 * 300 * @return The series key. 301 */ 302 public Comparable getSeriesKey(int series) { 303 Map map = (Map) this.list.get(series); 304 return (Comparable) map.get("key"); 305 } 306 307 /** 308 * Returns the number of data items for a series. 309 * 310 * @param series the series index (zero based). 311 * 312 * @return The item count. 313 */ 314 public int getItemCount(int series) { 315 return getBins(series).size(); 316 } 317 318 /** 319 * Returns the X value for a bin. This value won't be used for plotting 320 * histograms, since the renderer will ignore it. But other renderers can 321 * use it (for example, you could use the dataset to create a line 322 * chart). 323 * 324 * @param series the series index (zero based). 325 * @param item the item index (zero based). 326 * 327 * @return The start value. 328 */ 329 public Number getX(int series, int item) { 330 List bins = getBins(series); 331 HistogramBin bin = (HistogramBin) bins.get(item); 332 double x = (bin.getStartBoundary() + bin.getEndBoundary()) / 2.; 333 return new Double(x); 334 } 335 336 /** 337 * Returns the y-value for a bin (calculated to take into account the 338 * histogram type). 339 * 340 * @param series the series index (zero based). 341 * @param item the item index (zero based). 342 * 343 * @return The y-value. 344 */ 345 public Number getY(int series, int item) { 346 List bins = getBins(series); 347 HistogramBin bin = (HistogramBin) bins.get(item); 348 double total = getTotal(series); 349 double binWidth = getBinWidth(series); 350 351 if (this.type == HistogramType.FREQUENCY) { 352 return new Double(bin.getCount()); 353 } 354 else if (this.type == HistogramType.RELATIVE_FREQUENCY) { 355 return new Double(bin.getCount() / total); 356 } 357 else if (this.type == HistogramType.SCALE_AREA_TO_1) { 358 return new Double(bin.getCount() / (binWidth * total)); 359 } 360 else { // pretty sure this shouldn't ever happen 361 throw new IllegalStateException(); 362 } 363 } 364 365 /** 366 * Returns the start value for a bin. 367 * 368 * @param series the series index (zero based). 369 * @param item the item index (zero based). 370 * 371 * @return The start value. 372 */ 373 public Number getStartX(int series, int item) { 374 List bins = getBins(series); 375 HistogramBin bin = (HistogramBin) bins.get(item); 376 return new Double(bin.getStartBoundary()); 377 } 378 379 /** 380 * Returns the end value for a bin. 381 * 382 * @param series the series index (zero based). 383 * @param item the item index (zero based). 384 * 385 * @return The end value. 386 */ 387 public Number getEndX(int series, int item) { 388 List bins = getBins(series); 389 HistogramBin bin = (HistogramBin) bins.get(item); 390 return new Double(bin.getEndBoundary()); 391 } 392 393 /** 394 * Returns the start y-value for a bin (which is the same as the y-value, 395 * this method exists only to support the general form of the 396 * {@link IntervalXYDataset} interface). 397 * 398 * @param series the series index (zero based). 399 * @param item the item index (zero based). 400 * 401 * @return The y-value. 402 */ 403 public Number getStartY(int series, int item) { 404 return getY(series, item); 405 } 406 407 /** 408 * Returns the end y-value for a bin (which is the same as the y-value, 409 * this method exists only to support the general form of the 410 * {@link IntervalXYDataset} interface). 411 * 412 * @param series the series index (zero based). 413 * @param item the item index (zero based). 414 * 415 * @return The Y value. 416 */ 417 public Number getEndY(int series, int item) { 418 return getY(series, item); 419 } 420 421 /** 422 * Tests this dataset for equality with an arbitrary object. 423 * 424 * @param obj the object to test against (<code>null</code> permitted). 425 * 426 * @return A boolean. 427 */ 428 public boolean equals(Object obj) { 429 if (obj == this) { 430 return true; 431 } 432 if (!(obj instanceof HistogramDataset)) { 433 return false; 434 } 435 HistogramDataset that = (HistogramDataset) obj; 436 if (!ObjectUtilities.equal(this.type, that.type)) { 437 return false; 438 } 439 if (!ObjectUtilities.equal(this.list, that.list)) { 440 return false; 441 } 442 return true; 443 } 444 445 /** 446 * Returns a clone of the dataset. 447 * 448 * @return A clone of the dataset. 449 * 450 * @throws CloneNotSupportedException if the object cannot be cloned. 451 */ 452 public Object clone() throws CloneNotSupportedException { 453 return super.clone(); 454 } 455 456 }