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
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, 
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}