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