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