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