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