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