Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ------------- 28: * XYSeries.java 29: * ------------- 30: * (C) Copyright 2001-2007, Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Aaron Metzger; 34: * Jonathan Gabbai; 35: * Richard Atkinson; 36: * Michel Santos; 37: * 38: * $Id: XYSeries.java,v 1.13.2.4 2007/01/15 09:54:11 mungady Exp $ 39: * 40: * Changes 41: * ------- 42: * 15-Nov-2001 : Version 1 (DG); 43: * 03-Apr-2002 : Added an add(double, double) method (DG); 44: * 29-Apr-2002 : Added a clear() method (ARM); 45: * 06-Jun-2002 : Updated Javadoc comments (DG); 46: * 29-Aug-2002 : Modified to give user control over whether or not duplicate 47: * x-values are allowed (DG); 48: * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 49: * 11-Nov-2002 : Added maximum item count, code contributed by Jonathan 50: * Gabbai (DG); 51: * 26-Mar-2003 : Implemented Serializable (DG); 52: * 04-Aug-2003 : Added getItems() method (DG); 53: * 15-Aug-2003 : Changed 'data' from private to protected, added new add() 54: * methods with a 'notify' argument (DG); 55: * 22-Sep-2003 : Added getAllowDuplicateXValues() method (RA); 56: * 29-Jan-2004 : Added autoSort attribute, based on a contribution by 57: * Michel Santos - see patch 886740 (DG); 58: * 03-Feb-2004 : Added indexOf() method (DG); 59: * 16-Feb-2004 : Added remove() method (DG); 60: * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG); 61: * 21-Feb-2005 : Added update(Number, Number) and addOrUpdate(Number, Number) 62: * methods (DG); 63: * 03-May-2005 : Added a new constructor, fixed the setMaximumItemCount() 64: * method to remove items (and notify listeners) if necessary, 65: * fixed the add() and addOrUpdate() methods to handle unsorted 66: * series (DG); 67: * ------------- JFreeChart 1.0.x --------------------------------------------- 68: * 11-Jan-2005 : Renamed update(int, Number) --> updateByIndex() (DG); 69: * 15-Jan-2007 : Added toArray() method (DG); 70: * 71: */ 72: 73: package org.jfree.data.xy; 74: 75: import java.io.Serializable; 76: import java.util.Collections; 77: import java.util.List; 78: 79: import org.jfree.data.general.Series; 80: import org.jfree.data.general.SeriesChangeEvent; 81: import org.jfree.data.general.SeriesException; 82: import org.jfree.util.ObjectUtilities; 83: 84: /** 85: * Represents a sequence of zero or more data items in the form (x, y). By 86: * default, items in the series will be sorted into ascending order by x-value, 87: * and duplicate x-values are permitted. Both the sorting and duplicate 88: * defaults can be changed in the constructor. Y-values can be 89: * <code>null</code> to represent missing values. 90: */ 91: public class XYSeries extends Series implements Cloneable, Serializable { 92: 93: /** For serialization. */ 94: static final long serialVersionUID = -5908509288197150436L; 95: 96: // In version 0.9.12, in response to several developer requests, I changed 97: // the 'data' attribute from 'private' to 'protected', so that others can 98: // make subclasses that work directly with the underlying data structure. 99: 100: /** Storage for the data items in the series. */ 101: protected List data; 102: 103: /** The maximum number of items for the series. */ 104: private int maximumItemCount = Integer.MAX_VALUE; 105: 106: /** A flag that controls whether the items are automatically sorted. */ 107: private boolean autoSort; 108: 109: /** A flag that controls whether or not duplicate x-values are allowed. */ 110: private boolean allowDuplicateXValues; 111: 112: /** 113: * Creates a new empty series. By default, items added to the series will 114: * be sorted into ascending order by x-value, and duplicate x-values will 115: * be allowed (these defaults can be modified with another constructor. 116: * 117: * @param key the series key (<code>null</code> not permitted). 118: */ 119: public XYSeries(Comparable key) { 120: this(key, true, true); 121: } 122: 123: /** 124: * Constructs a new empty series, with the auto-sort flag set as requested, 125: * and duplicate values allowed. 126: * 127: * @param key the series key (<code>null</code> not permitted). 128: * @param autoSort a flag that controls whether or not the items in the 129: * series are sorted. 130: */ 131: public XYSeries(Comparable key, boolean autoSort) { 132: this(key, autoSort, true); 133: } 134: 135: /** 136: * Constructs a new xy-series that contains no data. You can specify 137: * whether or not duplicate x-values are allowed for the series. 138: * 139: * @param key the series key (<code>null</code> not permitted). 140: * @param autoSort a flag that controls whether or not the items in the 141: * series are sorted. 142: * @param allowDuplicateXValues a flag that controls whether duplicate 143: * x-values are allowed. 144: */ 145: public XYSeries(Comparable key, 146: boolean autoSort, 147: boolean allowDuplicateXValues) { 148: super(key); 149: this.data = new java.util.ArrayList(); 150: this.autoSort = autoSort; 151: this.allowDuplicateXValues = allowDuplicateXValues; 152: } 153: 154: /** 155: * Returns the flag that controls whether the items in the series are 156: * automatically sorted. There is no setter for this flag, it must be 157: * defined in the series constructor. 158: * 159: * @return A boolean. 160: */ 161: public boolean getAutoSort() { 162: return this.autoSort; 163: } 164: 165: /** 166: * Returns a flag that controls whether duplicate x-values are allowed. 167: * This flag can only be set in the constructor. 168: * 169: * @return A boolean. 170: */ 171: public boolean getAllowDuplicateXValues() { 172: return this.allowDuplicateXValues; 173: } 174: 175: /** 176: * Returns the number of items in the series. 177: * 178: * @return The item count. 179: */ 180: public int getItemCount() { 181: return this.data.size(); 182: } 183: 184: /** 185: * Returns the list of data items for the series (the list contains 186: * {@link XYDataItem} objects and is unmodifiable). 187: * 188: * @return The list of data items. 189: */ 190: public List getItems() { 191: return Collections.unmodifiableList(this.data); 192: } 193: 194: /** 195: * Returns the maximum number of items that will be retained in the series. 196: * The default value is <code>Integer.MAX_VALUE</code>. 197: * 198: * @return The maximum item count. 199: * @see #setMaximumItemCount(int) 200: */ 201: public int getMaximumItemCount() { 202: return this.maximumItemCount; 203: } 204: 205: /** 206: * Sets the maximum number of items that will be retained in the series. 207: * If you add a new item to the series such that the number of items will 208: * exceed the maximum item count, then the first element in the series is 209: * automatically removed, ensuring that the maximum item count is not 210: * exceeded. 211: * <p> 212: * Typically this value is set before the series is populated with data, 213: * but if it is applied later, it may cause some items to be removed from 214: * the series (in which case a {@link SeriesChangeEvent} will be sent to 215: * all registered listeners. 216: * 217: * @param maximum the maximum number of items for the series. 218: */ 219: public void setMaximumItemCount(int maximum) { 220: this.maximumItemCount = maximum; 221: boolean dataRemoved = false; 222: while (this.data.size() > maximum) { 223: this.data.remove(0); 224: dataRemoved = true; 225: } 226: if (dataRemoved) { 227: fireSeriesChanged(); 228: } 229: } 230: 231: /** 232: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 233: * all registered listeners. 234: * 235: * @param item the (x, y) item (<code>null</code> not permitted). 236: */ 237: public void add(XYDataItem item) { 238: // argument checking delegated... 239: add(item, true); 240: } 241: 242: /** 243: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 244: * all registered listeners. 245: * 246: * @param x the x value. 247: * @param y the y value. 248: */ 249: public void add(double x, double y) { 250: add(new Double(x), new Double(y), true); 251: } 252: 253: /** 254: * Adds a data item to the series and, if requested, sends a 255: * {@link SeriesChangeEvent} to all registered listeners. 256: * 257: * @param x the x value. 258: * @param y the y value. 259: * @param notify a flag that controls whether or not a 260: * {@link SeriesChangeEvent} is sent to all registered 261: * listeners. 262: */ 263: public void add(double x, double y, boolean notify) { 264: add(new Double(x), new Double(y), notify); 265: } 266: 267: /** 268: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 269: * all registered listeners. The unusual pairing of parameter types is to 270: * make it easier to add <code>null</code> y-values. 271: * 272: * @param x the x value. 273: * @param y the y value (<code>null</code> permitted). 274: */ 275: public void add(double x, Number y) { 276: add(new Double(x), y); 277: } 278: 279: /** 280: * Adds a data item to the series and, if requested, sends a 281: * {@link SeriesChangeEvent} to all registered listeners. The unusual 282: * pairing of parameter types is to make it easier to add null y-values. 283: * 284: * @param x the x value. 285: * @param y the y value (<code>null</code> permitted). 286: * @param notify a flag that controls whether or not a 287: * {@link SeriesChangeEvent} is sent to all registered 288: * listeners. 289: */ 290: public void add(double x, Number y, boolean notify) { 291: add(new Double(x), y, notify); 292: } 293: 294: /** 295: * Adds new data to the series and sends a {@link SeriesChangeEvent} to 296: * all registered listeners. 297: * <P> 298: * Throws an exception if the x-value is a duplicate AND the 299: * allowDuplicateXValues flag is false. 300: * 301: * @param x the x-value (<code>null</code> not permitted). 302: * @param y the y-value (<code>null</code> permitted). 303: */ 304: public void add(Number x, Number y) { 305: // argument checking delegated... 306: add(x, y, true); 307: } 308: 309: /** 310: * Adds new data to the series and, if requested, sends a 311: * {@link SeriesChangeEvent} to all registered listeners. 312: * <P> 313: * Throws an exception if the x-value is a duplicate AND the 314: * allowDuplicateXValues flag is false. 315: * 316: * @param x the x-value (<code>null</code> not permitted). 317: * @param y the y-value (<code>null</code> permitted). 318: * @param notify a flag the controls whether or not a 319: * {@link SeriesChangeEvent} is sent to all registered 320: * listeners. 321: */ 322: public void add(Number x, Number y, boolean notify) { 323: // delegate argument checking to XYDataItem... 324: XYDataItem item = new XYDataItem(x, y); 325: add(item, notify); 326: } 327: 328: /** 329: * Adds a data item to the series and, if requested, sends a 330: * {@link SeriesChangeEvent} to all registered listeners. 331: * 332: * @param item the (x, y) item (<code>null</code> not permitted). 333: * @param notify a flag that controls whether or not a 334: * {@link SeriesChangeEvent} is sent to all registered 335: * listeners. 336: */ 337: public void add(XYDataItem item, boolean notify) { 338: 339: if (item == null) { 340: throw new IllegalArgumentException("Null 'item' argument."); 341: } 342: 343: if (this.autoSort) { 344: int index = Collections.binarySearch(this.data, item); 345: if (index < 0) { 346: this.data.add(-index - 1, item); 347: } 348: else { 349: if (this.allowDuplicateXValues) { 350: // need to make sure we are adding *after* any duplicates 351: int size = this.data.size(); 352: while (index < size 353: && item.compareTo(this.data.get(index)) == 0) { 354: index++; 355: } 356: if (index < this.data.size()) { 357: this.data.add(index, item); 358: } 359: else { 360: this.data.add(item); 361: } 362: } 363: else { 364: throw new SeriesException("X-value already exists."); 365: } 366: } 367: } 368: else { 369: if (!this.allowDuplicateXValues) { 370: // can't allow duplicate values, so we need to check whether 371: // there is an item with the given x-value already 372: int index = indexOf(item.getX()); 373: if (index >= 0) { 374: throw new SeriesException("X-value already exists."); 375: } 376: } 377: this.data.add(item); 378: } 379: if (getItemCount() > this.maximumItemCount) { 380: this.data.remove(0); 381: } 382: if (notify) { 383: fireSeriesChanged(); 384: } 385: } 386: 387: /** 388: * Deletes a range of items from the series and sends a 389: * {@link SeriesChangeEvent} to all registered listeners. 390: * 391: * @param start the start index (zero-based). 392: * @param end the end index (zero-based). 393: */ 394: public void delete(int start, int end) { 395: for (int i = start; i <= end; i++) { 396: this.data.remove(start); 397: } 398: fireSeriesChanged(); 399: } 400: 401: /** 402: * Removes the item at the specified index and sends a 403: * {@link SeriesChangeEvent} to all registered listeners. 404: * 405: * @param index the index. 406: * 407: * @return The item removed. 408: */ 409: public XYDataItem remove(int index) { 410: XYDataItem result = (XYDataItem) this.data.remove(index); 411: fireSeriesChanged(); 412: return result; 413: } 414: 415: /** 416: * Removes the item with the specified x-value and sends a 417: * {@link SeriesChangeEvent} to all registered listeners. 418: * 419: * @param x the x-value. 420: 421: * @return The item removed. 422: */ 423: public XYDataItem remove(Number x) { 424: return remove(indexOf(x)); 425: } 426: 427: /** 428: * Removes all data items from the series. 429: */ 430: public void clear() { 431: if (this.data.size() > 0) { 432: this.data.clear(); 433: fireSeriesChanged(); 434: } 435: } 436: 437: /** 438: * Return the data item with the specified index. 439: * 440: * @param index the index. 441: * 442: * @return The data item with the specified index. 443: */ 444: public XYDataItem getDataItem(int index) { 445: return (XYDataItem) this.data.get(index); 446: } 447: 448: /** 449: * Returns the x-value at the specified index. 450: * 451: * @param index the index (zero-based). 452: * 453: * @return The x-value (never <code>null</code>). 454: */ 455: public Number getX(int index) { 456: return getDataItem(index).getX(); 457: } 458: 459: /** 460: * Returns the y-value at the specified index. 461: * 462: * @param index the index (zero-based). 463: * 464: * @return The y-value (possibly <code>null</code>). 465: */ 466: public Number getY(int index) { 467: return getDataItem(index).getY(); 468: } 469: 470: /** 471: * Updates the value of an item in the series and sends a 472: * {@link SeriesChangeEvent} to all registered listeners. 473: * 474: * @param index the item (zero based index). 475: * @param y the new value (<code>null</code> permitted). 476: * 477: * @deprecated Renamed updateByIndex(int, Number) to avoid confusion with 478: * the update(Number, Number) method. 479: */ 480: public void update(int index, Number y) { 481: XYDataItem item = getDataItem(index); 482: item.setY(y); 483: fireSeriesChanged(); 484: } 485: 486: /** 487: * Updates the value of an item in the series and sends a 488: * {@link SeriesChangeEvent} to all registered listeners. 489: * 490: * @param index the item (zero based index). 491: * @param y the new value (<code>null</code> permitted). 492: * 493: * @since 1.0.1 494: */ 495: public void updateByIndex(int index, Number y) { 496: update(index, y); 497: } 498: 499: /** 500: * Updates an item in the series. 501: * 502: * @param x the x-value (<code>null</code> not permitted). 503: * @param y the y-value (<code>null</code> permitted). 504: * 505: * @throws SeriesException if there is no existing item with the specified 506: * x-value. 507: */ 508: public void update(Number x, Number y) { 509: int index = indexOf(x); 510: if (index < 0) { 511: throw new SeriesException("No observation for x = " + x); 512: } 513: else { 514: XYDataItem item = getDataItem(index); 515: item.setY(y); 516: fireSeriesChanged(); 517: } 518: } 519: 520: /** 521: * Adds or updates an item in the series and sends a 522: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 523: * listeners. 524: * 525: * @param x the x-value (<code>null</code> not permitted). 526: * @param y the y-value (<code>null</code> permitted). 527: * 528: * @return A copy of the overwritten data item, or <code>null</code> if no 529: * item was overwritten. 530: */ 531: public XYDataItem addOrUpdate(Number x, Number y) { 532: if (x == null) { 533: throw new IllegalArgumentException("Null 'x' argument."); 534: } 535: XYDataItem overwritten = null; 536: int index = indexOf(x); 537: if (index >= 0) { 538: XYDataItem existing = (XYDataItem) this.data.get(index); 539: try { 540: overwritten = (XYDataItem) existing.clone(); 541: } 542: catch (CloneNotSupportedException e) { 543: throw new SeriesException("Couldn't clone XYDataItem!"); 544: } 545: existing.setY(y); 546: } 547: else { 548: // if the series is sorted, the negative index is a result from 549: // Collections.binarySearch() and tells us where to insert the 550: // new item...otherwise it will be just -1 and we should just 551: // append the value to the list... 552: if (this.autoSort) { 553: this.data.add(-index - 1, new XYDataItem(x, y)); 554: } 555: else { 556: this.data.add(new XYDataItem(x, y)); 557: } 558: // check if this addition will exceed the maximum item count... 559: if (getItemCount() > this.maximumItemCount) { 560: this.data.remove(0); 561: } 562: } 563: fireSeriesChanged(); 564: return overwritten; 565: } 566: 567: /** 568: * Returns the index of the item with the specified x-value, or a negative 569: * index if the series does not contain an item with that x-value. Be 570: * aware that for an unsorted series, the index is found by iterating 571: * through all items in the series. 572: * 573: * @param x the x-value (<code>null</code> not permitted). 574: * 575: * @return The index. 576: */ 577: public int indexOf(Number x) { 578: if (this.autoSort) { 579: return Collections.binarySearch(this.data, new XYDataItem(x, null)); 580: } 581: else { 582: for (int i = 0; i < this.data.size(); i++) { 583: XYDataItem item = (XYDataItem) this.data.get(i); 584: if (item.getX().equals(x)) { 585: return i; 586: } 587: } 588: return -1; 589: } 590: } 591: 592: /** 593: * Returns a new array containing the x and y values from this series. 594: * 595: * @return A new array containing the x and y values from this series. 596: * 597: * @since 1.0.4 598: */ 599: public double[][] toArray() { 600: int itemCount = getItemCount(); 601: double[][] result = new double[2][itemCount]; 602: for (int i = 0; i < itemCount; i++) { 603: result[0][i] = this.getX(i).doubleValue(); 604: Number y = getY(i); 605: if (y != null) { 606: result[1][i] = y.doubleValue(); 607: } 608: else { 609: result[1][i] = Double.NaN; 610: } 611: } 612: return result; 613: } 614: 615: /** 616: * Returns a clone of the series. 617: * 618: * @return A clone of the time series. 619: * 620: * @throws CloneNotSupportedException if there is a cloning problem. 621: */ 622: public Object clone() throws CloneNotSupportedException { 623: Object clone = createCopy(0, getItemCount() - 1); 624: return clone; 625: } 626: 627: /** 628: * Creates a new series by copying a subset of the data in this time series. 629: * 630: * @param start the index of the first item to copy. 631: * @param end the index of the last item to copy. 632: * 633: * @return A series containing a copy of this series from start until end. 634: * 635: * @throws CloneNotSupportedException if there is a cloning problem. 636: */ 637: public XYSeries createCopy(int start, int end) 638: throws CloneNotSupportedException { 639: 640: XYSeries copy = (XYSeries) super.clone(); 641: copy.data = new java.util.ArrayList(); 642: if (this.data.size() > 0) { 643: for (int index = start; index <= end; index++) { 644: XYDataItem item = (XYDataItem) this.data.get(index); 645: XYDataItem clone = (XYDataItem) item.clone(); 646: try { 647: copy.add(clone); 648: } 649: catch (SeriesException e) { 650: System.err.println("Unable to add cloned data item."); 651: } 652: } 653: } 654: return copy; 655: 656: } 657: 658: /** 659: * Tests this series for equality with an arbitrary object. 660: * 661: * @param obj the object to test against for equality 662: * (<code>null</code> permitted). 663: * 664: * @return A boolean. 665: */ 666: public boolean equals(Object obj) { 667: if (obj == this) { 668: return true; 669: } 670: if (!(obj instanceof XYSeries)) { 671: return false; 672: } 673: if (!super.equals(obj)) { 674: return false; 675: } 676: XYSeries that = (XYSeries) obj; 677: if (this.maximumItemCount != that.maximumItemCount) { 678: return false; 679: } 680: if (this.autoSort != that.autoSort) { 681: return false; 682: } 683: if (this.allowDuplicateXValues != that.allowDuplicateXValues) { 684: return false; 685: } 686: if (!ObjectUtilities.equal(this.data, that.data)) { 687: return false; 688: } 689: return true; 690: } 691: 692: /** 693: * Returns a hash code. 694: * 695: * @return A hash code. 696: */ 697: public int hashCode() { 698: int result = super.hashCode(); 699: result = 29 * result + (this.data != null ? this.data.hashCode() : 0); 700: result = 29 * result + this.maximumItemCount; 701: result = 29 * result + (this.autoSort ? 1 : 0); 702: result = 29 * result + (this.allowDuplicateXValues ? 1 : 0); 703: return result; 704: } 705: 706: } 707: