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