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 }