    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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
    043package org.jfree.data.statistics;
    044
    045import java.io.Serializable;
    046import java.util.ArrayList;
    047import java.util.Collections;
    048import java.util.Iterator;
    049import java.util.List;
    050
    051import org.jfree.data.DomainOrder;
    052import org.jfree.data.general.DatasetChangeEvent;
    053import org.jfree.data.xy.AbstractIntervalXYDataset;
    054import org.jfree.data.xy.IntervalXYDataset;
    055import org.jfree.util.ObjectUtilities;
    056import org.jfree.util.PublicCloneable;
    057
    058/**
    059 * A dataset used for creating simple histograms with custom defined bins.
    060 *
    061 * @see HistogramDataset
    062 */
    063public 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}