001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2021, 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-2021, 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.ObjectUtils; 052import org.jfree.chart.util.Args; 053import org.jfree.chart.util.PublicCloneable; 054 055import org.jfree.data.DomainOrder; 056import org.jfree.data.general.DatasetChangeEvent; 057import org.jfree.data.xy.AbstractIntervalXYDataset; 058import org.jfree.data.xy.IntervalXYDataset; 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} flag defaults to {@code true}. 087 * 088 * @param key the series key ({@code null} not permitted). 089 */ 090 public SimpleHistogramDataset(Comparable key) { 091 Args.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} 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}). 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} 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} 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} 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 * @see #addObservations(double[]) 248 * @see #removeAllBins() 249 */ 250 public void clearObservations() { 251 Iterator iterator = this.bins.iterator(); 252 while (iterator.hasNext()) { 253 SimpleHistogramBin bin = (SimpleHistogramBin) iterator.next(); 254 bin.setItemCount(0); 255 } 256 notifyListeners(new DatasetChangeEvent(this, this)); 257 } 258 259 /** 260 * Removes all bins and sends a {@link DatasetChangeEvent} to all 261 * registered listeners. 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}). 279 */ 280 @Override 281 public Number getX(int series, int item) { 282 return getXValue(series, item); 283 } 284 285 /** 286 * Returns the x-value (as a double primitive) for an item within a series. 287 * 288 * @param series the series index (zero-based). 289 * @param item the item index (zero-based). 290 * 291 * @return The x-value. 292 */ 293 @Override 294 public double getXValue(int series, int item) { 295 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 296 return (bin.getLowerBound() + bin.getUpperBound()) / 2.0; 297 } 298 299 /** 300 * Returns the y-value for an item within a series. 301 * 302 * @param series the series index (zero-based). 303 * @param item the item index (zero-based). 304 * 305 * @return The y-value (possibly {@code null}). 306 */ 307 @Override 308 public Number getY(int series, int item) { 309 return getYValue(series, item); 310 } 311 312 /** 313 * Returns the y-value (as a double primitive) for an item within a series. 314 * 315 * @param series the series index (zero-based). 316 * @param item the item index (zero-based). 317 * 318 * @return The y-value. 319 * 320 * @see #getAdjustForBinSize() 321 */ 322 @Override 323 public double getYValue(int series, int item) { 324 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 325 if (this.adjustForBinSize) { 326 return bin.getItemCount() 327 / (bin.getUpperBound() - bin.getLowerBound()); 328 } 329 else { 330 return bin.getItemCount(); 331 } 332 } 333 334 /** 335 * Returns the starting X value for the specified series and item. 336 * 337 * @param series the series index (zero-based). 338 * @param item the item index (zero-based). 339 * 340 * @return The value. 341 */ 342 @Override 343 public Number getStartX(int series, int item) { 344 return getStartXValue(series, item); 345 } 346 347 /** 348 * Returns the start x-value (as a double primitive) for an item within a 349 * series. 350 * 351 * @param series the series (zero-based index). 352 * @param item the item (zero-based index). 353 * 354 * @return The start x-value. 355 */ 356 @Override 357 public double getStartXValue(int series, int item) { 358 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 359 return bin.getLowerBound(); 360 } 361 362 /** 363 * Returns the ending X value for the specified series and item. 364 * 365 * @param series the series index (zero-based). 366 * @param item the item index (zero-based). 367 * 368 * @return The value. 369 */ 370 @Override 371 public Number getEndX(int series, int item) { 372 return getEndXValue(series, item); 373 } 374 375 /** 376 * Returns the end x-value (as a double primitive) for an item within a 377 * series. 378 * 379 * @param series the series index (zero-based). 380 * @param item the item index (zero-based). 381 * 382 * @return The end x-value. 383 */ 384 @Override 385 public double getEndXValue(int series, int item) { 386 SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item); 387 return bin.getUpperBound(); 388 } 389 390 /** 391 * Returns the starting Y value for the specified series and item. 392 * 393 * @param series the series index (zero-based). 394 * @param item the item index (zero-based). 395 * 396 * @return The value. 397 */ 398 @Override 399 public Number getStartY(int series, int item) { 400 return getY(series, item); 401 } 402 403 /** 404 * Returns the start y-value (as a double primitive) for an item within a 405 * series. 406 * 407 * @param series the series index (zero-based). 408 * @param item the item index (zero-based). 409 * 410 * @return The start y-value. 411 */ 412 @Override 413 public double getStartYValue(int series, int item) { 414 return getYValue(series, item); 415 } 416 417 /** 418 * Returns the ending Y value for the specified series and item. 419 * 420 * @param series the series index (zero-based). 421 * @param item the item index (zero-based). 422 * 423 * @return The value. 424 */ 425 @Override 426 public Number getEndY(int series, int item) { 427 return getY(series, item); 428 } 429 430 /** 431 * Returns the end y-value (as a double primitive) for an item within a 432 * series. 433 * 434 * @param series the series index (zero-based). 435 * @param item the item index (zero-based). 436 * 437 * @return The end y-value. 438 */ 439 @Override 440 public double getEndYValue(int series, int item) { 441 return getYValue(series, item); 442 } 443 444 /** 445 * Compares the dataset for equality with an arbitrary object. 446 * 447 * @param obj the object ({@code null} permitted). 448 * 449 * @return A boolean. 450 */ 451 @Override 452 public boolean equals(Object obj) { 453 if (obj == this) { 454 return true; 455 } 456 if (!(obj instanceof SimpleHistogramDataset)) { 457 return false; 458 } 459 SimpleHistogramDataset that = (SimpleHistogramDataset) obj; 460 if (!this.key.equals(that.key)) { 461 return false; 462 } 463 if (this.adjustForBinSize != that.adjustForBinSize) { 464 return false; 465 } 466 if (!this.bins.equals(that.bins)) { 467 return false; 468 } 469 return true; 470 } 471 472 /** 473 * Returns a clone of the dataset. 474 * 475 * @return A clone. 476 * 477 * @throws CloneNotSupportedException not thrown by this class, but maybe 478 * by subclasses (if any). 479 */ 480 @Override 481 public Object clone() throws CloneNotSupportedException { 482 SimpleHistogramDataset clone = (SimpleHistogramDataset) super.clone(); 483 clone.bins = (List) ObjectUtils.deepClone(this.bins); 484 return clone; 485 } 486 487}