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