001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------- 028 * XYSeries.java 029 * ------------- 030 * (C) Copyright 2001-2009, Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Aaron Metzger; 034 * Jonathan Gabbai; 035 * Richard Atkinson; 036 * Michel Santos; 037 * Ted Schwartz (fix for bug 1955483); 038 * 039 * Changes 040 * ------- 041 * 15-Nov-2001 : Version 1 (DG); 042 * 03-Apr-2002 : Added an add(double, double) method (DG); 043 * 29-Apr-2002 : Added a clear() method (ARM); 044 * 06-Jun-2002 : Updated Javadoc comments (DG); 045 * 29-Aug-2002 : Modified to give user control over whether or not duplicate 046 * x-values are allowed (DG); 047 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 048 * 11-Nov-2002 : Added maximum item count, code contributed by Jonathan 049 * Gabbai (DG); 050 * 26-Mar-2003 : Implemented Serializable (DG); 051 * 04-Aug-2003 : Added getItems() method (DG); 052 * 15-Aug-2003 : Changed 'data' from private to protected, added new add() 053 * methods with a 'notify' argument (DG); 054 * 22-Sep-2003 : Added getAllowDuplicateXValues() method (RA); 055 * 29-Jan-2004 : Added autoSort attribute, based on a contribution by 056 * Michel Santos - see patch 886740 (DG); 057 * 03-Feb-2004 : Added indexOf() method (DG); 058 * 16-Feb-2004 : Added remove() method (DG); 059 * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG); 060 * 21-Feb-2005 : Added update(Number, Number) and addOrUpdate(Number, Number) 061 * methods (DG); 062 * 03-May-2005 : Added a new constructor, fixed the setMaximumItemCount() 063 * method to remove items (and notify listeners) if necessary, 064 * fixed the add() and addOrUpdate() methods to handle unsorted 065 * series (DG); 066 * ------------- JFreeChart 1.0.x --------------------------------------------- 067 * 11-Jan-2005 : Renamed update(int, Number) --> updateByIndex() (DG); 068 * 15-Jan-2007 : Added toArray() method (DG); 069 * 31-Oct-2007 : Implemented faster hashCode() (DG); 070 * 22-Nov-2007 : Reimplemented clone() (DG); 071 * 01-May-2008 : Fixed bug 1955483 in addOrUpdate() method, thanks to 072 * Ted Schwartz (DG); 073 * 24-Nov-2008 : Further fix for 1955483 (DG); 074 * 06-Mar-2009 : Added minX, maxX, minY and maxY fields (DG); 075 * 076 */ 077 078 package org.jfree.data.xy; 079 080 import java.io.Serializable; 081 import java.util.Collections; 082 import java.util.Iterator; 083 import java.util.List; 084 085 import org.jfree.data.general.Series; 086 import org.jfree.data.general.SeriesChangeEvent; 087 import org.jfree.data.general.SeriesException; 088 import org.jfree.util.ObjectUtilities; 089 090 /** 091 * Represents a sequence of zero or more data items in the form (x, y). By 092 * default, items in the series will be sorted into ascending order by x-value, 093 * and duplicate x-values are permitted. Both the sorting and duplicate 094 * defaults can be changed in the constructor. Y-values can be 095 * <code>null</code> to represent missing values. 096 */ 097 public class XYSeries extends Series implements Cloneable, Serializable { 098 099 /** For serialization. */ 100 static final long serialVersionUID = -5908509288197150436L; 101 102 // In version 0.9.12, in response to several developer requests, I changed 103 // the 'data' attribute from 'private' to 'protected', so that others can 104 // make subclasses that work directly with the underlying data structure. 105 106 /** Storage for the data items in the series. */ 107 protected List data; 108 109 /** The maximum number of items for the series. */ 110 private int maximumItemCount = Integer.MAX_VALUE; 111 112 /** 113 * A flag that controls whether the items are automatically sorted 114 * (by x-value ascending). 115 */ 116 private boolean autoSort; 117 118 /** A flag that controls whether or not duplicate x-values are allowed. */ 119 private boolean allowDuplicateXValues; 120 121 /** The lowest x-value in the series, excluding Double.NaN values. */ 122 private double minX; 123 124 /** The highest x-value in the series, excluding Double.NaN values. */ 125 private double maxX; 126 127 /** The lowest y-value in the series, excluding Double.NaN values. */ 128 private double minY; 129 130 /** The highest y-value in the series, excluding Double.NaN values. */ 131 private double maxY; 132 133 /** 134 * Creates a new empty series. By default, items added to the series will 135 * be sorted into ascending order by x-value, and duplicate x-values will 136 * be allowed (these defaults can be modified with another constructor. 137 * 138 * @param key the series key (<code>null</code> not permitted). 139 */ 140 public XYSeries(Comparable key) { 141 this(key, true, true); 142 } 143 144 /** 145 * Constructs a new empty series, with the auto-sort flag set as requested, 146 * and duplicate values allowed. 147 * 148 * @param key the series key (<code>null</code> not permitted). 149 * @param autoSort a flag that controls whether or not the items in the 150 * series are sorted. 151 */ 152 public XYSeries(Comparable key, boolean autoSort) { 153 this(key, autoSort, true); 154 } 155 156 /** 157 * Constructs a new xy-series that contains no data. You can specify 158 * whether or not duplicate x-values are allowed for the series. 159 * 160 * @param key the series key (<code>null</code> not permitted). 161 * @param autoSort a flag that controls whether or not the items in the 162 * series are sorted. 163 * @param allowDuplicateXValues a flag that controls whether duplicate 164 * x-values are allowed. 165 */ 166 public XYSeries(Comparable key, boolean autoSort, 167 boolean allowDuplicateXValues) { 168 super(key); 169 this.data = new java.util.ArrayList(); 170 this.autoSort = autoSort; 171 this.allowDuplicateXValues = allowDuplicateXValues; 172 this.minX = Double.NaN; 173 this.maxX = Double.NaN; 174 this.minY = Double.NaN; 175 this.maxY = Double.NaN; 176 } 177 178 /** 179 * Returns the smallest x-value in the series, ignoring any Double.NaN 180 * values. This method returns Double.NaN if there is no smallest x-value 181 * (for example, when the series is empty). 182 * 183 * @return The smallest x-value. 184 * 185 * @see #getMaxX() 186 * 187 * @since 1.0.13 188 */ 189 public double getMinX() { 190 return this.minX; 191 } 192 193 /** 194 * Returns the largest x-value in the series, ignoring any Double.NaN 195 * values. This method returns Double.NaN if there is no largest x-value 196 * (for example, when the series is empty). 197 * 198 * @return The largest x-value. 199 * 200 * @see #getMinX() 201 * 202 * @since 1.0.13 203 */ 204 public double getMaxX() { 205 return this.maxX; 206 } 207 208 /** 209 * Returns the smallest y-value in the series, ignoring any null and 210 * Double.NaN values. This method returns Double.NaN if there is no 211 * smallest y-value (for example, when the series is empty). 212 * 213 * @return The smallest y-value. 214 * 215 * @see #getMaxY() 216 * 217 * @since 1.0.13 218 */ 219 public double getMinY() { 220 return this.minY; 221 } 222 223 /** 224 * Returns the largest y-value in the series, ignoring any Double.NaN 225 * values. This method returns Double.NaN if there is no largest y-value 226 * (for example, when the series is empty). 227 * 228 * @return The largest y-value. 229 * 230 * @see #getMinY() 231 * 232 * @since 1.0.13 233 */ 234 public double getMaxY() { 235 return this.maxY; 236 } 237 238 /** 239 * Updates the cached values for the minimum and maximum data values. 240 * 241 * @param item the item added (<code>null</code> not permitted). 242 * 243 * @since 1.0.13 244 */ 245 private void updateBoundsForAddedItem(XYDataItem item) { 246 double x = item.getXValue(); 247 this.minX = minIgnoreNaN(this.minX, x); 248 this.maxX = maxIgnoreNaN(this.maxX, x); 249 if (item.getY() != null) { 250 double y = item.getYValue(); 251 this.minY = minIgnoreNaN(this.minY, y); 252 this.maxY = maxIgnoreNaN(this.maxY, y); 253 } 254 } 255 256 /** 257 * Updates the cached values for the minimum and maximum data values on 258 * the basis that the specified item has just been removed. 259 * 260 * @param item the item added (<code>null</code> not permitted). 261 * 262 * @since 1.0.13 263 */ 264 private void updateBoundsForRemovedItem(XYDataItem item) { 265 boolean itemContributesToXBounds = false; 266 boolean itemContributesToYBounds = false; 267 double x = item.getXValue(); 268 if (!Double.isNaN(x)) { 269 if (x <= this.minX || x >= this.maxX) { 270 itemContributesToXBounds = true; 271 } 272 } 273 if (item.getY() != null) { 274 double y = item.getYValue(); 275 if (!Double.isNaN(y)) { 276 if (y <= this.minY || y >= this.maxY) { 277 itemContributesToYBounds = true; 278 } 279 } 280 } 281 if (itemContributesToYBounds) { 282 findBoundsByIteration(); 283 } 284 else if (itemContributesToXBounds) { 285 if (getAutoSort()) { 286 this.minX = getX(0).doubleValue(); 287 this.maxX = getX(getItemCount() - 1).doubleValue(); 288 } 289 else { 290 findBoundsByIteration(); 291 } 292 } 293 } 294 295 /** 296 * Finds the bounds of the x and y values for the series, by iterating 297 * through all the data items. 298 * 299 * @since 1.0.13 300 */ 301 private void findBoundsByIteration() { 302 this.minX = Double.NaN; 303 this.maxX = Double.NaN; 304 this.minY = Double.NaN; 305 this.maxY = Double.NaN; 306 Iterator iterator = this.data.iterator(); 307 while (iterator.hasNext()) { 308 XYDataItem item = (XYDataItem) iterator.next(); 309 updateBoundsForAddedItem(item); 310 } 311 } 312 313 /** 314 * Returns the flag that controls whether the items in the series are 315 * automatically sorted. There is no setter for this flag, it must be 316 * defined in the series constructor. 317 * 318 * @return A boolean. 319 */ 320 public boolean getAutoSort() { 321 return this.autoSort; 322 } 323 324 /** 325 * Returns a flag that controls whether duplicate x-values are allowed. 326 * This flag can only be set in the constructor. 327 * 328 * @return A boolean. 329 */ 330 public boolean getAllowDuplicateXValues() { 331 return this.allowDuplicateXValues; 332 } 333 334 /** 335 * Returns the number of items in the series. 336 * 337 * @return The item count. 338 * 339 * @see #getItems() 340 */ 341 public int getItemCount() { 342 return this.data.size(); 343 } 344 345 /** 346 * Returns the list of data items for the series (the list contains 347 * {@link XYDataItem} objects and is unmodifiable). 348 * 349 * @return The list of data items. 350 */ 351 public List getItems() { 352 return Collections.unmodifiableList(this.data); 353 } 354 355 /** 356 * Returns the maximum number of items that will be retained in the series. 357 * The default value is <code>Integer.MAX_VALUE</code>. 358 * 359 * @return The maximum item count. 360 * 361 * @see #setMaximumItemCount(int) 362 */ 363 public int getMaximumItemCount() { 364 return this.maximumItemCount; 365 } 366 367 /** 368 * Sets the maximum number of items that will be retained in the series. 369 * If you add a new item to the series such that the number of items will 370 * exceed the maximum item count, then the first element in the series is 371 * automatically removed, ensuring that the maximum item count is not 372 * exceeded. 373 * <p> 374 * Typically this value is set before the series is populated with data, 375 * but if it is applied later, it may cause some items to be removed from 376 * the series (in which case a {@link SeriesChangeEvent} will be sent to 377 * all registered listeners). 378 * 379 * @param maximum the maximum number of items for the series. 380 */ 381 public void setMaximumItemCount(int maximum) { 382 this.maximumItemCount = maximum; 383 int remove = this.data.size() - maximum; 384 if (remove > 0) { 385 this.data.subList(0, remove).clear(); 386 findBoundsByIteration(); 387 fireSeriesChanged(); 388 } 389 } 390 391 /** 392 * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 393 * all registered listeners. 394 * 395 * @param item the (x, y) item (<code>null</code> not permitted). 396 */ 397 public void add(XYDataItem item) { 398 // argument checking delegated... 399 add(item, true); 400 } 401 402 /** 403 * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 404 * all registered listeners. 405 * 406 * @param x the x value. 407 * @param y the y value. 408 */ 409 public void add(double x, double y) { 410 add(new Double(x), new Double(y), true); 411 } 412 413 /** 414 * Adds a data item to the series and, if requested, sends a 415 * {@link SeriesChangeEvent} to all registered listeners. 416 * 417 * @param x the x value. 418 * @param y the y value. 419 * @param notify a flag that controls whether or not a 420 * {@link SeriesChangeEvent} is sent to all registered 421 * listeners. 422 */ 423 public void add(double x, double y, boolean notify) { 424 add(new Double(x), new Double(y), notify); 425 } 426 427 /** 428 * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 429 * all registered listeners. The unusual pairing of parameter types is to 430 * make it easier to add <code>null</code> y-values. 431 * 432 * @param x the x value. 433 * @param y the y value (<code>null</code> permitted). 434 */ 435 public void add(double x, Number y) { 436 add(new Double(x), y); 437 } 438 439 /** 440 * Adds a data item to the series and, if requested, sends a 441 * {@link SeriesChangeEvent} to all registered listeners. The unusual 442 * pairing of parameter types is to make it easier to add null y-values. 443 * 444 * @param x the x value. 445 * @param y the y value (<code>null</code> permitted). 446 * @param notify a flag that controls whether or not a 447 * {@link SeriesChangeEvent} is sent to all registered 448 * listeners. 449 */ 450 public void add(double x, Number y, boolean notify) { 451 add(new Double(x), y, notify); 452 } 453 454 /** 455 * Adds a new data item to the series (in the correct position if the 456 * <code>autoSort</code> flag is set for the series) and sends a 457 * {@link SeriesChangeEvent} to all registered listeners. 458 * <P> 459 * Throws an exception if the x-value is a duplicate AND the 460 * allowDuplicateXValues flag is false. 461 * 462 * @param x the x-value (<code>null</code> not permitted). 463 * @param y the y-value (<code>null</code> permitted). 464 * 465 * @throws SeriesException if the x-value is a duplicate and the 466 * <code>allowDuplicateXValues</code> flag is not set for this series. 467 */ 468 public void add(Number x, Number y) { 469 // argument checking delegated... 470 add(x, y, true); 471 } 472 473 /** 474 * Adds new data to the series and, if requested, sends a 475 * {@link SeriesChangeEvent} to all registered listeners. 476 * <P> 477 * Throws an exception if the x-value is a duplicate AND the 478 * allowDuplicateXValues flag is false. 479 * 480 * @param x the x-value (<code>null</code> not permitted). 481 * @param y the y-value (<code>null</code> permitted). 482 * @param notify a flag the controls whether or not a 483 * {@link SeriesChangeEvent} is sent to all registered 484 * listeners. 485 */ 486 public void add(Number x, Number y, boolean notify) { 487 // delegate argument checking to XYDataItem... 488 XYDataItem item = new XYDataItem(x, y); 489 add(item, notify); 490 } 491 492 /** 493 * Adds a data item to the series and, if requested, sends a 494 * {@link SeriesChangeEvent} to all registered listeners. 495 * 496 * @param item the (x, y) item (<code>null</code> not permitted). 497 * @param notify a flag that controls whether or not a 498 * {@link SeriesChangeEvent} is sent to all registered 499 * listeners. 500 */ 501 public void add(XYDataItem item, boolean notify) { 502 if (item == null) { 503 throw new IllegalArgumentException("Null 'item' argument."); 504 } 505 if (this.autoSort) { 506 int index = Collections.binarySearch(this.data, item); 507 if (index < 0) { 508 this.data.add(-index - 1, item); 509 } 510 else { 511 if (this.allowDuplicateXValues) { 512 // need to make sure we are adding *after* any duplicates 513 int size = this.data.size(); 514 while (index < size && item.compareTo( 515 this.data.get(index)) == 0) { 516 index++; 517 } 518 if (index < this.data.size()) { 519 this.data.add(index, item); 520 } 521 else { 522 this.data.add(item); 523 } 524 } 525 else { 526 throw new SeriesException("X-value already exists."); 527 } 528 } 529 } 530 else { 531 if (!this.allowDuplicateXValues) { 532 // can't allow duplicate values, so we need to check whether 533 // there is an item with the given x-value already 534 int index = indexOf(item.getX()); 535 if (index >= 0) { 536 throw new SeriesException("X-value already exists."); 537 } 538 } 539 this.data.add(item); 540 } 541 updateBoundsForAddedItem(item); 542 if (getItemCount() > this.maximumItemCount) { 543 XYDataItem removed = (XYDataItem) this.data.remove(0); 544 updateBoundsForRemovedItem(removed); 545 } 546 if (notify) { 547 fireSeriesChanged(); 548 } 549 } 550 551 /** 552 * Deletes a range of items from the series and sends a 553 * {@link SeriesChangeEvent} to all registered listeners. 554 * 555 * @param start the start index (zero-based). 556 * @param end the end index (zero-based). 557 */ 558 public void delete(int start, int end) { 559 this.data.subList(start, end + 1).clear(); 560 findBoundsByIteration(); 561 fireSeriesChanged(); 562 } 563 564 /** 565 * Removes the item at the specified index and sends a 566 * {@link SeriesChangeEvent} to all registered listeners. 567 * 568 * @param index the index. 569 * 570 * @return The item removed. 571 */ 572 public XYDataItem remove(int index) { 573 XYDataItem removed = (XYDataItem) this.data.remove(index); 574 updateBoundsForRemovedItem(removed); 575 fireSeriesChanged(); 576 return removed; 577 } 578 579 /** 580 * Removes an item with the specified x-value and sends a 581 * {@link SeriesChangeEvent} to all registered listeners. Note that when 582 * a series permits multiple items with the same x-value, this method 583 * could remove any one of the items with that x-value. 584 * 585 * @param x the x-value. 586 587 * @return The item removed. 588 */ 589 public XYDataItem remove(Number x) { 590 return remove(indexOf(x)); 591 } 592 593 /** 594 * Removes all data items from the series and sends a 595 * {@link SeriesChangeEvent} to all registered listeners. 596 */ 597 public void clear() { 598 if (this.data.size() > 0) { 599 this.data.clear(); 600 this.minX = Double.NaN; 601 this.maxX = Double.NaN; 602 this.minY = Double.NaN; 603 this.maxY = Double.NaN; 604 fireSeriesChanged(); 605 } 606 } 607 608 /** 609 * Return the data item with the specified index. 610 * 611 * @param index the index. 612 * 613 * @return The data item with the specified index. 614 */ 615 public XYDataItem getDataItem(int index) { 616 return (XYDataItem) this.data.get(index); 617 } 618 619 /** 620 * Returns the x-value at the specified index. 621 * 622 * @param index the index (zero-based). 623 * 624 * @return The x-value (never <code>null</code>). 625 */ 626 public Number getX(int index) { 627 return getDataItem(index).getX(); 628 } 629 630 /** 631 * Returns the y-value at the specified index. 632 * 633 * @param index the index (zero-based). 634 * 635 * @return The y-value (possibly <code>null</code>). 636 */ 637 public Number getY(int index) { 638 return getDataItem(index).getY(); 639 } 640 641 /** 642 * Updates the value of an item in the series and sends a 643 * {@link SeriesChangeEvent} to all registered listeners. 644 * 645 * @param index the item (zero based index). 646 * @param y the new value (<code>null</code> permitted). 647 * 648 * @deprecated Renamed {@link #updateByIndex(int, Number)} to avoid 649 * confusion with the {@link #update(Number, Number)} method. 650 */ 651 public void update(int index, Number y) { 652 XYDataItem item = getDataItem(index); 653 654 // figure out if we need to iterate through all the y-values 655 boolean iterate = false; 656 double oldY = item.getYValue(); 657 if (!Double.isNaN(oldY)) { 658 iterate = oldY <= this.minY || oldY >= this.maxY; 659 } 660 item.setY(y); 661 662 if (iterate) { 663 findBoundsByIteration(); 664 } 665 else if (y != null) { 666 double yy = y.doubleValue(); 667 this.minY = minIgnoreNaN(this.minY, yy); 668 this.maxY = maxIgnoreNaN(this.maxY, yy); 669 } 670 fireSeriesChanged(); 671 } 672 673 /** 674 * A function to find the minimum of two values, but ignoring any 675 * Double.NaN values. 676 * 677 * @param a the first value. 678 * @param b the second value. 679 * 680 * @return The minimum of the two values. 681 */ 682 private double minIgnoreNaN(double a, double b) { 683 if (Double.isNaN(a)) { 684 return b; 685 } 686 else { 687 if (Double.isNaN(b)) { 688 return a; 689 } 690 else { 691 return Math.min(a, b); 692 } 693 } 694 } 695 696 /** 697 * A function to find the maximum of two values, but ignoring any 698 * Double.NaN values. 699 * 700 * @param a the first value. 701 * @param b the second value. 702 * 703 * @return The maximum of the two values. 704 */ 705 private double maxIgnoreNaN(double a, double b) { 706 if (Double.isNaN(a)) { 707 return b; 708 } 709 else { 710 if (Double.isNaN(b)) { 711 return a; 712 } 713 else { 714 return Math.max(a, b); 715 } 716 } 717 } 718 719 /** 720 * Updates the value of an item in the series and sends a 721 * {@link SeriesChangeEvent} to all registered listeners. 722 * 723 * @param index the item (zero based index). 724 * @param y the new value (<code>null</code> permitted). 725 * 726 * @since 1.0.1 727 */ 728 public void updateByIndex(int index, Number y) { 729 update(index, y); 730 } 731 732 /** 733 * Updates an item in the series. 734 * 735 * @param x the x-value (<code>null</code> not permitted). 736 * @param y the y-value (<code>null</code> permitted). 737 * 738 * @throws SeriesException if there is no existing item with the specified 739 * x-value. 740 */ 741 public void update(Number x, Number y) { 742 int index = indexOf(x); 743 if (index < 0) { 744 throw new SeriesException("No observation for x = " + x); 745 } 746 else { 747 updateByIndex(index, y); 748 } 749 } 750 751 /** 752 * Adds or updates an item in the series and sends a 753 * {@link SeriesChangeEvent} to all registered listeners. 754 * 755 * @param x the x-value. 756 * @param y the y-value. 757 * 758 * @return The item that was overwritten, if any. 759 * 760 * @since 1.0.10 761 */ 762 public XYDataItem addOrUpdate(double x, double y) { 763 return addOrUpdate(new Double(x), new Double(y)); 764 } 765 766 /** 767 * Adds or updates an item in the series and sends a 768 * {@link SeriesChangeEvent} to all registered listeners. 769 * 770 * @param x the x-value (<code>null</code> not permitted). 771 * @param y the y-value (<code>null</code> permitted). 772 * 773 * @return A copy of the overwritten data item, or <code>null</code> if no 774 * item was overwritten. 775 */ 776 public XYDataItem addOrUpdate(Number x, Number y) { 777 if (x == null) { 778 throw new IllegalArgumentException("Null 'x' argument."); 779 } 780 if (this.allowDuplicateXValues) { 781 add(x, y); 782 return null; 783 } 784 785 // if we get to here, we know that duplicate X values are not permitted 786 XYDataItem overwritten = null; 787 int index = indexOf(x); 788 if (index >= 0) { 789 XYDataItem existing = (XYDataItem) this.data.get(index); 790 try { 791 overwritten = (XYDataItem) existing.clone(); 792 } 793 catch (CloneNotSupportedException e) { 794 throw new SeriesException("Couldn't clone XYDataItem!"); 795 } 796 // figure out if we need to iterate through all the y-values 797 boolean iterate = false; 798 double oldY = existing.getYValue(); 799 if (!Double.isNaN(oldY)) { 800 iterate = oldY <= this.minY || oldY >= this.maxY; 801 } 802 existing.setY(y); 803 804 if (iterate) { 805 findBoundsByIteration(); 806 } 807 else if (y != null) { 808 double yy = y.doubleValue(); 809 this.minY = minIgnoreNaN(this.minY, yy); 810 this.maxY = minIgnoreNaN(this.maxY, yy); 811 } 812 } 813 else { 814 // if the series is sorted, the negative index is a result from 815 // Collections.binarySearch() and tells us where to insert the 816 // new item...otherwise it will be just -1 and we should just 817 // append the value to the list... 818 XYDataItem item = new XYDataItem(x, y); 819 if (this.autoSort) { 820 this.data.add(-index - 1, item); 821 } 822 else { 823 this.data.add(item); 824 } 825 updateBoundsForAddedItem(item); 826 827 // check if this addition will exceed the maximum item count... 828 if (getItemCount() > this.maximumItemCount) { 829 XYDataItem removed = (XYDataItem) this.data.remove(0); 830 updateBoundsForRemovedItem(removed); 831 } 832 } 833 fireSeriesChanged(); 834 return overwritten; 835 } 836 837 /** 838 * Returns the index of the item with the specified x-value, or a negative 839 * index if the series does not contain an item with that x-value. Be 840 * aware that for an unsorted series, the index is found by iterating 841 * through all items in the series. 842 * 843 * @param x the x-value (<code>null</code> not permitted). 844 * 845 * @return The index. 846 */ 847 public int indexOf(Number x) { 848 if (this.autoSort) { 849 return Collections.binarySearch(this.data, new XYDataItem(x, null)); 850 } 851 else { 852 for (int i = 0; i < this.data.size(); i++) { 853 XYDataItem item = (XYDataItem) this.data.get(i); 854 if (item.getX().equals(x)) { 855 return i; 856 } 857 } 858 return -1; 859 } 860 } 861 862 /** 863 * Returns a new array containing the x and y values from this series. 864 * 865 * @return A new array containing the x and y values from this series. 866 * 867 * @since 1.0.4 868 */ 869 public double[][] toArray() { 870 int itemCount = getItemCount(); 871 double[][] result = new double[2][itemCount]; 872 for (int i = 0; i < itemCount; i++) { 873 result[0][i] = this.getX(i).doubleValue(); 874 Number y = getY(i); 875 if (y != null) { 876 result[1][i] = y.doubleValue(); 877 } 878 else { 879 result[1][i] = Double.NaN; 880 } 881 } 882 return result; 883 } 884 885 /** 886 * Returns a clone of the series. 887 * 888 * @return A clone of the series. 889 * 890 * @throws CloneNotSupportedException if there is a cloning problem. 891 */ 892 public Object clone() throws CloneNotSupportedException { 893 XYSeries clone = (XYSeries) super.clone(); 894 clone.data = (List) ObjectUtilities.deepClone(this.data); 895 return clone; 896 } 897 898 /** 899 * Creates a new series by copying a subset of the data in this time series. 900 * 901 * @param start the index of the first item to copy. 902 * @param end the index of the last item to copy. 903 * 904 * @return A series containing a copy of this series from start until end. 905 * 906 * @throws CloneNotSupportedException if there is a cloning problem. 907 */ 908 public XYSeries createCopy(int start, int end) 909 throws CloneNotSupportedException { 910 911 XYSeries copy = (XYSeries) super.clone(); 912 copy.data = new java.util.ArrayList(); 913 if (this.data.size() > 0) { 914 for (int index = start; index <= end; index++) { 915 XYDataItem item = (XYDataItem) this.data.get(index); 916 XYDataItem clone = (XYDataItem) item.clone(); 917 try { 918 copy.add(clone); 919 } 920 catch (SeriesException e) { 921 System.err.println("Unable to add cloned data item."); 922 } 923 } 924 } 925 return copy; 926 927 } 928 929 /** 930 * Tests this series for equality with an arbitrary object. 931 * 932 * @param obj the object to test against for equality 933 * (<code>null</code> permitted). 934 * 935 * @return A boolean. 936 */ 937 public boolean equals(Object obj) { 938 if (obj == this) { 939 return true; 940 } 941 if (!(obj instanceof XYSeries)) { 942 return false; 943 } 944 if (!super.equals(obj)) { 945 return false; 946 } 947 XYSeries that = (XYSeries) obj; 948 if (this.maximumItemCount != that.maximumItemCount) { 949 return false; 950 } 951 if (this.autoSort != that.autoSort) { 952 return false; 953 } 954 if (this.allowDuplicateXValues != that.allowDuplicateXValues) { 955 return false; 956 } 957 if (!ObjectUtilities.equal(this.data, that.data)) { 958 return false; 959 } 960 return true; 961 } 962 963 /** 964 * Returns a hash code. 965 * 966 * @return A hash code. 967 */ 968 public int hashCode() { 969 int result = super.hashCode(); 970 // it is too slow to look at every data item, so let's just look at 971 // the first, middle and last items... 972 int count = getItemCount(); 973 if (count > 0) { 974 XYDataItem item = getDataItem(0); 975 result = 29 * result + item.hashCode(); 976 } 977 if (count > 1) { 978 XYDataItem item = getDataItem(count - 1); 979 result = 29 * result + item.hashCode(); 980 } 981 if (count > 2) { 982 XYDataItem item = getDataItem(count / 2); 983 result = 29 * result + item.hashCode(); 984 } 985 result = 29 * result + this.maximumItemCount; 986 result = 29 * result + (this.autoSort ? 1 : 0); 987 result = 29 * result + (this.allowDuplicateXValues ? 1 : 0); 988 return result; 989 } 990 991 } 992