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 }