Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ----------------------- 28: * XYSeriesCollection.java 29: * ----------------------- 30: * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Aaron Metzger; 34: * 35: * Changes 36: * ------- 37: * 15-Nov-2001 : Version 1 (DG); 38: * 03-Apr-2002 : Added change listener code (DG); 39: * 29-Apr-2002 : Added removeSeries, removeAllSeries methods (ARM); 40: * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 41: * 26-Mar-2003 : Implemented Serializable (DG); 42: * 04-Aug-2003 : Added getSeries() method (DG); 43: * 31-Mar-2004 : Modified to use an XYIntervalDelegate. 44: * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG); 45: * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG); 46: * 17-Nov-2004 : Updated for changes to DomainInfo interface (DG); 47: * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG); 48: * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG); 49: * 05-Oct-2005 : Made the interval delegate a dataset listener (DG); 50: * ------------- JFREECHART 1.0.x --------------------------------------------- 51: * 27-Nov-2006 : Added clone() override (DG); 52: * 08-May-2007 : Added indexOf(XYSeries) method (DG); 53: * 54: */ 55: 56: package org.jfree.data.xy; 57: 58: import java.io.Serializable; 59: import java.util.Collections; 60: import java.util.List; 61: 62: import org.jfree.data.DomainInfo; 63: import org.jfree.data.Range; 64: import org.jfree.data.general.DatasetChangeEvent; 65: import org.jfree.data.general.DatasetUtilities; 66: import org.jfree.util.ObjectUtilities; 67: 68: /** 69: * Represents a collection of {@link XYSeries} objects that can be used as a 70: * dataset. 71: */ 72: public class XYSeriesCollection extends AbstractIntervalXYDataset 73: implements IntervalXYDataset, DomainInfo, 74: Serializable { 75: 76: /** For serialization. */ 77: private static final long serialVersionUID = -7590013825931496766L; 78: 79: /** The series that are included in the collection. */ 80: private List data; 81: 82: /** The interval delegate (used to calculate the start and end x-values). */ 83: private IntervalXYDelegate intervalDelegate; 84: 85: /** 86: * Constructs an empty dataset. 87: */ 88: public XYSeriesCollection() { 89: this(null); 90: } 91: 92: /** 93: * Constructs a dataset and populates it with a single series. 94: * 95: * @param series the series (<code>null</code> ignored). 96: */ 97: public XYSeriesCollection(XYSeries series) { 98: this.data = new java.util.ArrayList(); 99: 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: }