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