001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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 by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: SimpleHistogramDataset.java,v 1.7.2.1 2005/10/25 21:34:46 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 10-Jan-2005 : Version 1 (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.
085         * 
086         * @param key  the series key.
087         */
088        public SimpleHistogramDataset(Comparable key) {
089            this.key = key;
090            this.bins = new ArrayList();
091            this.adjustForBinSize = true;
092        }
093        
094        /**
095         * Returns a flag that controls whether or not the bin count is divided by 
096         * the bin size in the {@link #getXValue(int, int)} method.
097         * 
098         * @return A boolean.
099         */
100        public boolean getAdjustForBinSize() {
101            return this.adjustForBinSize;
102        }
103        
104        /**
105         * Sets the flag that controls whether or not the bin count is divided by 
106         * the bin size in the {@link #getXValue(int, int)} method.
107         * 
108         * @param adjust  the flag.
109         */
110        public void setAdjustForBinSize(boolean adjust) {
111            this.adjustForBinSize = adjust;
112            notifyListeners(new DatasetChangeEvent(this, this));
113        }
114        
115        /**
116         * Returns the number of series in the dataset (always 1 for this dataset).
117         *
118         * @return The series count.
119         */
120        public int getSeriesCount() {
121            return 1;
122        }
123    
124        /**
125         * Returns the key for a series.
126         *
127         * @param series  the series (zero-based index, ignored in this dataset).
128         *
129         * @return The key for the series.
130         */
131        public Comparable getSeriesKey(int series) {
132            return this.key;    
133        }
134        
135        /**
136         * Returns the order of the domain (or X) values returned by the dataset.
137         * 
138         * @return The order (never <code>null</code>).
139         */
140        public DomainOrder getDomainOrder() {
141            return DomainOrder.ASCENDING;
142        }
143        
144        /**
145         * Returns the number of items in a series.
146         *
147         * @param series  the series index (zero-based, ignored in this dataset).
148         *
149         * @return The item count.
150         */
151        public int getItemCount(int series) {
152            return this.bins.size();
153        }
154        
155        /**
156         * Adds a bin to the dataset.  An exception is thrown if the bin overlaps 
157         * with any existing bin in the dataset.
158         * 
159         * @param bin  the bin (<code>null</code> not permitted).
160         */
161        public void addBin(SimpleHistogramBin bin) {
162            // check that the new bin doesn't overlap with any existing bin
163            Iterator iterator = this.bins.iterator();
164            while (iterator.hasNext()) {
165                SimpleHistogramBin existingBin 
166                    = (SimpleHistogramBin) iterator.next();
167                if (bin.overlapsWith(existingBin)) {
168                    throw new RuntimeException("Overlapping bin");
169                }
170            }
171            this.bins.add(bin);
172            Collections.sort(this.bins);
173        }
174        
175        /**
176         * Adds an observation to the dataset (by incrementing the item count for 
177         * the appropriate bin).  A runtime exception is thrown if the value does 
178         * not fit into any bin.
179         * 
180         * @param value  the value.
181         */
182        public void addObservation(double value) {
183            addObservation(value, true);
184        }
185        
186        /**
187         * Adds an observation to the dataset (by incrementing the item count for 
188         * the appropriate bin).  A runtime exception is thrown if the value does 
189         * not fit into any bin.
190         * 
191         * @param value  the value.
192         * @param notify  send {@link DatasetChangeEvent} to listeners?
193         */
194        public void addObservation(double value, boolean notify) {
195            boolean placed = false;
196            Iterator iterator = this.bins.iterator();
197            while (iterator.hasNext() && !placed) {
198                SimpleHistogramBin bin = (SimpleHistogramBin) iterator.next();
199                if (bin.accepts(value)) {
200                    bin.setItemCount(bin.getItemCount() + 1);
201                    placed = true;
202                }
203            }
204            if (!placed) {
205                throw new RuntimeException("No bin.");
206            }
207            if (notify) {
208                notifyListeners(new DatasetChangeEvent(this, this)); 
209            }
210        }
211        
212        /**
213         * Adds a set of values to the dataset.
214         * 
215         * @param values  the values.
216         */
217        public void addObservations(double[] values) {
218            for (int i = 0; i < values.length; i++) {
219                addObservation(values[i], false);
220            }
221            notifyListeners(new DatasetChangeEvent(this, this));
222        }
223    
224        /**
225         * Returns the x-value for an item within a series.  The x-values may or 
226         * may not be returned in ascending order, that is up to the class 
227         * implementing the interface.
228         *
229         * @param series  the series index (zero-based).
230         * @param item  the item index (zero-based).
231         *
232         * @return The x-value (never <code>null</code>).
233         */
234        public Number getX(int series, int item) {
235            return new Double(getXValue(series, item));
236        }
237    
238        /**
239         * Returns the x-value (as a double primitive) for an item within a series.
240         * 
241         * @param series  the series index (zero-based).
242         * @param item  the item index (zero-based).
243         * 
244         * @return The x-value.
245         */
246        public double getXValue(int series, int item) {
247            SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item);
248            return (bin.getLowerBound() + bin.getUpperBound()) / 2.0;
249        }
250        
251        /**
252         * Returns the y-value for an item within a series.
253         *
254         * @param series  the series index (zero-based).
255         * @param item  the item index (zero-based).
256         *
257         * @return The y-value (possibly <code>null</code>).
258         */
259        public Number getY(int series, int item) {
260            return new Double(getYValue(series, item));
261        }
262    
263        /**
264         * Returns the y-value (as a double primitive) for an item within a series.
265         * 
266         * @param series  the series index (zero-based).
267         * @param item  the item index (zero-based).
268         * 
269         * @return The y-value.
270         */
271        public double getYValue(int series, int item) {
272            SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item);
273            if (this.adjustForBinSize) {
274                return bin.getItemCount() 
275                       / (bin.getUpperBound() - bin.getLowerBound());
276            }
277            else {
278                return bin.getItemCount();
279            }
280        }
281        
282        /**
283         * Returns the starting X value for the specified series and item.
284         *
285         * @param series  the series index (zero-based).
286         * @param item  the item index (zero-based).
287         *
288         * @return The value.
289         */
290        public Number getStartX(int series, int item) {
291            return new Double(getStartXValue(series, item));
292        }
293    
294        /**
295         * Returns the start x-value (as a double primitive) for an item within a 
296         * series.
297         * 
298         * @param series  the series (zero-based index).
299         * @param item  the item (zero-based index).
300         * 
301         * @return The start x-value.
302         */
303        public double getStartXValue(int series, int item) {
304            SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item);
305            return bin.getLowerBound();
306        }
307    
308        /**
309         * Returns the ending X value for the specified series and item.
310         *
311         * @param series  the series index (zero-based).
312         * @param item  the item index (zero-based).
313         *
314         * @return The value.
315         */
316        public Number getEndX(int series, int item) {
317            return new Double(getEndXValue(series, item));
318        }
319    
320        /**
321         * Returns the end x-value (as a double primitive) for an item within a 
322         * series.
323         * 
324         * @param series  the series index (zero-based).
325         * @param item  the item index (zero-based).
326         * 
327         * @return The end x-value.
328         */
329        public double getEndXValue(int series, int item) {
330            SimpleHistogramBin bin = (SimpleHistogramBin) this.bins.get(item);
331            return bin.getUpperBound();
332        }
333    
334        /**
335         * Returns the starting Y 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        public Number getStartY(int series, int item) {
343            return getY(series, item);
344        }
345    
346        /**
347         * Returns the start y-value (as a double primitive) for an item within a 
348         * series.
349         * 
350         * @param series  the series index (zero-based).
351         * @param item  the item index (zero-based).
352         * 
353         * @return The start y-value.
354         */
355        public double getStartYValue(int series, int item) {
356            return getYValue(series, item);
357        }
358    
359        /**
360         * Returns the ending Y value for the specified series and item.
361         *
362         * @param series  the series index (zero-based).
363         * @param item  the item index (zero-based).
364         *
365         * @return The value.
366         */
367        public Number getEndY(int series, int item) {
368            return getY(series, item);
369        }
370    
371        /**
372         * Returns the end y-value (as a double primitive) for an item within a 
373         * series.
374         * 
375         * @param series  the series index (zero-based).
376         * @param item  the item index (zero-based).
377         * 
378         * @return The end y-value.
379         */
380        public double getEndYValue(int series, int item) {
381            return getYValue(series, item);
382        }
383    
384        /**
385         * Compares the dataset for equality with an arbitrary object.
386         * 
387         * @param obj  the object (<code>null</code> permitted).
388         * 
389         * @return A boolean.
390         */
391        public boolean equals(Object obj) {
392            if (obj == this) {
393                return true;
394            }
395            if (!(obj instanceof SimpleHistogramDataset)) {
396                return false;
397            }
398            SimpleHistogramDataset that = (SimpleHistogramDataset) obj;
399            if (!this.key.equals(that.key)) {
400                return false;
401            }
402            if (this.adjustForBinSize != that.adjustForBinSize) {
403                return false;
404            }
405            if (!this.bins.equals(that.bins)) {
406                return false;
407            }
408            return true;
409        }
410        
411        /**
412         * Returns a clone of the dataset.
413         * 
414         * @return A clone.
415         * 
416         * @throws CloneNotSupportedException not thrown by this class, but maybe 
417         *         by subclasses (if any).
418         */
419        public Object clone() throws CloneNotSupportedException {
420            SimpleHistogramDataset clone = (SimpleHistogramDataset) super.clone();
421            clone.bins = (List) ObjectUtilities.deepClone(this.bins);
422            return clone;
423        }
424        
425    }