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