    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    }