    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    }