001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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-2006, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Aaron Metzger; 034 * 035 * $Id: XYSeriesCollection.java,v 1.12.2.5 2007/03/08 13:57:09 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 15-Nov-2001 : Version 1 (DG); 040 * 03-Apr-2002 : Added change listener code (DG); 041 * 29-Apr-2002 : Added removeSeries, removeAllSeries methods (ARM); 042 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 043 * 26-Mar-2003 : Implemented Serializable (DG); 044 * 04-Aug-2003 : Added getSeries() method (DG); 045 * 31-Mar-2004 : Modified to use an XYIntervalDelegate. 046 * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG); 047 * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG); 048 * 17-Nov-2004 : Updated for changes to DomainInfo interface (DG); 049 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG); 050 * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG); 051 * 05-Oct-2005 : Made the interval delegate a dataset listener (DG); 052 * ------------- JFREECHART 1.0.x --------------------------------------------- 053 * 27-Nov-2006 : Added clone() override (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.List; 062 063 import org.jfree.data.DomainInfo; 064 import org.jfree.data.Range; 065 import org.jfree.data.general.DatasetChangeEvent; 066 import org.jfree.data.general.DatasetUtilities; 067 import org.jfree.util.ObjectUtilities; 068 069 /** 070 * Represents a collection of {@link XYSeries} objects that can be used as a 071 * dataset. 072 */ 073 public class XYSeriesCollection extends AbstractIntervalXYDataset 074 implements IntervalXYDataset, DomainInfo, 075 Serializable { 076 077 /** For serialization. */ 078 private static final long serialVersionUID = -7590013825931496766L; 079 080 /** The series that are included in the collection. */ 081 private List data; 082 083 /** The interval delegate (used to calculate the start and end x-values). */ 084 private IntervalXYDelegate intervalDelegate; 085 086 /** 087 * Constructs an empty dataset. 088 */ 089 public XYSeriesCollection() { 090 this(null); 091 } 092 093 /** 094 * Constructs a dataset and populates it with a single series. 095 * 096 * @param series the series (<code>null</code> ignored). 097 */ 098 public XYSeriesCollection(XYSeries series) { 099 this.data = new java.util.ArrayList(); 100 this.intervalDelegate = new IntervalXYDelegate(this, false); 101 addChangeListener(this.intervalDelegate); 102 if (series != null) { 103 this.data.add(series); 104 series.addChangeListener(this); 105 } 106 } 107 108 /** 109 * Adds a series to the collection and sends a {@link DatasetChangeEvent} 110 * to all registered listeners. 111 * 112 * @param series the series (<code>null</code> not permitted). 113 */ 114 public void addSeries(XYSeries series) { 115 116 if (series == null) { 117 throw new IllegalArgumentException("Null 'series' argument."); 118 } 119 this.data.add(series); 120 series.addChangeListener(this); 121 fireDatasetChanged(); 122 123 } 124 125 /** 126 * Removes a series from the collection and sends a 127 * {@link DatasetChangeEvent} to all registered listeners. 128 * 129 * @param series the series index (zero-based). 130 */ 131 public void removeSeries(int series) { 132 133 if ((series < 0) || (series >= getSeriesCount())) { 134 throw new IllegalArgumentException("Series index out of bounds."); 135 } 136 137 // fetch the series, remove the change listener, then remove the series. 138 XYSeries ts = (XYSeries) this.data.get(series); 139 ts.removeChangeListener(this); 140 this.data.remove(series); 141 fireDatasetChanged(); 142 143 } 144 145 /** 146 * Removes a series from the collection and sends a 147 * {@link DatasetChangeEvent} to all registered listeners. 148 * 149 * @param series the series (<code>null</code> not permitted). 150 */ 151 public void removeSeries(XYSeries series) { 152 153 if (series == null) { 154 throw new IllegalArgumentException("Null 'series' argument."); 155 } 156 if (this.data.contains(series)) { 157 series.removeChangeListener(this); 158 this.data.remove(series); 159 fireDatasetChanged(); 160 } 161 162 } 163 164 /** 165 * Removes all the series from the collection and sends a 166 * {@link DatasetChangeEvent} to all registered listeners. 167 */ 168 public void removeAllSeries() { 169 // Unregister the collection as a change listener to each series in 170 // the collection. 171 for (int i = 0; i < this.data.size(); i++) { 172 XYSeries series = (XYSeries) this.data.get(i); 173 series.removeChangeListener(this); 174 } 175 176 // Remove all the series from the collection and notify listeners. 177 this.data.clear(); 178 fireDatasetChanged(); 179 } 180 181 /** 182 * Returns the number of series in the collection. 183 * 184 * @return The series count. 185 */ 186 public int getSeriesCount() { 187 return this.data.size(); 188 } 189 190 /** 191 * Returns a list of all the series in the collection. 192 * 193 * @return The list (which is unmodifiable). 194 */ 195 public List getSeries() { 196 return Collections.unmodifiableList(this.data); 197 } 198 199 /** 200 * Returns a series from the collection. 201 * 202 * @param series the series index (zero-based). 203 * 204 * @return The series. 205 * 206 * @throws IllegalArgumentException if <code>series</code> is not in the 207 * range <code>0</code> to <code>getSeriesCount() - 1</code>. 208 */ 209 public XYSeries getSeries(int series) { 210 if ((series < 0) || (series >= getSeriesCount())) { 211 throw new IllegalArgumentException("Series index out of bounds"); 212 } 213 return (XYSeries) this.data.get(series); 214 } 215 216 /** 217 * Returns the key for a series. 218 * 219 * @param series the series index (in the range <code>0</code> to 220 * <code>getSeriesCount() - 1</code>). 221 * 222 * @return The key for a series. 223 * 224 * @throws IllegalArgumentException if <code>series</code> is not in the 225 * specified range. 226 */ 227 public Comparable getSeriesKey(int series) { 228 // defer argument checking 229 return getSeries(series).getKey(); 230 } 231 232 /** 233 * Returns the number of items in the specified series. 234 * 235 * @param series the series (zero-based index). 236 * 237 * @return The item count. 238 * 239 * @throws IllegalArgumentException if <code>series</code> is not in the 240 * range <code>0</code> to <code>getSeriesCount() - 1</code>. 241 */ 242 public int getItemCount(int series) { 243 // defer argument checking 244 return getSeries(series).getItemCount(); 245 } 246 247 /** 248 * Returns the x-value for the specified series and item. 249 * 250 * @param series the series (zero-based index). 251 * @param item the item (zero-based index). 252 * 253 * @return The value. 254 */ 255 public Number getX(int series, int item) { 256 XYSeries ts = (XYSeries) this.data.get(series); 257 XYDataItem xyItem = ts.getDataItem(item); 258 return xyItem.getX(); 259 } 260 261 /** 262 * Returns the starting X value for the specified series and item. 263 * 264 * @param series the series (zero-based index). 265 * @param item the item (zero-based index). 266 * 267 * @return The starting X value. 268 */ 269 public Number getStartX(int series, int item) { 270 return this.intervalDelegate.getStartX(series, item); 271 } 272 273 /** 274 * Returns the ending X value for the specified series and item. 275 * 276 * @param series the series (zero-based index). 277 * @param item the item (zero-based index). 278 * 279 * @return The ending X value. 280 */ 281 public Number getEndX(int series, int item) { 282 return this.intervalDelegate.getEndX(series, item); 283 } 284 285 /** 286 * Returns the y-value for the specified series and item. 287 * 288 * @param series the series (zero-based index). 289 * @param index the index of the item of interest (zero-based). 290 * 291 * @return The value (possibly <code>null</code>). 292 */ 293 public Number getY(int series, int index) { 294 295 XYSeries ts = (XYSeries) this.data.get(series); 296 XYDataItem xyItem = ts.getDataItem(index); 297 return xyItem.getY(); 298 299 } 300 301 /** 302 * Returns the starting Y value for the specified series and item. 303 * 304 * @param series the series (zero-based index). 305 * @param item the item (zero-based index). 306 * 307 * @return The starting Y value. 308 */ 309 public Number getStartY(int series, int item) { 310 return getY(series, item); 311 } 312 313 /** 314 * Returns the ending Y value for the specified series and item. 315 * 316 * @param series the series (zero-based index). 317 * @param item the item (zero-based index). 318 * 319 * @return The ending Y value. 320 */ 321 public Number getEndY(int series, int item) { 322 return getY(series, item); 323 } 324 325 /** 326 * Tests this collection for equality with an arbitrary object. 327 * 328 * @param obj the object (<code>null</code> permitted). 329 * 330 * @return A boolean. 331 */ 332 public boolean equals(Object obj) { 333 /* 334 * XXX 335 * 336 * what about the interval delegate...? 337 * The interval width etc wasn't considered 338 * before, hence i did not add it here (AS) 339 * 340 */ 341 342 if (obj == this) { 343 return true; 344 } 345 if (!(obj instanceof XYSeriesCollection)) { 346 return false; 347 } 348 XYSeriesCollection that = (XYSeriesCollection) obj; 349 return ObjectUtilities.equal(this.data, that.data); 350 } 351 352 /** 353 * Returns a clone of this instance. 354 * 355 * @return A clone. 356 * 357 * @throws CloneNotSupportedException if there is a problem. 358 */ 359 public Object clone() throws CloneNotSupportedException { 360 XYSeriesCollection clone = (XYSeriesCollection) super.clone(); 361 clone.data = (List) ObjectUtilities.deepClone(this.data); 362 clone.intervalDelegate 363 = (IntervalXYDelegate) this.intervalDelegate.clone(); 364 return clone; 365 } 366 367 /** 368 * Returns a hash code. 369 * 370 * @return A hash code. 371 */ 372 public int hashCode() { 373 // Same question as for equals (AS) 374 return (this.data != null ? this.data.hashCode() : 0); 375 } 376 377 /** 378 * Returns the minimum x-value in the dataset. 379 * 380 * @param includeInterval a flag that determines whether or not the 381 * x-interval is taken into account. 382 * 383 * @return The minimum value. 384 */ 385 public double getDomainLowerBound(boolean includeInterval) { 386 return this.intervalDelegate.getDomainLowerBound(includeInterval); 387 } 388 389 /** 390 * Returns the maximum x-value in the dataset. 391 * 392 * @param includeInterval a flag that determines whether or not the 393 * x-interval is taken into account. 394 * 395 * @return The maximum value. 396 */ 397 public double getDomainUpperBound(boolean includeInterval) { 398 return this.intervalDelegate.getDomainUpperBound(includeInterval); 399 } 400 401 /** 402 * Returns the range of the values in this dataset's domain. 403 * 404 * @param includeInterval a flag that determines whether or not the 405 * x-interval is taken into account. 406 * 407 * @return The range. 408 */ 409 public Range getDomainBounds(boolean includeInterval) { 410 if (includeInterval) { 411 return this.intervalDelegate.getDomainBounds(includeInterval); 412 } 413 else { 414 return DatasetUtilities.iterateDomainBounds(this, includeInterval); 415 } 416 417 } 418 419 /** 420 * Returns the interval width. This is used to calculate the start and end 421 * x-values, if/when the dataset is used as an {@link IntervalXYDataset}. 422 * 423 * @return The interval width. 424 */ 425 public double getIntervalWidth() { 426 return this.intervalDelegate.getIntervalWidth(); 427 } 428 429 /** 430 * Sets the interval width and sends a {@link DatasetChangeEvent} to all 431 * registered listeners. 432 * 433 * @param width the width (negative values not permitted). 434 */ 435 public void setIntervalWidth(double width) { 436 if (width < 0.0) { 437 throw new IllegalArgumentException("Negative 'width' argument."); 438 } 439 this.intervalDelegate.setFixedIntervalWidth(width); 440 fireDatasetChanged(); 441 } 442 443 /** 444 * Returns the interval position factor. 445 * 446 * @return The interval position factor. 447 */ 448 public double getIntervalPositionFactor() { 449 return this.intervalDelegate.getIntervalPositionFactor(); 450 } 451 452 /** 453 * Sets the interval position factor. This controls where the x-value is in 454 * relation to the interval surrounding the x-value (0.0 means the x-value 455 * will be positioned at the start, 0.5 in the middle, and 1.0 at the end). 456 * 457 * @param factor the factor. 458 */ 459 public void setIntervalPositionFactor(double factor) { 460 this.intervalDelegate.setIntervalPositionFactor(factor); 461 fireDatasetChanged(); 462 } 463 464 /** 465 * Returns whether the interval width is automatically calculated or not. 466 * 467 * @return Whether the width is automatically calculated or not. 468 */ 469 public boolean isAutoWidth() { 470 return this.intervalDelegate.isAutoWidth(); 471 } 472 473 /** 474 * Sets the flag that indicates wether the interval width is automatically 475 * calculated or not. 476 * 477 * @param b a boolean. 478 */ 479 public void setAutoWidth(boolean b) { 480 this.intervalDelegate.setAutoWidth(b); 481 fireDatasetChanged(); 482 } 483 484 }