001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, 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 * 22-Apr-2008 : Implemented PublicCloneable (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.Iterator; 063 import java.util.List; 064 065 import org.jfree.data.DomainInfo; 066 import org.jfree.data.Range; 067 import org.jfree.data.UnknownKeyException; 068 import org.jfree.data.general.DatasetChangeEvent; 069 import org.jfree.data.general.DatasetUtilities; 070 import org.jfree.util.ObjectUtilities; 071 import org.jfree.util.PublicCloneable; 072 073 /** 074 * Represents a collection of {@link XYSeries} objects that can be used as a 075 * dataset. 076 */ 077 public class XYSeriesCollection extends AbstractIntervalXYDataset 078 implements IntervalXYDataset, DomainInfo, PublicCloneable, 079 Serializable { 080 081 /** For serialization. */ 082 private static final long serialVersionUID = -7590013825931496766L; 083 084 /** The series that are included in the collection. */ 085 private List data; 086 087 /** The interval delegate (used to calculate the start and end x-values). */ 088 private IntervalXYDelegate intervalDelegate; 089 090 /** 091 * Constructs an empty dataset. 092 */ 093 public XYSeriesCollection() { 094 this(null); 095 } 096 097 /** 098 * Constructs a dataset and populates it with a single series. 099 * 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 }