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