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