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