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