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