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 }