001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, 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-2022, 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 java.util.Objects; 045import org.jfree.chart.internal.Args; 046import org.jfree.chart.internal.CloneUtils; 047import org.jfree.chart.api.PublicCloneable; 048 049import org.jfree.data.DomainOrder; 050import org.jfree.data.general.DatasetChangeEvent; 051import org.jfree.data.xy.AbstractIntervalXYDataset; 052import org.jfree.data.xy.IntervalXYDataset; 053 054/** 055 * A dataset used for creating simple histograms with custom defined bins. 056 * 057 * @see HistogramDataset 058 */ 059public class SimpleHistogramDataset<K extends Comparable<K>> 060 extends AbstractIntervalXYDataset implements IntervalXYDataset, 061 Cloneable, PublicCloneable, Serializable { 062 063 /** For serialization. */ 064 private static final long serialVersionUID = 7997996479768018443L; 065 066 /** The series key. */ 067 private K key; 068 069 /** The bins. */ 070 private List<SimpleHistogramBin> bins; 071 072 /** 073 * A flag that controls whether or not the bin count is divided by the 074 * bin size. 075 */ 076 private boolean adjustForBinSize; 077 078 /** 079 * Creates a new histogram dataset. Note that the 080 * {@code adjustForBinSize} flag defaults to {@code true}. 081 * 082 * @param key the series key ({@code null} not permitted). 083 */ 084 public SimpleHistogramDataset(K key) { 085 Args.nullNotPermitted(key, "key"); 086 this.key = key; 087 this.bins = new ArrayList<>(); 088 this.adjustForBinSize = true; 089 } 090 091 /** 092 * Returns a flag that controls whether or not the bin count is divided by 093 * the bin size in the {@link #getXValue(int, int)} method. 094 * 095 * @return A boolean. 096 * 097 * @see #setAdjustForBinSize(boolean) 098 */ 099 public boolean getAdjustForBinSize() { 100 return this.adjustForBinSize; 101 } 102 103 /** 104 * Sets the flag that controls whether or not the bin count is divided by 105 * the bin size in the {@link #getYValue(int, int)} method, and sends a 106 * {@link DatasetChangeEvent} to all registered listeners. 107 * 108 * @param adjust the flag. 109 * 110 * @see #getAdjustForBinSize() 111 */ 112 public void setAdjustForBinSize(boolean adjust) { 113 this.adjustForBinSize = adjust; 114 notifyListeners(new DatasetChangeEvent(this, this)); 115 } 116 117 /** 118 * Returns the number of series in the dataset (always 1 for this dataset). 119 * 120 * @return The series count. 121 */ 122 @Override 123 public int getSeriesCount() { 124 return 1; 125 } 126 127 /** 128 * Returns the key for a series. Since this dataset only stores a single 129 * series, the {@code series} argument is ignored. 130 * 131 * @param series the series (zero-based index, ignored in this dataset). 132 * 133 * @return The key for the series. 134 */ 135 @Override 136 public K getSeriesKey(int series) { 137 return this.key; 138 } 139 140 /** 141 * Returns the order of the domain (or X) values returned by the dataset. 142 * 143 * @return The order (never {@code null}). 144 */ 145 @Override 146 public DomainOrder getDomainOrder() { 147 return DomainOrder.ASCENDING; 148 } 149 150 /** 151 * Returns the number of items in a series. Since this dataset only stores 152 * a single series, the {@code series} argument is ignored. 153 * 154 * @param series the series index (zero-based, ignored in this dataset). 155 * 156 * @return The item count. 157 */ 158 @Override 159 public int getItemCount(int series) { 160 return this.bins.size(); 161 } 162 163 /** 164 * Adds a bin to the dataset. An exception is thrown if the bin overlaps 165 * with any existing bin in the dataset. 166 * 167 * @param binToAdd the bin ({@code null} not permitted). 168 * 169 * @see #removeAllBins() 170 */ 171 public void addBin(SimpleHistogramBin binToAdd) { 172 // check that the new bin doesn't overlap with any existing bin 173 for (SimpleHistogramBin bin : this.bins) { 174 if (binToAdd.overlapsWith(bin)) { 175 throw new RuntimeException("Overlapping bin"); 176 } 177 } 178 this.bins.add(binToAdd); 179 Collections.sort(this.bins); 180 } 181 182 /** 183 * Adds an observation to the dataset (by incrementing the item count for 184 * the appropriate bin). A runtime exception is thrown if the value does 185 * not fit into any bin. 186 * 187 * @param value the value. 188 */ 189 public void addObservation(double value) { 190 addObservation(value, true); 191 } 192 193 /** 194 * Adds an observation to the dataset (by incrementing the item count for 195 * the appropriate bin). A runtime exception is thrown if the value does 196 * not fit into any bin. 197 * 198 * @param value the value. 199 * @param notify send {@link DatasetChangeEvent} to listeners? 200 */ 201 public void addObservation(double value, boolean notify) { 202 boolean placed = false; 203 Iterator iterator = this.bins.iterator(); 204 while (iterator.hasNext() && !placed) { 205 SimpleHistogramBin bin = (SimpleHistogramBin) iterator.next(); 206 if (bin.accepts(value)) { 207 bin.setItemCount(bin.getItemCount() + 1); 208 placed = true; 209 } 210 } 211 if (!placed) { 212 throw new RuntimeException("No bin."); 213 } 214 if (notify) { 215 notifyListeners(new DatasetChangeEvent(this, this)); 216 } 217 } 218 219 /** 220 * Adds a set of values to the dataset and sends a 221 * {@link DatasetChangeEvent} to all registered listeners. 222 * 223 * @param values the values ({@code null} not permitted). 224 * 225 * @see #clearObservations() 226 */ 227 public void addObservations(double[] values) { 228 for (int i = 0; i < values.length; i++) { 229 addObservation(values[i], false); 230 } 231 notifyListeners(new DatasetChangeEvent(this, this)); 232 } 233 234 /** 235 * Removes all current observation data and sends a 236 * {@link DatasetChangeEvent} to all registered listeners. 237 * 238 * @since 1.0.6 239 * 240 * @see #addObservations(double[]) 241 * @see #removeAllBins() 242 */ 243 public void clearObservations() { 244 for (SimpleHistogramBin bin : this.bins) { 245 bin.setItemCount(0); 246 } 247 notifyListeners(new DatasetChangeEvent(this, this)); 248 } 249 250 /** 251 * Removes all bins and sends a {@link DatasetChangeEvent} to all 252 * registered listeners. 253 * 254 * @since 1.0.6 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 = 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 = 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 = 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 = 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 @Override 466 public int hashCode(){ 467 int hash = 7; 468 hash = 11 * hash + Objects.hashCode(this.key); 469 hash = 11 * hash + Objects.hashCode(this.bins); 470 hash = 11 * hash + (this.adjustForBinSize ? 1 : 0); 471 return hash; 472 } 473 474 /** 475 * Returns a clone of the dataset. 476 * 477 * @return A clone. 478 * 479 * @throws CloneNotSupportedException not thrown by this class, but maybe 480 * by subclasses (if any). 481 */ 482 @Override 483 public Object clone() throws CloneNotSupportedException { 484 SimpleHistogramDataset clone = (SimpleHistogramDataset) super.clone(); 485 clone.bins = CloneUtils.cloneList(this.bins); 486 return clone; 487 } 488 489}