    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     * XYSeriesCollection.java
    029     * -----------------------
    030     * (C) Copyright 2001-2008, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Aaron Metzger;
    034     *
    035     * Changes
    036     * -------
    037     * 15-Nov-2001 : Version 1 (DG);
    038     * 03-Apr-2002 : Added change listener code (DG);
    039     * 29-Apr-2002 : Added removeSeries, removeAllSeries methods (ARM);
    040     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    041     * 26-Mar-2003 : Implemented Serializable (DG);
    042     * 04-Aug-2003 : Added getSeries() method (DG);
    043     * 31-Mar-2004 : Modified to use an XYIntervalDelegate.
    044     * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
    045     * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
    046     * 17-Nov-2004 : Updated for changes to DomainInfo interface (DG);
    047     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
    048     * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG);
    049     * 05-Oct-2005 : Made the interval delegate a dataset listener (DG);
    050     * ------------- JFREECHART 1.0.x ---------------------------------------------
    051     * 27-Nov-2006 : Added clone() override (DG);
    052     * 08-May-2007 : Added indexOf(XYSeries) method (DG);
    053     * 03-Dec-2007 : Added getSeries(Comparable) method (DG);
    054     * 22-Apr-2008 : Implemented PublicCloneable (DG);
    055     *
    056     */
    057
    058    package org.jfree.data.xy;
    059
    060    import java.io.Serializable;
    061    import java.util.Collections;
    062    import java.util.Iterator;
    063    import java.util.List;
    064
    065    import org.jfree.data.DomainInfo;
    066    import org.jfree.data.Range;
    067    import org.jfree.data.UnknownKeyException;
    068    import org.jfree.data.general.DatasetChangeEvent;
    069    import org.jfree.data.general.DatasetUtilities;
    070    import org.jfree.util.ObjectUtilities;
    071    import org.jfree.util.PublicCloneable;
    072
    073    /**
    074     * Represents a collection of {@link XYSeries} objects that can be used as a
    075     * dataset.
    076     */
    077    public class XYSeriesCollection extends AbstractIntervalXYDataset
    078            implements IntervalXYDataset, DomainInfo, PublicCloneable,
    079                       Serializable {
    080
    081        /** For serialization. */
    082        private static final long serialVersionUID = -7590013825931496766L;
    083
    084        /** The series that are included in the collection. */
    085        private List data;
    086
    087        /** The interval delegate (used to calculate the start and end x-values). */
    088        private IntervalXYDelegate intervalDelegate;
    089
    090        /**
    091         * Constructs an empty dataset.
    092         */
    093        public XYSeriesCollection() {
    094            this(null);
    095        }
    096
    097        /**
    098         * Constructs a dataset and populates it with a single series.
    099         *
    100         * @param series  the series (<code>null</code> ignored).
    101         */
    102        public XYSeriesCollection(XYSeries series) {
    103            this.data = new java.util.ArrayList();
    104            this.intervalDelegate = new IntervalXYDelegate(this, false);
    105            addChangeListener(this.intervalDelegate);
    106            if (series != null) {
    107                this.data.add(series);
    108                series.addChangeListener(this);
    109            }
    110        }
    111
    112        /**
    113         * Adds a series to the collection and sends a {@link DatasetChangeEvent}
    114         * to all registered listeners.
    115         *
    116         * @param series  the series (<code>null</code> not permitted).
    117         */
    118        public void addSeries(XYSeries series) {
    119
    120            if (series == null) {
    121                throw new IllegalArgumentException("Null 'series' argument.");
    122            }
    123            this.data.add(series);
    124            series.addChangeListener(this);
    125            fireDatasetChanged();
    126
    127        }
    128
    129        /**
    130         * Removes a series from the collection and sends a
    131         * {@link DatasetChangeEvent} to all registered listeners.
    132         *
    133         * @param series  the series index (zero-based).
    134         */
    135        public void removeSeries(int series) {
    136
    137            if ((series < 0) || (series >= getSeriesCount())) {
    138                throw new IllegalArgumentException("Series index out of bounds.");
    139            }
    140
    141            // fetch the series, remove the change listener, then remove the series.
    142            XYSeries ts = (XYSeries) this.data.get(series);
    143            ts.removeChangeListener(this);
    144            this.data.remove(series);
    145            fireDatasetChanged();
    146
    147        }
    148
    149        /**
    150         * Removes a series from the collection and sends a
    151         * {@link DatasetChangeEvent} to all registered listeners.
    152         *
    153         * @param series  the series (<code>null</code> not permitted).
    154         */
    155        public void removeSeries(XYSeries series) {
    156
    157            if (series == null) {
    158                throw new IllegalArgumentException("Null 'series' argument.");
    159            }
    160            if (this.data.contains(series)) {
    161                series.removeChangeListener(this);
    162                this.data.remove(series);
    163                fireDatasetChanged();
    164            }
    165
    166        }
    167
    168        /**
    169         * Removes all the series from the collection and sends a
    170         * {@link DatasetChangeEvent} to all registered listeners.
    171         */
    172        public void removeAllSeries() {
    173            // Unregister the collection as a change listener to each series in
    174            // the collection.
    175            for (int i = 0; i < this.data.size(); i++) {
    176              XYSeries series = (XYSeries) this.data.get(i);
    177              series.removeChangeListener(this);
    178            }
    179
    180            // Remove all the series from the collection and notify listeners.
    181            this.data.clear();
    182            fireDatasetChanged();
    183        }
    184
    185        /**
    186         * Returns the number of series in the collection.
    187         *
    188         * @return The series count.
    189         */
    190        public int getSeriesCount() {
    191            return this.data.size();
    192        }
    193
    194        /**
    195         * Returns a list of all the series in the collection.
    196         *
    197         * @return The list (which is unmodifiable).
    198         */
    199        public List getSeries() {
    200            return Collections.unmodifiableList(this.data);
    201        }
    202
    203        /**
    204         * Returns the index of the specified series, or -1 if that series is not
    205         * present in the dataset.
    206         *
    207         * @param series  the series (<code>null</code> not permitted).
    208         *
    209         * @return The series index.
    210         *
    211         * @since 1.0.6
    212         */
    213        public int indexOf(XYSeries series) {
    214            if (series == null) {
    215                throw new IllegalArgumentException("Null 'series' argument.");
    216            }
    217            return this.data.indexOf(series);
    218        }
    219
    220        /**
    221         * Returns a series from the collection.
    222         *
    223         * @param series  the series index (zero-based).
    224         *
    225         * @return The series.
    226         *
    227         * @throws IllegalArgumentException if <code>series</code> is not in the
    228         *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
    229         */
    230        public XYSeries getSeries(int series) {
    231            if ((series < 0) || (series >= getSeriesCount())) {
    232                throw new IllegalArgumentException("Series index out of bounds");
    233            }
    234            return (XYSeries) this.data.get(series);
    235        }
    236
    237        /**
    238         * Returns a series from the collection.
    239         *
    240         * @param key  the key (<code>null</code> not permitted).
    241         *
    242         * @return The series with the specified key.
    243         *
    244         * @throws UnknownKeyException if <code>key</code> is not found in the
    245         *         collection.
    246         *
    247         * @since 1.0.9
    248         */
    249        public XYSeries getSeries(Comparable key) {
    250            if (key == null) {
    251                throw new IllegalArgumentException("Null 'key' argument.");
    252            }
    253            Iterator iterator = this.data.iterator();
    254            while (iterator.hasNext()) {
    255                XYSeries series = (XYSeries) iterator.next();
    256                if (key.equals(series.getKey())) {
    257                    return series;
    258                }
    259            }
    260            throw new UnknownKeyException("Key not found: " + key);
    261        }
    262
    263        /**
    264         * Returns the key for a series.
    265         *
    266         * @param series  the series index (in the range <code>0</code> to
    267         *     <code>getSeriesCount() - 1</code>).
    268         *
    269         * @return The key for a series.
    270         *
    271         * @throws IllegalArgumentException if <code>series</code> is not in the
    272         *     specified range.
    273         */
    274        public Comparable getSeriesKey(int series) {
    275            // defer argument checking
    276            return getSeries(series).getKey();
    277        }
    278
    279        /**
    280         * Returns the number of items in the specified series.
    281         *
    282         * @param series  the series (zero-based index).
    283         *
    284         * @return The item count.
    285         *
    286         * @throws IllegalArgumentException if <code>series</code> is not in the
    287         *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
    288         */
    289        public int getItemCount(int series) {
    290            // defer argument checking
    291            return getSeries(series).getItemCount();
    292        }
    293
    294        /**
    295         * Returns the x-value for the specified series and item.
    296         *
    297         * @param series  the series (zero-based index).
    298         * @param item  the item (zero-based index).
    299         *
    300         * @return The value.
    301         */
    302        public Number getX(int series, int item) {
    303            XYSeries ts = (XYSeries) this.data.get(series);
    304            XYDataItem xyItem = ts.getDataItem(item);
    305            return xyItem.getX();
    306        }
    307
    308        /**
    309         * Returns the starting X value for the specified series and item.
    310         *
    311         * @param series  the series (zero-based index).
    312         * @param item  the item (zero-based index).
    313         *
    314         * @return The starting X value.
    315         */
    316        public Number getStartX(int series, int item) {
    317            return this.intervalDelegate.getStartX(series, item);
    318        }
    319
    320        /**
    321         * Returns the ending X value for the specified series and item.
    322         *
    323         * @param series  the series (zero-based index).
    324         * @param item  the item (zero-based index).
    325         *
    326         * @return The ending X value.
    327         */
    328        public Number getEndX(int series, int item) {
    329            return this.intervalDelegate.getEndX(series, item);
    330        }
    331
    332        /**
    333         * Returns the y-value for the specified series and item.
    334         *
    335         * @param series  the series (zero-based index).
    336         * @param index  the index of the item of interest (zero-based).
    337         *
    338         * @return The value (possibly <code>null</code>).
    339         */
    340        public Number getY(int series, int index) {
    341
    342            XYSeries ts = (XYSeries) this.data.get(series);
    343            XYDataItem xyItem = ts.getDataItem(index);
    344            return xyItem.getY();
    345
    346        }
    347
    348        /**
    349         * Returns the starting Y value for the specified series and item.
    350         *
    351         * @param series  the series (zero-based index).
    352         * @param item  the item (zero-based index).
    353         *
    354         * @return The starting Y value.
    355         */
    356        public Number getStartY(int series, int item) {
    357            return getY(series, item);
    358        }
    359
    360        /**
    361         * Returns the ending Y value for the specified series and item.
    362         *
    363         * @param series  the series (zero-based index).
    364         * @param item  the item (zero-based index).
    365         *
    366         * @return The ending Y value.
    367         */
    368        public Number getEndY(int series, int item) {
    369            return getY(series, item);
    370        }
    371
    372        /**
    373         * Tests this collection for equality with an arbitrary object.
    374         *
    375         * @param obj  the object (<code>null</code> permitted).
    376         *
    377         * @return A boolean.
    378         */
    379        public boolean equals(Object obj) {
    380            /*
    381             * XXX
    382             *
    383             * what about  the interval delegate...?
    384             * The interval width etc wasn't considered
    385             * before, hence i did not add it here (AS)
    386             *
    387             */
    388
    389            if (obj == this) {
    390                return true;
    391            }
    392            if (!(obj instanceof XYSeriesCollection)) {
    393                return false;
    394            }
    395            XYSeriesCollection that = (XYSeriesCollection) obj;
    396            return ObjectUtilities.equal(this.data, that.data);
    397        }
    398
    399        /**
    400         * Returns a clone of this instance.
    401         *
    402         * @return A clone.
    403         *
    404         * @throws CloneNotSupportedException if there is a problem.
    405         */
    406        public Object clone() throws CloneNotSupportedException {
    407            XYSeriesCollection clone = (XYSeriesCollection) super.clone();
    408            clone.data = (List) ObjectUtilities.deepClone(this.data);
    409            clone.intervalDelegate
    410                    = (IntervalXYDelegate) this.intervalDelegate.clone();
    411            return clone;
    412        }
    413
    414        /**
    415         * Returns a hash code.
    416         *
    417         * @return A hash code.
    418         */
    419        public int hashCode() {
    420            // Same question as for equals (AS)
    421            return (this.data != null ? this.data.hashCode() : 0);
    422        }
    423
    424        /**
    425         * Returns the minimum x-value in the dataset.
    426         *
    427         * @param includeInterval  a flag that determines whether or not the
    428         *                         x-interval is taken into account.
    429         *
    430         * @return The minimum value.
    431         */
    432        public double getDomainLowerBound(boolean includeInterval) {
    433            return this.intervalDelegate.getDomainLowerBound(includeInterval);
    434        }
    435
    436        /**
    437         * Returns the maximum x-value in the dataset.
    438         *
    439         * @param includeInterval  a flag that determines whether or not the
    440         *                         x-interval is taken into account.
    441         *
    442         * @return The maximum value.
    443         */
    444        public double getDomainUpperBound(boolean includeInterval) {
    445            return this.intervalDelegate.getDomainUpperBound(includeInterval);
    446        }
    447
    448        /**
    449         * Returns the range of the values in this dataset's domain.
    450         *
    451         * @param includeInterval  a flag that determines whether or not the
    452         *                         x-interval is taken into account.
    453         *
    454         * @return The range.
    455         */
    456        public Range getDomainBounds(boolean includeInterval) {
    457            if (includeInterval) {
    458                return this.intervalDelegate.getDomainBounds(includeInterval);
    459            }
    460            else {
    461                return DatasetUtilities.iterateDomainBounds(this, includeInterval);
    462            }
    463
    464        }
    465
    466        /**
    467         * Returns the interval width. This is used to calculate the start and end
    468         * x-values, if/when the dataset is used as an {@link IntervalXYDataset}.
    469         *
    470         * @return The interval width.
    471         */
    472        public double getIntervalWidth() {
    473            return this.intervalDelegate.getIntervalWidth();
    474        }
    475
    476        /**
    477         * Sets the interval width and sends a {@link DatasetChangeEvent} to all
    478         * registered listeners.
    479         *
    480         * @param width  the width (negative values not permitted).
    481         */
    482        public void setIntervalWidth(double width) {
    483            if (width < 0.0) {
    484                throw new IllegalArgumentException("Negative 'width' argument.");
    485            }
    486            this.intervalDelegate.setFixedIntervalWidth(width);
    487            fireDatasetChanged();
    488        }
    489
    490        /**
    491         * Returns the interval position factor.
    492         *
    493         * @return The interval position factor.
    494         */
    495        public double getIntervalPositionFactor() {
    496            return this.intervalDelegate.getIntervalPositionFactor();
    497        }
    498
    499        /**
    500         * Sets the interval position factor. This controls where the x-value is in
    501         * relation to the interval surrounding the x-value (0.0 means the x-value
    502         * will be positioned at the start, 0.5 in the middle, and 1.0 at the end).
    503         *
    504         * @param factor  the factor.
    505         */
    506        public void setIntervalPositionFactor(double factor) {
    507            this.intervalDelegate.setIntervalPositionFactor(factor);
    508            fireDatasetChanged();
    509        }
    510
    511        /**
    512         * Returns whether the interval width is automatically calculated or not.
    513         *
    514         * @return Whether the width is automatically calculated or not.
    515         */
    516        public boolean isAutoWidth() {
    517            return this.intervalDelegate.isAutoWidth();
    518        }
    519
    520        /**
    521         * Sets the flag that indicates wether the interval width is automatically
    522         * calculated or not.
    523         *
    524         * @param b  a boolean.
    525         */
    526        public void setAutoWidth(boolean b) {
    527            this.intervalDelegate.setAutoWidth(b);
    528            fireDatasetChanged();
    529        }
    530
    531    }