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