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