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
043package org.jfree.data.statistics;
044
045import java.io.Serializable;
046import java.util.ArrayList;
047import java.util.Collections;
048import java.util.Iterator;
049import java.util.List;
050
051import org.jfree.data.DomainOrder;
052import org.jfree.data.general.DatasetChangeEvent;
053import org.jfree.data.xy.AbstractIntervalXYDataset;
054import org.jfree.data.xy.IntervalXYDataset;
055import org.jfree.util.ObjectUtilities;
056import org.jfree.util.PublicCloneable;
057
058/**
059 * A dataset used for creating simple histograms with custom defined bins.
060 *
061 * @see HistogramDataset
062 */
063public 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}