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