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