Source for org.jfree.data.xy.XYSeries

   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:  * XYSeries.java
  29:  * -------------
  30:  * (C) Copyright 2001-2008, Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Aaron Metzger;
  34:  *                   Jonathan Gabbai;
  35:  *                   Richard Atkinson;
  36:  *                   Michel Santos;
  37:  *                   Ted Schwartz (fix for bug 1955483);
  38:  *
  39:  * Changes
  40:  * -------
  41:  * 15-Nov-2001 : Version 1 (DG);
  42:  * 03-Apr-2002 : Added an add(double, double) method (DG);
  43:  * 29-Apr-2002 : Added a clear() method (ARM);
  44:  * 06-Jun-2002 : Updated Javadoc comments (DG);
  45:  * 29-Aug-2002 : Modified to give user control over whether or not duplicate
  46:  *               x-values are allowed (DG);
  47:  * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48:  * 11-Nov-2002 : Added maximum item count, code contributed by Jonathan
  49:  *               Gabbai (DG);
  50:  * 26-Mar-2003 : Implemented Serializable (DG);
  51:  * 04-Aug-2003 : Added getItems() method (DG);
  52:  * 15-Aug-2003 : Changed 'data' from private to protected, added new add()
  53:  *               methods with a 'notify' argument (DG);
  54:  * 22-Sep-2003 : Added getAllowDuplicateXValues() method (RA);
  55:  * 29-Jan-2004 : Added autoSort attribute, based on a contribution by
  56:  *               Michel Santos - see patch 886740 (DG);
  57:  * 03-Feb-2004 : Added indexOf() method (DG);
  58:  * 16-Feb-2004 : Added remove() method (DG);
  59:  * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
  60:  * 21-Feb-2005 : Added update(Number, Number) and addOrUpdate(Number, Number)
  61:  *               methods (DG);
  62:  * 03-May-2005 : Added a new constructor, fixed the setMaximumItemCount()
  63:  *               method to remove items (and notify listeners) if necessary,
  64:  *               fixed the add() and addOrUpdate() methods to handle unsorted
  65:  *               series (DG);
  66:  * ------------- JFreeChart 1.0.x ---------------------------------------------
  67:  * 11-Jan-2005 : Renamed update(int, Number) --> updateByIndex() (DG);
  68:  * 15-Jan-2007 : Added toArray() method (DG);
  69:  * 31-Oct-2007 : Implemented faster hashCode() (DG);
  70:  * 22-Nov-2007 : Reimplemented clone() (DG);
  71:  * 01-May-2008 : Fixed bug 1955483 in addOrUpdate() method, thanks to
  72:  *               Ted Schwartz (DG);
  73:  *
  74:  */
  75: 
  76: package org.jfree.data.xy;
  77: 
  78: import java.io.Serializable;
  79: import java.util.Collections;
  80: import java.util.List;
  81: 
  82: import org.jfree.data.general.Series;
  83: import org.jfree.data.general.SeriesChangeEvent;
  84: import org.jfree.data.general.SeriesException;
  85: import org.jfree.util.ObjectUtilities;
  86: 
  87: /**
  88:  * Represents a sequence of zero or more data items in the form (x, y).  By
  89:  * default, items in the series will be sorted into ascending order by x-value,
  90:  * and duplicate x-values are permitted.  Both the sorting and duplicate
  91:  * defaults can be changed in the constructor.  Y-values can be
  92:  * <code>null</code> to represent missing values.
  93:  */
  94: public class XYSeries extends Series implements Cloneable, Serializable {
  95: 
  96:     /** For serialization. */
  97:     static final long serialVersionUID = -5908509288197150436L;
  98: 
  99:     // In version 0.9.12, in response to several developer requests, I changed
 100:     // the 'data' attribute from 'private' to 'protected', so that others can
 101:     // make subclasses that work directly with the underlying data structure.
 102: 
 103:     /** Storage for the data items in the series. */
 104:     protected List data;
 105: 
 106:     /** The maximum number of items for the series. */
 107:     private int maximumItemCount = Integer.MAX_VALUE;
 108: 
 109:     /** A flag that controls whether the items are automatically sorted. */
 110:     private boolean autoSort;
 111: 
 112:     /** A flag that controls whether or not duplicate x-values are allowed. */
 113:     private boolean allowDuplicateXValues;
 114: 
 115:     /**
 116:      * Creates a new empty series.  By default, items added to the series will
 117:      * be sorted into ascending order by x-value, and duplicate x-values will
 118:      * be allowed (these defaults can be modified with another constructor.
 119:      *
 120:      * @param key  the series key (<code>null</code> not permitted).
 121:      */
 122:     public XYSeries(Comparable key) {
 123:         this(key, true, true);
 124:     }
 125: 
 126:     /**
 127:      * Constructs a new empty series, with the auto-sort flag set as requested,
 128:      * and duplicate values allowed.
 129:      *
 130:      * @param key  the series key (<code>null</code> not permitted).
 131:      * @param autoSort  a flag that controls whether or not the items in the
 132:      *                  series are sorted.
 133:      */
 134:     public XYSeries(Comparable key, boolean autoSort) {
 135:         this(key, autoSort, true);
 136:     }
 137: 
 138:     /**
 139:      * Constructs a new xy-series that contains no data.  You can specify
 140:      * whether or not duplicate x-values are allowed for the series.
 141:      *
 142:      * @param key  the series key (<code>null</code> not permitted).
 143:      * @param autoSort  a flag that controls whether or not the items in the
 144:      *                  series are sorted.
 145:      * @param allowDuplicateXValues  a flag that controls whether duplicate
 146:      *                               x-values are allowed.
 147:      */
 148:     public XYSeries(Comparable key,
 149:                     boolean autoSort,
 150:                     boolean allowDuplicateXValues) {
 151:         super(key);
 152:         this.data = new java.util.ArrayList();
 153:         this.autoSort = autoSort;
 154:         this.allowDuplicateXValues = allowDuplicateXValues;
 155:     }
 156: 
 157:     /**
 158:      * Returns the flag that controls whether the items in the series are
 159:      * automatically sorted.  There is no setter for this flag, it must be
 160:      * defined in the series constructor.
 161:      *
 162:      * @return A boolean.
 163:      */
 164:     public boolean getAutoSort() {
 165:         return this.autoSort;
 166:     }
 167: 
 168:     /**
 169:      * Returns a flag that controls whether duplicate x-values are allowed.
 170:      * This flag can only be set in the constructor.
 171:      *
 172:      * @return A boolean.
 173:      */
 174:     public boolean getAllowDuplicateXValues() {
 175:         return this.allowDuplicateXValues;
 176:     }
 177: 
 178:     /**
 179:      * Returns the number of items in the series.
 180:      *
 181:      * @return The item count.
 182:      */
 183:     public int getItemCount() {
 184:         return this.data.size();
 185:     }
 186: 
 187:     /**
 188:      * Returns the list of data items for the series (the list contains
 189:      * {@link XYDataItem} objects and is unmodifiable).
 190:      *
 191:      * @return The list of data items.
 192:      */
 193:     public List getItems() {
 194:         return Collections.unmodifiableList(this.data);
 195:     }
 196: 
 197:     /**
 198:      * Returns the maximum number of items that will be retained in the series.
 199:      * The default value is <code>Integer.MAX_VALUE</code>.
 200:      *
 201:      * @return The maximum item count.
 202:      * @see #setMaximumItemCount(int)
 203:      */
 204:     public int getMaximumItemCount() {
 205:         return this.maximumItemCount;
 206:     }
 207: 
 208:     /**
 209:      * Sets the maximum number of items that will be retained in the series.
 210:      * If you add a new item to the series such that the number of items will
 211:      * exceed the maximum item count, then the first element in the series is
 212:      * automatically removed, ensuring that the maximum item count is not
 213:      * exceeded.
 214:      * <p>
 215:      * Typically this value is set before the series is populated with data,
 216:      * but if it is applied later, it may cause some items to be removed from
 217:      * the series (in which case a {@link SeriesChangeEvent} will be sent to
 218:      * all registered listeners.
 219:      *
 220:      * @param maximum  the maximum number of items for the series.
 221:      */
 222:     public void setMaximumItemCount(int maximum) {
 223:         this.maximumItemCount = maximum;
 224:         boolean dataRemoved = false;
 225:         while (this.data.size() > maximum) {
 226:             this.data.remove(0);
 227:             dataRemoved = true;
 228:         }
 229:         if (dataRemoved) {
 230:             fireSeriesChanged();
 231:         }
 232:     }
 233: 
 234:     /**
 235:      * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
 236:      * all registered listeners.
 237:      *
 238:      * @param item  the (x, y) item (<code>null</code> not permitted).
 239:      */
 240:     public void add(XYDataItem item) {
 241:         // argument checking delegated...
 242:         add(item, true);
 243:     }
 244: 
 245:     /**
 246:      * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
 247:      * all registered listeners.
 248:      *
 249:      * @param x  the x value.
 250:      * @param y  the y value.
 251:      */
 252:     public void add(double x, double y) {
 253:         add(new Double(x), new Double(y), true);
 254:     }
 255: 
 256:     /**
 257:      * Adds a data item to the series and, if requested, sends a
 258:      * {@link SeriesChangeEvent} to all registered listeners.
 259:      *
 260:      * @param x  the x value.
 261:      * @param y  the y value.
 262:      * @param notify  a flag that controls whether or not a
 263:      *                {@link SeriesChangeEvent} is sent to all registered
 264:      *                listeners.
 265:      */
 266:     public void add(double x, double y, boolean notify) {
 267:         add(new Double(x), new Double(y), notify);
 268:     }
 269: 
 270:     /**
 271:      * Adds a data item to the series and sends a {@link SeriesChangeEvent} to
 272:      * all registered listeners.  The unusual pairing of parameter types is to
 273:      * make it easier to add <code>null</code> y-values.
 274:      *
 275:      * @param x  the x value.
 276:      * @param y  the y value (<code>null</code> permitted).
 277:      */
 278:     public void add(double x, Number y) {
 279:         add(new Double(x), y);
 280:     }
 281: 
 282:     /**
 283:      * Adds a data item to the series and, if requested, sends a
 284:      * {@link SeriesChangeEvent} to all registered listeners.  The unusual
 285:      * pairing of parameter types is to make it easier to add null y-values.
 286:      *
 287:      * @param x  the x value.
 288:      * @param y  the y value (<code>null</code> permitted).
 289:      * @param notify  a flag that controls whether or not a
 290:      *                {@link SeriesChangeEvent} is sent to all registered
 291:      *                listeners.
 292:      */
 293:     public void add(double x, Number y, boolean notify) {
 294:         add(new Double(x), y, notify);
 295:     }
 296: 
 297:     /**
 298:      * Adds new data to the series and sends a {@link SeriesChangeEvent} to
 299:      * all registered listeners.
 300:      * <P>
 301:      * Throws an exception if the x-value is a duplicate AND the
 302:      * allowDuplicateXValues flag is false.
 303:      *
 304:      * @param x  the x-value (<code>null</code> not permitted).
 305:      * @param y  the y-value (<code>null</code> permitted).
 306:      */
 307:     public void add(Number x, Number y) {
 308:         // argument checking delegated...
 309:         add(x, y, true);
 310:     }
 311: 
 312:     /**
 313:      * Adds new data to the series and, if requested, sends a
 314:      * {@link SeriesChangeEvent} to all registered listeners.
 315:      * <P>
 316:      * Throws an exception if the x-value is a duplicate AND the
 317:      * allowDuplicateXValues flag is false.
 318:      *
 319:      * @param x  the x-value (<code>null</code> not permitted).
 320:      * @param y  the y-value (<code>null</code> permitted).
 321:      * @param notify  a flag the controls whether or not a
 322:      *                {@link SeriesChangeEvent} is sent to all registered
 323:      *                listeners.
 324:      */
 325:     public void add(Number x, Number y, boolean notify) {
 326:         // delegate argument checking to XYDataItem...
 327:         XYDataItem item = new XYDataItem(x, y);
 328:         add(item, notify);
 329:     }
 330: 
 331:     /**
 332:      * Adds a data item to the series and, if requested, sends a
 333:      * {@link SeriesChangeEvent} to all registered listeners.
 334:      *
 335:      * @param item  the (x, y) item (<code>null</code> not permitted).
 336:      * @param notify  a flag that controls whether or not a
 337:      *                {@link SeriesChangeEvent} is sent to all registered
 338:      *                listeners.
 339:      */
 340:     public void add(XYDataItem item, boolean notify) {
 341: 
 342:         if (item == null) {
 343:             throw new IllegalArgumentException("Null 'item' argument.");
 344:         }
 345: 
 346:         if (this.autoSort) {
 347:             int index = Collections.binarySearch(this.data, item);
 348:             if (index < 0) {
 349:                 this.data.add(-index - 1, item);
 350:             }
 351:             else {
 352:                 if (this.allowDuplicateXValues) {
 353:                     // need to make sure we are adding *after* any duplicates
 354:                     int size = this.data.size();
 355:                     while (index < size
 356:                            && item.compareTo(this.data.get(index)) == 0) {
 357:                         index++;
 358:                     }
 359:                     if (index < this.data.size()) {
 360:                         this.data.add(index, item);
 361:                     }
 362:                     else {
 363:                         this.data.add(item);
 364:                     }
 365:                 }
 366:                 else {
 367:                     throw new SeriesException("X-value already exists.");
 368:                 }
 369:             }
 370:         }
 371:         else {
 372:             if (!this.allowDuplicateXValues) {
 373:                 // can't allow duplicate values, so we need to check whether
 374:                 // there is an item with the given x-value already
 375:                 int index = indexOf(item.getX());
 376:                 if (index >= 0) {
 377:                     throw new SeriesException("X-value already exists.");
 378:                 }
 379:             }
 380:             this.data.add(item);
 381:         }
 382:         if (getItemCount() > this.maximumItemCount) {
 383:             this.data.remove(0);
 384:         }
 385:         if (notify) {
 386:             fireSeriesChanged();
 387:         }
 388:     }
 389: 
 390:     /**
 391:      * Deletes a range of items from the series and sends a
 392:      * {@link SeriesChangeEvent} to all registered listeners.
 393:      *
 394:      * @param start  the start index (zero-based).
 395:      * @param end  the end index (zero-based).
 396:      */
 397:     public void delete(int start, int end) {
 398:         for (int i = start; i <= end; i++) {
 399:             this.data.remove(start);
 400:         }
 401:         fireSeriesChanged();
 402:     }
 403: 
 404:     /**
 405:      * Removes the item at the specified index and sends a
 406:      * {@link SeriesChangeEvent} to all registered listeners.
 407:      *
 408:      * @param index  the index.
 409:      *
 410:      * @return The item removed.
 411:      */
 412:     public XYDataItem remove(int index) {
 413:         XYDataItem result = (XYDataItem) this.data.remove(index);
 414:         fireSeriesChanged();
 415:         return result;
 416:     }
 417: 
 418:     /**
 419:      * Removes the item with the specified x-value and sends a
 420:      * {@link SeriesChangeEvent} to all registered listeners.
 421:      *
 422:      * @param x  the x-value.
 423: 
 424:      * @return The item removed.
 425:      */
 426:     public XYDataItem remove(Number x) {
 427:         return remove(indexOf(x));
 428:     }
 429: 
 430:     /**
 431:      * Removes all data items from the series.
 432:      */
 433:     public void clear() {
 434:         if (this.data.size() > 0) {
 435:             this.data.clear();
 436:             fireSeriesChanged();
 437:         }
 438:     }
 439: 
 440:     /**
 441:      * Return the data item with the specified index.
 442:      *
 443:      * @param index  the index.
 444:      *
 445:      * @return The data item with the specified index.
 446:      */
 447:     public XYDataItem getDataItem(int index) {
 448:         return (XYDataItem) this.data.get(index);
 449:     }
 450: 
 451:     /**
 452:      * Returns the x-value at the specified index.
 453:      *
 454:      * @param index  the index (zero-based).
 455:      *
 456:      * @return The x-value (never <code>null</code>).
 457:      */
 458:     public Number getX(int index) {
 459:         return getDataItem(index).getX();
 460:     }
 461: 
 462:     /**
 463:      * Returns the y-value at the specified index.
 464:      *
 465:      * @param index  the index (zero-based).
 466:      *
 467:      * @return The y-value (possibly <code>null</code>).
 468:      */
 469:     public Number getY(int index) {
 470:         return getDataItem(index).getY();
 471:     }
 472: 
 473:     /**
 474:      * Updates the value of an item in the series and sends a
 475:      * {@link SeriesChangeEvent} to all registered listeners.
 476:      *
 477:      * @param index  the item (zero based index).
 478:      * @param y  the new value (<code>null</code> permitted).
 479:      *
 480:      * @deprecated Renamed {@link #updateByIndex(int, Number)} to avoid
 481:      *         confusion with the {@link #update(Number, Number)} method.
 482:      */
 483:     public void update(int index, Number y) {
 484:         XYDataItem item = getDataItem(index);
 485:         item.setY(y);
 486:         fireSeriesChanged();
 487:     }
 488: 
 489:     /**
 490:      * Updates the value of an item in the series and sends a
 491:      * {@link SeriesChangeEvent} to all registered listeners.
 492:      *
 493:      * @param index  the item (zero based index).
 494:      * @param y  the new value (<code>null</code> permitted).
 495:      *
 496:      * @since 1.0.1
 497:      */
 498:     public void updateByIndex(int index, Number y) {
 499:         update(index, y);
 500:     }
 501: 
 502:     /**
 503:      * Updates an item in the series.
 504:      *
 505:      * @param x  the x-value (<code>null</code> not permitted).
 506:      * @param y  the y-value (<code>null</code> permitted).
 507:      *
 508:      * @throws SeriesException if there is no existing item with the specified
 509:      *         x-value.
 510:      */
 511:     public void update(Number x, Number y) {
 512:         int index = indexOf(x);
 513:         if (index < 0) {
 514:             throw new SeriesException("No observation for x = " + x);
 515:         }
 516:         else {
 517:             XYDataItem item = getDataItem(index);
 518:             item.setY(y);
 519:             fireSeriesChanged();
 520:         }
 521:     }
 522: 
 523:     /**
 524:      * Adds or updates an item in the series and sends a
 525:      * {@link SeriesChangeEvent} to all registered listeners.
 526:      *
 527:      * @param x  the x-value.
 528:      * @param y  the y-value.
 529:      *
 530:      * @return The item that was overwritten, if any.
 531:      *
 532:      * @since 1.0.10
 533:      */
 534:     public XYDataItem addOrUpdate(double x, double y) {
 535:         return addOrUpdate(new Double(x), new Double(y));
 536:     }
 537: 
 538:     /**
 539:      * Adds or updates an item in the series and sends a
 540:      * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
 541:      * listeners.
 542:      *
 543:      * @param x  the x-value (<code>null</code> not permitted).
 544:      * @param y  the y-value (<code>null</code> permitted).
 545:      *
 546:      * @return A copy of the overwritten data item, or <code>null</code> if no
 547:      *         item was overwritten.
 548:      */
 549:     public XYDataItem addOrUpdate(Number x, Number y) {
 550:         if (x == null) {
 551:             throw new IllegalArgumentException("Null 'x' argument.");
 552:         }
 553:         XYDataItem overwritten = null;
 554:         int index = indexOf(x);
 555:         if (index >= 0 && !this.allowDuplicateXValues) {
 556:             XYDataItem existing = (XYDataItem) this.data.get(index);
 557:             try {
 558:                 overwritten = (XYDataItem) existing.clone();
 559:             }
 560:             catch (CloneNotSupportedException e) {
 561:                 throw new SeriesException("Couldn't clone XYDataItem!");
 562:             }
 563:             existing.setY(y);
 564:         }
 565:         else {
 566:             // if the series is sorted, the negative index is a result from
 567:             // Collections.binarySearch() and tells us where to insert the
 568:             // new item...otherwise it will be just -1 and we should just
 569:             // append the value to the list...
 570:             if (this.autoSort) {
 571:                 this.data.add(-index - 1, new XYDataItem(x, y));
 572:             }
 573:             else {
 574:                 this.data.add(new XYDataItem(x, y));
 575:             }
 576:             // check if this addition will exceed the maximum item count...
 577:             if (getItemCount() > this.maximumItemCount) {
 578:                 this.data.remove(0);
 579:             }
 580:         }
 581:         fireSeriesChanged();
 582:         return overwritten;
 583:     }
 584: 
 585:     /**
 586:      * Returns the index of the item with the specified x-value, or a negative
 587:      * index if the series does not contain an item with that x-value.  Be
 588:      * aware that for an unsorted series, the index is found by iterating
 589:      * through all items in the series.
 590:      *
 591:      * @param x  the x-value (<code>null</code> not permitted).
 592:      *
 593:      * @return The index.
 594:      */
 595:     public int indexOf(Number x) {
 596:         if (this.autoSort) {
 597:             return Collections.binarySearch(this.data, new XYDataItem(x, null));
 598:         }
 599:         else {
 600:             for (int i = 0; i < this.data.size(); i++) {
 601:                 XYDataItem item = (XYDataItem) this.data.get(i);
 602:                 if (item.getX().equals(x)) {
 603:                     return i;
 604:                 }
 605:             }
 606:             return -1;
 607:         }
 608:     }
 609: 
 610:     /**
 611:      * Returns a new array containing the x and y values from this series.
 612:      *
 613:      * @return A new array containing the x and y values from this series.
 614:      *
 615:      * @since 1.0.4
 616:      */
 617:     public double[][] toArray() {
 618:         int itemCount = getItemCount();
 619:         double[][] result = new double[2][itemCount];
 620:         for (int i = 0; i < itemCount; i++) {
 621:             result[0][i] = this.getX(i).doubleValue();
 622:             Number y = getY(i);
 623:             if (y != null) {
 624:                 result[1][i] = y.doubleValue();
 625:             }
 626:             else {
 627:                 result[1][i] = Double.NaN;
 628:             }
 629:         }
 630:         return result;
 631:     }
 632: 
 633:     /**
 634:      * Returns a clone of the series.
 635:      *
 636:      * @return A clone of the series.
 637:      *
 638:      * @throws CloneNotSupportedException if there is a cloning problem.
 639:      */
 640:     public Object clone() throws CloneNotSupportedException {
 641:         XYSeries clone = (XYSeries) super.clone();
 642:         clone.data = (List) ObjectUtilities.deepClone(this.data);
 643:         return clone;
 644:     }
 645: 
 646:     /**
 647:      * Creates a new series by copying a subset of the data in this time series.
 648:      *
 649:      * @param start  the index of the first item to copy.
 650:      * @param end  the index of the last item to copy.
 651:      *
 652:      * @return A series containing a copy of this series from start until end.
 653:      *
 654:      * @throws CloneNotSupportedException if there is a cloning problem.
 655:      */
 656:     public XYSeries createCopy(int start, int end)
 657:         throws CloneNotSupportedException {
 658: 
 659:         XYSeries copy = (XYSeries) super.clone();
 660:         copy.data = new java.util.ArrayList();
 661:         if (this.data.size() > 0) {
 662:             for (int index = start; index <= end; index++) {
 663:                 XYDataItem item = (XYDataItem) this.data.get(index);
 664:                 XYDataItem clone = (XYDataItem) item.clone();
 665:                 try {
 666:                     copy.add(clone);
 667:                 }
 668:                 catch (SeriesException e) {
 669:                     System.err.println("Unable to add cloned data item.");
 670:                 }
 671:             }
 672:         }
 673:         return copy;
 674: 
 675:     }
 676: 
 677:     /**
 678:      * Tests this series for equality with an arbitrary object.
 679:      *
 680:      * @param obj  the object to test against for equality
 681:      *             (<code>null</code> permitted).
 682:      *
 683:      * @return A boolean.
 684:      */
 685:     public boolean equals(Object obj) {
 686:         if (obj == this) {
 687:             return true;
 688:         }
 689:         if (!(obj instanceof XYSeries)) {
 690:             return false;
 691:         }
 692:         if (!super.equals(obj)) {
 693:             return false;
 694:         }
 695:         XYSeries that = (XYSeries) obj;
 696:         if (this.maximumItemCount != that.maximumItemCount) {
 697:             return false;
 698:         }
 699:         if (this.autoSort != that.autoSort) {
 700:             return false;
 701:         }
 702:         if (this.allowDuplicateXValues != that.allowDuplicateXValues) {
 703:             return false;
 704:         }
 705:         if (!ObjectUtilities.equal(this.data, that.data)) {
 706:             return false;
 707:         }
 708:         return true;
 709:     }
 710: 
 711:     /**
 712:      * Returns a hash code.
 713:      *
 714:      * @return A hash code.
 715:      */
 716:     public int hashCode() {
 717:         int result = super.hashCode();
 718:         // it is too slow to look at every data item, so let's just look at
 719:         // the first, middle and last items...
 720:         int count = getItemCount();
 721:         if (count > 0) {
 722:             XYDataItem item = getDataItem(0);
 723:             result = 29 * result + item.hashCode();
 724:         }
 725:         if (count > 1) {
 726:             XYDataItem item = getDataItem(count - 1);
 727:             result = 29 * result + item.hashCode();
 728:         }
 729:         if (count > 2) {
 730:             XYDataItem item = getDataItem(count / 2);
 731:             result = 29 * result + item.hashCode();
 732:         }
 733:         result = 29 * result + this.maximumItemCount;
 734:         result = 29 * result + (this.autoSort ? 1 : 0);
 735:         result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
 736:         return result;
 737:     }
 738: 
 739: }
 740: