Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, 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-2008, 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: * 03-Dec-2007 : Added getSeries(Comparable) method (DG); 54: * 22-Apr-2008 : Implemented PublicCloneable (DG); 55: * 56: */ 57: 58: package org.jfree.data.xy; 59: 60: import java.io.Serializable; 61: import java.util.Collections; 62: import java.util.Iterator; 63: import java.util.List; 64: 65: import org.jfree.data.DomainInfo; 66: import org.jfree.data.Range; 67: import org.jfree.data.UnknownKeyException; 68: import org.jfree.data.general.DatasetChangeEvent; 69: import org.jfree.data.general.DatasetUtilities; 70: import org.jfree.util.ObjectUtilities; 71: import org.jfree.util.PublicCloneable; 72: 73: /** 74: * Represents a collection of {@link XYSeries} objects that can be used as a 75: * dataset. 76: */ 77: public class XYSeriesCollection extends AbstractIntervalXYDataset 78: implements IntervalXYDataset, DomainInfo, PublicCloneable, 79: Serializable { 80: 81: /** For serialization. */ 82: private static final long serialVersionUID = -7590013825931496766L; 83: 84: /** The series that are included in the collection. */ 85: private List data; 86: 87: /** The interval delegate (used to calculate the start and end x-values). */ 88: private IntervalXYDelegate intervalDelegate; 89: 90: /** 91: * Constructs an empty dataset. 92: */ 93: public XYSeriesCollection() { 94: this(null); 95: } 96: 97: /** 98: * Constructs a dataset and populates it with a single series. 99: * 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: }