Source for org.jfree.data.xy.XYSeriesCollection

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, 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-2006, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Aaron Metzger;
  34:  *
  35:  * $Id: XYSeriesCollection.java,v 1.12.2.5 2007/03/08 13:57:09 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 15-Nov-2001 : Version 1 (DG);
  40:  * 03-Apr-2002 : Added change listener code (DG);
  41:  * 29-Apr-2002 : Added removeSeries, removeAllSeries methods (ARM);
  42:  * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  43:  * 26-Mar-2003 : Implemented Serializable (DG);
  44:  * 04-Aug-2003 : Added getSeries() method (DG);
  45:  * 31-Mar-2004 : Modified to use an XYIntervalDelegate.
  46:  * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
  47:  * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
  48:  * 17-Nov-2004 : Updated for changes to DomainInfo interface (DG);
  49:  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
  50:  * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG);
  51:  * 05-Oct-2005 : Made the interval delegate a dataset listener (DG);
  52:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  53:  * 27-Nov-2006 : Added clone() override (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.List;
  62: 
  63: import org.jfree.data.DomainInfo;
  64: import org.jfree.data.Range;
  65: import org.jfree.data.general.DatasetChangeEvent;
  66: import org.jfree.data.general.DatasetUtilities;
  67: import org.jfree.util.ObjectUtilities;
  68: 
  69: /**
  70:  * Represents a collection of {@link XYSeries} objects that can be used as a 
  71:  * dataset.
  72:  */
  73: public class XYSeriesCollection extends AbstractIntervalXYDataset
  74:                                 implements IntervalXYDataset, DomainInfo, 
  75:                                            Serializable {
  76: 
  77:     /** For serialization. */
  78:     private static final long serialVersionUID = -7590013825931496766L;
  79:     
  80:     /** The series that are included in the collection. */
  81:     private List data;
  82:     
  83:     /** The interval delegate (used to calculate the start and end x-values). */
  84:     private IntervalXYDelegate intervalDelegate;
  85:     
  86:     /**
  87:      * Constructs an empty dataset.
  88:      */
  89:     public XYSeriesCollection() {
  90:         this(null);
  91:     }
  92: 
  93:     /**
  94:      * Constructs a dataset and populates it with a single series.
  95:      *
  96:      * @param series  the series (<code>null</code> ignored).
  97:      */
  98:     public XYSeriesCollection(XYSeries series) {
  99:         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: }