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