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 * 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 * 055 */ 056 057 package org.jfree.data.xy; 058 059 import java.io.Serializable; 060 import java.util.Collections; 061 import java.util.Iterator; 062 import java.util.List; 063 064 import org.jfree.data.DomainInfo; 065 import org.jfree.data.Range; 066 import org.jfree.data.UnknownKeyException; 067 import org.jfree.data.general.DatasetChangeEvent; 068 import org.jfree.data.general.DatasetUtilities; 069 import org.jfree.util.ObjectUtilities; 070 071 /** 072 * Represents a collection of {@link XYSeries} objects that can be used as a 073 * dataset. 074 */ 075 public class XYSeriesCollection extends AbstractIntervalXYDataset 076 implements IntervalXYDataset, DomainInfo, 077 Serializable { 078 079 /** For serialization. */ 080 private static final long serialVersionUID = -7590013825931496766L; 081 082 /** The series that are included in the collection. */ 083 private List data; 084 085 /** The interval delegate (used to calculate the start and end x-values). */ 086 private IntervalXYDelegate intervalDelegate; 087 088 /** 089 * Constructs an empty dataset. 090 */ 091 public XYSeriesCollection() { 092 this(null); 093 } 094 095 /** 096 * Constructs a dataset and populates it with a single series. 097 * 098 * @param series the series (<code>null</code> ignored). 099 */ 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 }