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-2008, 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-2008, 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:  * 03-Dec-2007 : Added getSeries(Comparable) method (DG);
  54:  * 22-Apr-2008 : Implemented PublicCloneable (DG);
  55:  *
  56:  */
  57: 
  58: package org.jfree.data.xy;
  59: 
  60: import java.io.Serializable;
  61: import java.util.Collections;
  62: import java.util.Iterator;
  63: import java.util.List;
  64: 
  65: import org.jfree.data.DomainInfo;
  66: import org.jfree.data.Range;
  67: import org.jfree.data.UnknownKeyException;
  68: import org.jfree.data.general.DatasetChangeEvent;
  69: import org.jfree.data.general.DatasetUtilities;
  70: import org.jfree.util.ObjectUtilities;
  71: import org.jfree.util.PublicCloneable;
  72: 
  73: /**
  74:  * Represents a collection of {@link XYSeries} objects that can be used as a
  75:  * dataset.
  76:  */
  77: public class XYSeriesCollection extends AbstractIntervalXYDataset
  78:         implements IntervalXYDataset, DomainInfo, PublicCloneable,
  79:                    Serializable {
  80: 
  81:     /** For serialization. */
  82:     private static final long serialVersionUID = -7590013825931496766L;
  83: 
  84:     /** The series that are included in the collection. */
  85:     private List data;
  86: 
  87:     /** The interval delegate (used to calculate the start and end x-values). */
  88:     private IntervalXYDelegate intervalDelegate;
  89: 
  90:     /**
  91:      * Constructs an empty dataset.
  92:      */
  93:     public XYSeriesCollection() {
  94:         this(null);
  95:     }
  96: 
  97:     /**
  98:      * Constructs a dataset and populates it with a single series.
  99:      *
 100:      * @param series  the series (<code>null</code> ignored).
 101:      */
 102:     public XYSeriesCollection(XYSeries series) {
 103:         this.data = new java.util.ArrayList();
 104:         this.intervalDelegate = new IntervalXYDelegate(this, false);
 105:         addChangeListener(this.intervalDelegate);
 106:         if (series != null) {
 107:             this.data.add(series);
 108:             series.addChangeListener(this);
 109:         }
 110:     }
 111: 
 112:     /**
 113:      * Adds a series to the collection and sends a {@link DatasetChangeEvent}
 114:      * to all registered listeners.
 115:      *
 116:      * @param series  the series (<code>null</code> not permitted).
 117:      */
 118:     public void addSeries(XYSeries series) {
 119: 
 120:         if (series == null) {
 121:             throw new IllegalArgumentException("Null 'series' argument.");
 122:         }
 123:         this.data.add(series);
 124:         series.addChangeListener(this);
 125:         fireDatasetChanged();
 126: 
 127:     }
 128: 
 129:     /**
 130:      * Removes a series from the collection and sends a
 131:      * {@link DatasetChangeEvent} to all registered listeners.
 132:      *
 133:      * @param series  the series index (zero-based).
 134:      */
 135:     public void removeSeries(int series) {
 136: 
 137:         if ((series < 0) || (series >= getSeriesCount())) {
 138:             throw new IllegalArgumentException("Series index out of bounds.");
 139:         }
 140: 
 141:         // fetch the series, remove the change listener, then remove the series.
 142:         XYSeries ts = (XYSeries) this.data.get(series);
 143:         ts.removeChangeListener(this);
 144:         this.data.remove(series);
 145:         fireDatasetChanged();
 146: 
 147:     }
 148: 
 149:     /**
 150:      * Removes a series from the collection and sends a
 151:      * {@link DatasetChangeEvent} to all registered listeners.
 152:      *
 153:      * @param series  the series (<code>null</code> not permitted).
 154:      */
 155:     public void removeSeries(XYSeries series) {
 156: 
 157:         if (series == null) {
 158:             throw new IllegalArgumentException("Null 'series' argument.");
 159:         }
 160:         if (this.data.contains(series)) {
 161:             series.removeChangeListener(this);
 162:             this.data.remove(series);
 163:             fireDatasetChanged();
 164:         }
 165: 
 166:     }
 167: 
 168:     /**
 169:      * Removes all the series from the collection and sends a
 170:      * {@link DatasetChangeEvent} to all registered listeners.
 171:      */
 172:     public void removeAllSeries() {
 173:         // Unregister the collection as a change listener to each series in
 174:         // the collection.
 175:         for (int i = 0; i < this.data.size(); i++) {
 176:           XYSeries series = (XYSeries) this.data.get(i);
 177:           series.removeChangeListener(this);
 178:         }
 179: 
 180:         // Remove all the series from the collection and notify listeners.
 181:         this.data.clear();
 182:         fireDatasetChanged();
 183:     }
 184: 
 185:     /**
 186:      * Returns the number of series in the collection.
 187:      *
 188:      * @return The series count.
 189:      */
 190:     public int getSeriesCount() {
 191:         return this.data.size();
 192:     }
 193: 
 194:     /**
 195:      * Returns a list of all the series in the collection.
 196:      *
 197:      * @return The list (which is unmodifiable).
 198:      */
 199:     public List getSeries() {
 200:         return Collections.unmodifiableList(this.data);
 201:     }
 202: 
 203:     /**
 204:      * Returns the index of the specified series, or -1 if that series is not
 205:      * present in the dataset.
 206:      *
 207:      * @param series  the series (<code>null</code> not permitted).
 208:      *
 209:      * @return The series index.
 210:      *
 211:      * @since 1.0.6
 212:      */
 213:     public int indexOf(XYSeries series) {
 214:         if (series == null) {
 215:             throw new IllegalArgumentException("Null 'series' argument.");
 216:         }
 217:         return this.data.indexOf(series);
 218:     }
 219: 
 220:     /**
 221:      * Returns a series from the collection.
 222:      *
 223:      * @param series  the series index (zero-based).
 224:      *
 225:      * @return The series.
 226:      *
 227:      * @throws IllegalArgumentException if <code>series</code> is not in the
 228:      *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
 229:      */
 230:     public XYSeries getSeries(int series) {
 231:         if ((series < 0) || (series >= getSeriesCount())) {
 232:             throw new IllegalArgumentException("Series index out of bounds");
 233:         }
 234:         return (XYSeries) this.data.get(series);
 235:     }
 236: 
 237:     /**
 238:      * Returns a series from the collection.
 239:      *
 240:      * @param key  the key (<code>null</code> not permitted).
 241:      *
 242:      * @return The series with the specified key.
 243:      *
 244:      * @throws UnknownKeyException if <code>key</code> is not found in the
 245:      *         collection.
 246:      *
 247:      * @since 1.0.9
 248:      */
 249:     public XYSeries getSeries(Comparable key) {
 250:         if (key == null) {
 251:             throw new IllegalArgumentException("Null 'key' argument.");
 252:         }
 253:         Iterator iterator = this.data.iterator();
 254:         while (iterator.hasNext()) {
 255:             XYSeries series = (XYSeries) iterator.next();
 256:             if (key.equals(series.getKey())) {
 257:                 return series;
 258:             }
 259:         }
 260:         throw new UnknownKeyException("Key not found: " + key);
 261:     }
 262: 
 263:     /**
 264:      * Returns the key for a series.
 265:      *
 266:      * @param series  the series index (in the range <code>0</code> to
 267:      *     <code>getSeriesCount() - 1</code>).
 268:      *
 269:      * @return The key for a series.
 270:      *
 271:      * @throws IllegalArgumentException if <code>series</code> is not in the
 272:      *     specified range.
 273:      */
 274:     public Comparable getSeriesKey(int series) {
 275:         // defer argument checking
 276:         return getSeries(series).getKey();
 277:     }
 278: 
 279:     /**
 280:      * Returns the number of items in the specified series.
 281:      *
 282:      * @param series  the series (zero-based index).
 283:      *
 284:      * @return The item count.
 285:      *
 286:      * @throws IllegalArgumentException if <code>series</code> is not in the
 287:      *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
 288:      */
 289:     public int getItemCount(int series) {
 290:         // defer argument checking
 291:         return getSeries(series).getItemCount();
 292:     }
 293: 
 294:     /**
 295:      * Returns the x-value for the specified series and item.
 296:      *
 297:      * @param series  the series (zero-based index).
 298:      * @param item  the item (zero-based index).
 299:      *
 300:      * @return The value.
 301:      */
 302:     public Number getX(int series, int item) {
 303:         XYSeries ts = (XYSeries) this.data.get(series);
 304:         XYDataItem xyItem = ts.getDataItem(item);
 305:         return xyItem.getX();
 306:     }
 307: 
 308:     /**
 309:      * Returns the starting X value for the specified series and item.
 310:      *
 311:      * @param series  the series (zero-based index).
 312:      * @param item  the item (zero-based index).
 313:      *
 314:      * @return The starting X value.
 315:      */
 316:     public Number getStartX(int series, int item) {
 317:         return this.intervalDelegate.getStartX(series, item);
 318:     }
 319: 
 320:     /**
 321:      * Returns the ending X value for the specified series and item.
 322:      *
 323:      * @param series  the series (zero-based index).
 324:      * @param item  the item (zero-based index).
 325:      *
 326:      * @return The ending X value.
 327:      */
 328:     public Number getEndX(int series, int item) {
 329:         return this.intervalDelegate.getEndX(series, item);
 330:     }
 331: 
 332:     /**
 333:      * Returns the y-value for the specified series and item.
 334:      *
 335:      * @param series  the series (zero-based index).
 336:      * @param index  the index of the item of interest (zero-based).
 337:      *
 338:      * @return The value (possibly <code>null</code>).
 339:      */
 340:     public Number getY(int series, int index) {
 341: 
 342:         XYSeries ts = (XYSeries) this.data.get(series);
 343:         XYDataItem xyItem = ts.getDataItem(index);
 344:         return xyItem.getY();
 345: 
 346:     }
 347: 
 348:     /**
 349:      * Returns the starting Y value for the specified series and item.
 350:      *
 351:      * @param series  the series (zero-based index).
 352:      * @param item  the item (zero-based index).
 353:      *
 354:      * @return The starting Y value.
 355:      */
 356:     public Number getStartY(int series, int item) {
 357:         return getY(series, item);
 358:     }
 359: 
 360:     /**
 361:      * Returns the ending Y value for the specified series and item.
 362:      *
 363:      * @param series  the series (zero-based index).
 364:      * @param item  the item (zero-based index).
 365:      *
 366:      * @return The ending Y value.
 367:      */
 368:     public Number getEndY(int series, int item) {
 369:         return getY(series, item);
 370:     }
 371: 
 372:     /**
 373:      * Tests this collection for equality with an arbitrary object.
 374:      *
 375:      * @param obj  the object (<code>null</code> permitted).
 376:      *
 377:      * @return A boolean.
 378:      */
 379:     public boolean equals(Object obj) {
 380:         /*
 381:          * XXX
 382:          *
 383:          * what about  the interval delegate...?
 384:          * The interval width etc wasn't considered
 385:          * before, hence i did not add it here (AS)
 386:          *
 387:          */
 388: 
 389:         if (obj == this) {
 390:             return true;
 391:         }
 392:         if (!(obj instanceof XYSeriesCollection)) {
 393:             return false;
 394:         }
 395:         XYSeriesCollection that = (XYSeriesCollection) obj;
 396:         return ObjectUtilities.equal(this.data, that.data);
 397:     }
 398: 
 399:     /**
 400:      * Returns a clone of this instance.
 401:      *
 402:      * @return A clone.
 403:      *
 404:      * @throws CloneNotSupportedException if there is a problem.
 405:      */
 406:     public Object clone() throws CloneNotSupportedException {
 407:         XYSeriesCollection clone = (XYSeriesCollection) super.clone();
 408:         clone.data = (List) ObjectUtilities.deepClone(this.data);
 409:         clone.intervalDelegate
 410:                 = (IntervalXYDelegate) this.intervalDelegate.clone();
 411:         return clone;
 412:     }
 413: 
 414:     /**
 415:      * Returns a hash code.
 416:      *
 417:      * @return A hash code.
 418:      */
 419:     public int hashCode() {
 420:         // Same question as for equals (AS)
 421:         return (this.data != null ? this.data.hashCode() : 0);
 422:     }
 423: 
 424:     /**
 425:      * Returns the minimum x-value in the dataset.
 426:      *
 427:      * @param includeInterval  a flag that determines whether or not the
 428:      *                         x-interval is taken into account.
 429:      *
 430:      * @return The minimum value.
 431:      */
 432:     public double getDomainLowerBound(boolean includeInterval) {
 433:         return this.intervalDelegate.getDomainLowerBound(includeInterval);
 434:     }
 435: 
 436:     /**
 437:      * Returns the maximum x-value in the dataset.
 438:      *
 439:      * @param includeInterval  a flag that determines whether or not the
 440:      *                         x-interval is taken into account.
 441:      *
 442:      * @return The maximum value.
 443:      */
 444:     public double getDomainUpperBound(boolean includeInterval) {
 445:         return this.intervalDelegate.getDomainUpperBound(includeInterval);
 446:     }
 447: 
 448:     /**
 449:      * Returns the range of the values in this dataset's domain.
 450:      *
 451:      * @param includeInterval  a flag that determines whether or not the
 452:      *                         x-interval is taken into account.
 453:      *
 454:      * @return The range.
 455:      */
 456:     public Range getDomainBounds(boolean includeInterval) {
 457:         if (includeInterval) {
 458:             return this.intervalDelegate.getDomainBounds(includeInterval);
 459:         }
 460:         else {
 461:             return DatasetUtilities.iterateDomainBounds(this, includeInterval);
 462:         }
 463: 
 464:     }
 465: 
 466:     /**
 467:      * Returns the interval width. This is used to calculate the start and end
 468:      * x-values, if/when the dataset is used as an {@link IntervalXYDataset}.
 469:      *
 470:      * @return The interval width.
 471:      */
 472:     public double getIntervalWidth() {
 473:         return this.intervalDelegate.getIntervalWidth();
 474:     }
 475: 
 476:     /**
 477:      * Sets the interval width and sends a {@link DatasetChangeEvent} to all
 478:      * registered listeners.
 479:      *
 480:      * @param width  the width (negative values not permitted).
 481:      */
 482:     public void setIntervalWidth(double width) {
 483:         if (width < 0.0) {
 484:             throw new IllegalArgumentException("Negative 'width' argument.");
 485:         }
 486:         this.intervalDelegate.setFixedIntervalWidth(width);
 487:         fireDatasetChanged();
 488:     }
 489: 
 490:     /**
 491:      * Returns the interval position factor.
 492:      *
 493:      * @return The interval position factor.
 494:      */
 495:     public double getIntervalPositionFactor() {
 496:         return this.intervalDelegate.getIntervalPositionFactor();
 497:     }
 498: 
 499:     /**
 500:      * Sets the interval position factor. This controls where the x-value is in
 501:      * relation to the interval surrounding the x-value (0.0 means the x-value
 502:      * will be positioned at the start, 0.5 in the middle, and 1.0 at the end).
 503:      *
 504:      * @param factor  the factor.
 505:      */
 506:     public void setIntervalPositionFactor(double factor) {
 507:         this.intervalDelegate.setIntervalPositionFactor(factor);
 508:         fireDatasetChanged();
 509:     }
 510: 
 511:     /**
 512:      * Returns whether the interval width is automatically calculated or not.
 513:      *
 514:      * @return Whether the width is automatically calculated or not.
 515:      */
 516:     public boolean isAutoWidth() {
 517:         return this.intervalDelegate.isAutoWidth();
 518:     }
 519: 
 520:     /**
 521:      * Sets the flag that indicates wether the interval width is automatically
 522:      * calculated or not.
 523:      *
 524:      * @param b  a boolean.
 525:      */
 526:     public void setAutoWidth(boolean b) {
 527:         this.intervalDelegate.setAutoWidth(b);
 528:         fireDatasetChanged();
 529:     }
 530: 
 531: }