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