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