Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, 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: * TimePeriodValues.java 29: * --------------------- 30: * (C) Copyright 2003-2006, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * $Id: TimePeriodValues.java,v 1.8.2.2 2006/10/03 15:16:33 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 22-Apr-2003 : Version 1 (DG); 40: * 30-Jul-2003 : Added clone and equals methods while testing (DG); 41: * 11-Mar-2005 : Fixed bug in bounds recalculation - see bug report 42: * 1161329 (DG); 43: * ------------- JFREECHART 1.0.0 --------------------------------------------- 44: * 03-Oct-2006 : Fixed NullPointerException in equals(), fire change event in 45: * add() method, updated API docs (DG); 46: * 47: */ 48: 49: package org.jfree.data.time; 50: 51: import java.io.Serializable; 52: import java.util.ArrayList; 53: import java.util.List; 54: 55: import org.jfree.data.general.Series; 56: import org.jfree.data.general.SeriesChangeEvent; 57: import org.jfree.data.general.SeriesException; 58: import org.jfree.util.ObjectUtilities; 59: 60: /** 61: * A structure containing zero, one or many {@link TimePeriodValue} instances. 62: * The time periods can overlap, and are maintained in the order that they are 63: * added to the collection. 64: * <p> 65: * This is similar to the {@link TimeSeries} class, except that the time 66: * periods can have irregular lengths. 67: */ 68: public class TimePeriodValues extends Series implements Serializable { 69: 70: /** For serialization. */ 71: static final long serialVersionUID = -2210593619794989709L; 72: 73: /** Default value for the domain description. */ 74: protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time"; 75: 76: /** Default value for the range description. */ 77: protected static final String DEFAULT_RANGE_DESCRIPTION = "Value"; 78: 79: /** A description of the domain. */ 80: private String domain; 81: 82: /** A description of the range. */ 83: private String range; 84: 85: /** The list of data pairs in the series. */ 86: private List data; 87: 88: /** Index of the time period with the minimum start milliseconds. */ 89: private int minStartIndex = -1; 90: 91: /** Index of the time period with the maximum start milliseconds. */ 92: private int maxStartIndex = -1; 93: 94: /** Index of the time period with the minimum middle milliseconds. */ 95: private int minMiddleIndex = -1; 96: 97: /** Index of the time period with the maximum middle milliseconds. */ 98: private int maxMiddleIndex = -1; 99: 100: /** Index of the time period with the minimum end milliseconds. */ 101: private int minEndIndex = -1; 102: 103: /** Index of the time period with the maximum end milliseconds. */ 104: private int maxEndIndex = -1; 105: 106: /** 107: * Creates a new (empty) collection of time period values. 108: * 109: * @param name the name of the series (<code>null</code> not permitted). 110: */ 111: public TimePeriodValues(String name) { 112: this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION); 113: } 114: 115: /** 116: * Creates a new time series that contains no data. 117: * <P> 118: * Descriptions can be specified for the domain and range. One situation 119: * where this is helpful is when generating a chart for the time series - 120: * axis labels can be taken from the domain and range description. 121: * 122: * @param name the name of the series (<code>null</code> not permitted). 123: * @param domain the domain description. 124: * @param range the range description. 125: */ 126: public TimePeriodValues(String name, String domain, String range) { 127: super(name); 128: this.domain = domain; 129: this.range = range; 130: this.data = new ArrayList(); 131: } 132: 133: /** 134: * Returns the domain description. 135: * 136: * @return The domain description (possibly <code>null</code>). 137: * 138: * @see #getRangeDescription() 139: * @see #setDomainDescription(String) 140: */ 141: public String getDomainDescription() { 142: return this.domain; 143: } 144: 145: /** 146: * Sets the domain description and fires a property change event (with the 147: * property name <code>Domain</code> if the description changes). 148: * 149: * @param description the new description (<code>null</code> permitted). 150: * 151: * @see #getDomainDescription() 152: */ 153: public void setDomainDescription(String description) { 154: String old = this.domain; 155: this.domain = description; 156: firePropertyChange("Domain", old, description); 157: } 158: 159: /** 160: * Returns the range description. 161: * 162: * @return The range description (possibly <code>null</code>). 163: * 164: * @see #getDomainDescription() 165: * @see #setRangeDescription(String) 166: */ 167: public String getRangeDescription() { 168: return this.range; 169: } 170: 171: /** 172: * Sets the range description and fires a property change event with the 173: * name <code>Range</code>. 174: * 175: * @param description the new description (<code>null</code> permitted). 176: * 177: * @see #getRangeDescription() 178: */ 179: public void setRangeDescription(String description) { 180: String old = this.range; 181: this.range = description; 182: firePropertyChange("Range", old, description); 183: } 184: 185: /** 186: * Returns the number of items in the series. 187: * 188: * @return The item count. 189: */ 190: public int getItemCount() { 191: return this.data.size(); 192: } 193: 194: /** 195: * Returns one data item for the series. 196: * 197: * @param index the item index (in the range <code>0</code> to 198: * <code>getItemCount() - 1</code>). 199: * 200: * @return One data item for the series. 201: */ 202: public TimePeriodValue getDataItem(int index) { 203: return (TimePeriodValue) this.data.get(index); 204: } 205: 206: /** 207: * Returns the time period at the specified index. 208: * 209: * @param index the item index (in the range <code>0</code> to 210: * <code>getItemCount() - 1</code>). 211: * 212: * @return The time period at the specified index. 213: * 214: * @see #getDataItem(int) 215: */ 216: public TimePeriod getTimePeriod(int index) { 217: return getDataItem(index).getPeriod(); 218: } 219: 220: /** 221: * Returns the value at the specified index. 222: * 223: * @param index the item index (in the range <code>0</code> to 224: * <code>getItemCount() - 1</code>). 225: * 226: * @return The value at the specified index (possibly <code>null</code>). 227: * 228: * @see #getDataItem(int) 229: */ 230: public Number getValue(int index) { 231: return getDataItem(index).getValue(); 232: } 233: 234: /** 235: * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 236: * all registered listeners. 237: * 238: * @param item the item (<code>null</code> not permitted). 239: */ 240: public void add(TimePeriodValue item) { 241: if (item == null) { 242: throw new IllegalArgumentException("Null item not allowed."); 243: } 244: this.data.add(item); 245: updateBounds(item.getPeriod(), this.data.size() - 1); 246: fireSeriesChanged(); 247: } 248: 249: /** 250: * Update the index values for the maximum and minimum bounds. 251: * 252: * @param period the time period. 253: * @param index the index of the time period. 254: */ 255: private void updateBounds(TimePeriod period, int index) { 256: 257: long start = period.getStart().getTime(); 258: long end = period.getEnd().getTime(); 259: long middle = start + ((end - start) / 2); 260: 261: if (this.minStartIndex >= 0) { 262: long minStart = getDataItem(this.minStartIndex).getPeriod() 263: .getStart().getTime(); 264: if (start < minStart) { 265: this.minStartIndex = index; 266: } 267: } 268: else { 269: this.minStartIndex = index; 270: } 271: 272: if (this.maxStartIndex >= 0) { 273: long maxStart = getDataItem(this.maxStartIndex).getPeriod() 274: .getStart().getTime(); 275: if (start > maxStart) { 276: this.maxStartIndex = index; 277: } 278: } 279: else { 280: this.maxStartIndex = index; 281: } 282: 283: if (this.minMiddleIndex >= 0) { 284: long s = getDataItem(this.minMiddleIndex).getPeriod().getStart() 285: .getTime(); 286: long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd() 287: .getTime(); 288: long minMiddle = s + (e - s) / 2; 289: if (middle < minMiddle) { 290: this.minMiddleIndex = index; 291: } 292: } 293: else { 294: this.minMiddleIndex = index; 295: } 296: 297: if (this.maxMiddleIndex >= 0) { 298: long s = getDataItem(this.minMiddleIndex).getPeriod().getStart() 299: .getTime(); 300: long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd() 301: .getTime(); 302: long maxMiddle = s + (e - s) / 2; 303: if (middle > maxMiddle) { 304: this.maxMiddleIndex = index; 305: } 306: } 307: else { 308: this.maxMiddleIndex = index; 309: } 310: 311: if (this.minEndIndex >= 0) { 312: long minEnd = getDataItem(this.minEndIndex).getPeriod().getEnd() 313: .getTime(); 314: if (end < minEnd) { 315: this.minEndIndex = index; 316: } 317: } 318: else { 319: this.minEndIndex = index; 320: } 321: 322: if (this.maxEndIndex >= 0) { 323: long maxEnd = getDataItem(this.maxEndIndex).getPeriod().getEnd() 324: .getTime(); 325: if (end > maxEnd) { 326: this.maxEndIndex = index; 327: } 328: } 329: else { 330: this.maxEndIndex = index; 331: } 332: 333: } 334: 335: /** 336: * Recalculates the bounds for the collection of items. 337: */ 338: private void recalculateBounds() { 339: this.minStartIndex = -1; 340: this.minMiddleIndex = -1; 341: this.minEndIndex = -1; 342: this.maxStartIndex = -1; 343: this.maxMiddleIndex = -1; 344: this.maxEndIndex = -1; 345: for (int i = 0; i < this.data.size(); i++) { 346: TimePeriodValue tpv = (TimePeriodValue) this.data.get(i); 347: updateBounds(tpv.getPeriod(), i); 348: } 349: } 350: 351: /** 352: * Adds a new data item to the series and sends a {@link SeriesChangeEvent} 353: * to all registered listeners. 354: * 355: * @param period the time period (<code>null</code> not permitted). 356: * @param value the value. 357: * 358: * @see #add(TimePeriod, Number) 359: */ 360: public void add(TimePeriod period, double value) { 361: TimePeriodValue item = new TimePeriodValue(period, value); 362: add(item); 363: } 364: 365: /** 366: * Adds a new data item to the series and sends a {@link SeriesChangeEvent} 367: * to all registered listeners. 368: * 369: * @param period the time period (<code>null</code> not permitted). 370: * @param value the value (<code>null</code> permitted). 371: */ 372: public void add(TimePeriod period, Number value) { 373: TimePeriodValue item = new TimePeriodValue(period, value); 374: add(item); 375: } 376: 377: /** 378: * Updates (changes) the value of a data item and sends a 379: * {@link SeriesChangeEvent} to all registered listeners. 380: * 381: * @param index the index of the data item to update. 382: * @param value the new value (<code>null</code> not permitted). 383: */ 384: public void update(int index, Number value) { 385: TimePeriodValue item = getDataItem(index); 386: item.setValue(value); 387: fireSeriesChanged(); 388: } 389: 390: /** 391: * Deletes data from start until end index (end inclusive) and sends a 392: * {@link SeriesChangeEvent} to all registered listeners. 393: * 394: * @param start the index of the first period to delete. 395: * @param end the index of the last period to delete. 396: */ 397: public void delete(int start, int end) { 398: for (int i = 0; i <= (end - start); i++) { 399: this.data.remove(start); 400: } 401: recalculateBounds(); 402: fireSeriesChanged(); 403: } 404: 405: /** 406: * Tests the series for equality with another object. 407: * 408: * @param obj the object (<code>null</code> permitted). 409: * 410: * @return <code>true</code> or <code>false</code>. 411: */ 412: public boolean equals(Object obj) { 413: if (obj == this) { 414: return true; 415: } 416: if (!(obj instanceof TimePeriodValues)) { 417: return false; 418: } 419: if (!super.equals(obj)) { 420: return false; 421: } 422: TimePeriodValues that = (TimePeriodValues) obj; 423: if (!ObjectUtilities.equal(this.getDomainDescription(), 424: that.getDomainDescription())) { 425: return false; 426: } 427: if (!ObjectUtilities.equal(this.getRangeDescription(), 428: that.getRangeDescription())) { 429: return false; 430: } 431: int count = getItemCount(); 432: if (count != that.getItemCount()) { 433: return false; 434: } 435: for (int i = 0; i < count; i++) { 436: if (!getDataItem(i).equals(that.getDataItem(i))) { 437: return false; 438: } 439: } 440: return true; 441: } 442: 443: /** 444: * Returns a hash code value for the object. 445: * 446: * @return The hashcode 447: */ 448: public int hashCode() { 449: int result; 450: result = (this.domain != null ? this.domain.hashCode() : 0); 451: result = 29 * result + (this.range != null ? this.range.hashCode() : 0); 452: result = 29 * result + this.data.hashCode(); 453: result = 29 * result + this.minStartIndex; 454: result = 29 * result + this.maxStartIndex; 455: result = 29 * result + this.minMiddleIndex; 456: result = 29 * result + this.maxMiddleIndex; 457: result = 29 * result + this.minEndIndex; 458: result = 29 * result + this.maxEndIndex; 459: return result; 460: } 461: 462: /** 463: * Returns a clone of the collection. 464: * <P> 465: * Notes: 466: * <ul> 467: * <li>no need to clone the domain and range descriptions, since String 468: * object is immutable;</li> 469: * <li>we pass over to the more general method createCopy(start, end). 470: * </li> 471: * </ul> 472: * 473: * @return A clone of the time series. 474: * 475: * @throws CloneNotSupportedException if there is a cloning problem. 476: */ 477: public Object clone() throws CloneNotSupportedException { 478: Object clone = createCopy(0, getItemCount() - 1); 479: return clone; 480: } 481: 482: /** 483: * Creates a new instance by copying a subset of the data in this 484: * collection. 485: * 486: * @param start the index of the first item to copy. 487: * @param end the index of the last item to copy. 488: * 489: * @return A copy of a subset of the items. 490: * 491: * @throws CloneNotSupportedException if there is a cloning problem. 492: */ 493: public TimePeriodValues createCopy(int start, int end) 494: throws CloneNotSupportedException { 495: 496: TimePeriodValues copy = (TimePeriodValues) super.clone(); 497: 498: copy.data = new ArrayList(); 499: if (this.data.size() > 0) { 500: for (int index = start; index <= end; index++) { 501: TimePeriodValue item = (TimePeriodValue) this.data.get(index); 502: TimePeriodValue clone = (TimePeriodValue) item.clone(); 503: try { 504: copy.add(clone); 505: } 506: catch (SeriesException e) { 507: System.err.println("Failed to add cloned item."); 508: } 509: } 510: } 511: return copy; 512: 513: } 514: 515: /** 516: * Returns the index of the time period with the minimum start milliseconds. 517: * 518: * @return The index. 519: */ 520: public int getMinStartIndex() { 521: return this.minStartIndex; 522: } 523: 524: /** 525: * Returns the index of the time period with the maximum start milliseconds. 526: * 527: * @return The index. 528: */ 529: public int getMaxStartIndex() { 530: return this.maxStartIndex; 531: } 532: 533: /** 534: * Returns the index of the time period with the minimum middle 535: * milliseconds. 536: * 537: * @return The index. 538: */ 539: public int getMinMiddleIndex() { 540: return this.minMiddleIndex; 541: } 542: 543: /** 544: * Returns the index of the time period with the maximum middle 545: * milliseconds. 546: * 547: * @return The index. 548: */ 549: public int getMaxMiddleIndex() { 550: return this.maxMiddleIndex; 551: } 552: 553: /** 554: * Returns the index of the time period with the minimum end milliseconds. 555: * 556: * @return The index. 557: */ 558: public int getMinEndIndex() { 559: return this.minEndIndex; 560: } 561: 562: /** 563: * Returns the index of the time period with the maximum end milliseconds. 564: * 565: * @return The index. 566: */ 567: public int getMaxEndIndex() { 568: return this.maxEndIndex; 569: } 570: 571: }