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