001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * SimpleHistogramDataset.java 029 * --------------------------- 030 * (C) Copyright 2005-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Sergei Ivanov; 034 * 035 * Changes 036 * ------- 037 * 10-Jan-2005 : Version 1 (DG); 038 * 21-May-2007 : Added clearObservations() and removeAllBins() (SI); 039 * 10-Jul-2007 : Added null argument check to constructor (DG); 040 * 041 */ 042 043 package org.jfree.data.statistics; 044 045 import java.io.Serializable; 046 import java.util.ArrayList; 047 import java.util.Collections; 048 import java.util.Iterator; 049 import java.util.List; 050 051 import org.jfree.data.DomainOrder; 052 import org.jfree.data.general.DatasetChangeEvent; 053 import org.jfree.data.xy.AbstractIntervalXYDataset; 054 import org.jfree.data.xy.IntervalXYDataset; 055 import org.jfree.util.ObjectUtilities; 056 import org.jfree.util.PublicCloneable; 057 058 /** 059 * A dataset used for creating simple histograms with custom defined bins. 060 * 061 * @see HistogramDataset 062 */ 063 public class SimpleHistogramDataset extends AbstractIntervalXYDataset 064 implements IntervalXYDataset, Cloneable, PublicCloneable, 065 Serializable { 066 067 /** For serialization. */ 068 private static final long serialVersionUID = 7997996479768018443L; 069 070 /** The series key. */ 071 private Comparable key; 072 073 /** The bins. */ 074 private List bins; 075 076 /** 077 * A flag that controls whether or not the bin count is divided by the 078 * bin size. 079 */ 080 private boolean adjustForBinSize; 081 082 /** 083 * Creates a new histogram dataset. Note that the 084 * <code>adjustForBinSize</code> flag defaults to <code>true</code>. 085 * 086 * @param key the series key (<code>null</code> not permitted). 087 */ 088 public SimpleHistogramDataset(Comparable key) { 089 if (key == null) { 090 throw new IllegalArgumentException("Null 'key' argument."); 091 } 092 this.key = key; 093 this.bins = new ArrayList(); 094 this.adjustForBinSize = true; 095 } 096 097 /** 098 * Returns a flag that controls whether or not the bin count is divided by 099 * the bin size in the {@link #getXValue(int, int)} method. 100 * 101 * @return A boolean. 102 * 103 * @see #setAdjustForBinSize(boolean) 104 */ 105 public boolean getAdjustForBinSize() { 106 return this.adjustForBinSize; 107 } 108 109 /** 110 * Sets the flag that controls whether or not the bin count is divided by 111 * the bin size in the {@link #getYValue(int, int)} method, and sends a 112 * {@link DatasetChangeEvent} to all registered listeners. 113 * 114 * @param adjust the flag. 115 * 116 * @see #getAdjustForBinSize() 117 */ 118 public void setAdjustForBinSize(boolean adjust) { 119 this.adjustForBinSize = adjust; 120 notifyListeners(new DatasetChangeEvent(this, this)); 121 } 122 123 /** 124 * Returns the number of series in the dataset (always 1 for this dataset). 125 * 126 * @return The series count. 127 */ 128 public int getSeriesCount() { 129 return 1; 130 } 131 132 /** 133 * Returns the key for a series. Since this dataset only stores a single 134 * series, the <code>series</code> argument is ignored. 135 * 136 * @param series the series (zero-based index, ignored in this dataset). 137 * 138 * @return The key for the series. 139 */ 140 public Comparable getSeriesKey(int series) { 141 return this.key; 142 } 143 144 /** 145 * Returns the order of the domain (or X) values returned by the dataset. 146 * 147 * @return The order (never <code>null</code>). 148 */ 149 public DomainOrder getDomainOrder() { 150 return DomainOrder.ASCENDING; 151 } 152 153 /** 154 * Returns the number of items in a series. Since this dataset only stores 155 * a single series, the <code>series</code> argument is ignored. 156 * 157 * @param series the series index (zero-based, ignored in this dataset). 158 * 159 * @return The item count. 160 */ 161 public int getItemCount(int series) { 162 return this.bins.size(); 163 } 164 165 /** 166 * Adds a bin to the dataset. An exception is thrown if the bin overlaps 167 * with any existing bin in the dataset. 168 * 169 * @param bin the bin (<code>null</code> not permitted). 170 * 171 * @see #removeAllBins() 172 */ 173 public void addBin(SimpleHistogramBin bin) { 174 // check that the new bin doesn't overlap with any existing bin 175 Iterator iterator = this.bins.iterator(); 176 while (iterator.hasNext()) { 177 SimpleHistogramBin existingBin 178 = (SimpleHistogramBin) iterator.next(); 179 if (bin.overlapsWith(existingBin)) { 180 throw new RuntimeException("Overlapping bin"); 181 } 182 } 183 this.bins.add(bin); 184 Collections.sort(this.bins); 185 } 186 187 /** 188 * Adds an observation to the dataset (by incrementing the item count for 189 * the appropriate bin). A runtime exception is thrown if the value does 190 * not fit into any bin. 191 * 192 * @param value the value. 193 */ 194 public void addObservation(double value) { 195 addObservation(value, true); 196 } 197 198 /** 199 * Adds an observation to the dataset (by incrementing the item count for 200 * the appropriate bin). A runtime exception is thrown if the value does 201 * not fit into any bin. 202 * 203 * @param value the value. 204 * @param notify send {@link DatasetChangeEvent} to listeners? 205 */ 206 public void addObservation(double value, boolean notify) { 207 boolean placed = false; 208 Iterator iterator = this.bins.iterator(); 209 while (iterator.hasNext() && !placed) { 210 SimpleHistogramBin bin = (SimpleHistogramBin) iterator.next(); 211 if (bin.accepts(value)) { 212 bin.setItemCount(bin.getItemCount() + 1); 213 placed = true; 214 } 215 } 216 if (!placed) { 217 throw new RuntimeException("No bin."); 218 } 219 if (notify) { 220 notifyListeners(new DatasetChangeEvent(this, this)); 221 } 222 } 223 224 /** 225 * Adds a set of values to the dataset and sends a 226 * {@link DatasetChangeEvent} to all registered listeners. 227 * 228 * @param values the values (<code>null</code> not permitted). 229 * 230 * @see #clearObservations() 231 */ 232 public void addObservations(double[] values) { 233 for (int i = 0; i < values.length; i++) { 234 addObservation(values[i], false); 235 } 236 notifyListeners(new DatasetChangeEvent(this, this)); 237 } 238 239 /** 240 * Removes all current observation data and sends a 241 * {@link DatasetChangeEvent} to all registered listeners. 242 * 243 * @since 1.0.6 244 * 245 * @see #addObservations(double[]) 246 * @see #removeAllBins() 247 */ 248 public void clearObservations() { 249 Iterator iterator = this.bins.iterator(); 250 while (iterator.hasNext()) { 251 SimpleHistogramBin bin = (SimpleHistogramBin) iterator.next(); 252 bin.setItemCount(0); 253 } 254 notifyListeners(new DatasetChangeEvent(this, this)); 255 } 256 257 /** 258 * Removes all bins and sends a {@link DatasetChangeEvent} to all 259 * registered listeners. 260 * 261 * @since 1.0.6 262 * 263 * @see #addBin(SimpleHistogramBin) 264 */ 265 public void removeAllBins() { 266 this.bins = new ArrayList(); 267 notifyListeners(new DatasetChangeEvent(this, this)); 268 } 269 270 /** 271 * Returns the x-value for an item within a series. The x-values may or 272 * may not be returned in ascending order, that is up to the class 273 * implementing the interface. 274 * 275 * @param series the series index (zero-based). 276 * @param item the item index (zero-based). 277 * 278 * @return The x-value (never <code>null</code>). 279 */ 280 public Number getX(int series, int item) { 281 return new Double(getXValue(series, item)); 282 } 283 284 /** 285 * Returns the x-value (as a double primitive) for an item within a series. 286 * 287 * @param series the series index (zero-based). 288 * @param item the item index (zero-based). 289 * 290 * @return The x-value. 291 */ 292 public double getXValue(int series, int item) { 293 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 294 return (bin.getLowerBound() + bin.getUpperBound()) / 2.0; 295 } 296 297 /** 298 * Returns the y-value for an item within a series. 299 * 300 * @param series the series index (zero-based). 301 * @param item the item index (zero-based). 302 * 303 * @return The y-value (possibly <code>null</code>). 304 */ 305 public Number getY(int series, int item) { 306 return new Double(getYValue(series, item)); 307 } 308 309 /** 310 * Returns the y-value (as a double primitive) for an item within a series. 311 * 312 * @param series the series index (zero-based). 313 * @param item the item index (zero-based). 314 * 315 * @return The y-value. 316 * 317 * @see #getAdjustForBinSize() 318 */ 319 public double getYValue(int series, int item) { 320 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 321 if (this.adjustForBinSize) { 322 return bin.getItemCount() 323 / (bin.getUpperBound() - bin.getLowerBound()); 324 } 325 else { 326 return bin.getItemCount(); 327 } 328 } 329 330 /** 331 * Returns the starting X value for the specified series and item. 332 * 333 * @param series the series index (zero-based). 334 * @param item the item index (zero-based). 335 * 336 * @return The value. 337 */ 338 public Number getStartX(int series, int item) { 339 return new Double(getStartXValue(series, item)); 340 } 341 342 /** 343 * Returns the start x-value (as a double primitive) for an item within a 344 * series. 345 * 346 * @param series the series (zero-based index). 347 * @param item the item (zero-based index). 348 * 349 * @return The start x-value. 350 */ 351 public double getStartXValue(int series, int item) { 352 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 353 return bin.getLowerBound(); 354 } 355 356 /** 357 * Returns the ending X value for the specified series and item. 358 * 359 * @param series the series index (zero-based). 360 * @param item the item index (zero-based). 361 * 362 * @return The value. 363 */ 364 public Number getEndX(int series, int item) { 365 return new Double(getEndXValue(series, item)); 366 } 367 368 /** 369 * Returns the end x-value (as a double primitive) for an item within a 370 * series. 371 * 372 * @param series the series index (zero-based). 373 * @param item the item index (zero-based). 374 * 375 * @return The end x-value. 376 */ 377 public double getEndXValue(int series, int item) { 378 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 379 return bin.getUpperBound(); 380 } 381 382 /** 383 * Returns the starting Y value for the specified series and item. 384 * 385 * @param series the series index (zero-based). 386 * @param item the item index (zero-based). 387 * 388 * @return The value. 389 */ 390 public Number getStartY(int series, int item) { 391 return getY(series, item); 392 } 393 394 /** 395 * Returns the start y-value (as a double primitive) for an item within a 396 * series. 397 * 398 * @param series the series index (zero-based). 399 * @param item the item index (zero-based). 400 * 401 * @return The start y-value. 402 */ 403 public double getStartYValue(int series, int item) { 404 return getYValue(series, item); 405 } 406 407 /** 408 * Returns the ending Y value for the specified series and item. 409 * 410 * @param series the series index (zero-based). 411 * @param item the item index (zero-based). 412 * 413 * @return The value. 414 */ 415 public Number getEndY(int series, int item) { 416 return getY(series, item); 417 } 418 419 /** 420 * Returns the end y-value (as a double primitive) for an item within a 421 * series. 422 * 423 * @param series the series index (zero-based). 424 * @param item the item index (zero-based). 425 * 426 * @return The end y-value. 427 */ 428 public double getEndYValue(int series, int item) { 429 return getYValue(series, item); 430 } 431 432 /** 433 * Compares the dataset for equality with an arbitrary object. 434 * 435 * @param obj the object (<code>null</code> permitted). 436 * 437 * @return A boolean. 438 */ 439 public boolean equals(Object obj) { 440 if (obj == this) { 441 return true; 442 } 443 if (!(obj instanceof SimpleHistogramDataset)) { 444 return false; 445 } 446 SimpleHistogramDataset that = (SimpleHistogramDataset) obj; 447 if (!this.key.equals(that.key)) { 448 return false; 449 } 450 if (this.adjustForBinSize != that.adjustForBinSize) { 451 return false; 452 } 453 if (!this.bins.equals(that.bins)) { 454 return false; 455 } 456 return true; 457 } 458 459 /** 460 * Returns a clone of the dataset. 461 * 462 * @return A clone. 463 * 464 * @throws CloneNotSupportedException not thrown by this class, but maybe 465 * by subclasses (if any). 466 */ 467 public Object clone() throws CloneNotSupportedException { 468 SimpleHistogramDataset clone = (SimpleHistogramDataset) super.clone(); 469 clone.bins = (List) ObjectUtilities.deepClone(this.bins); 470 return clone; 471 } 472 473 }