Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2005, 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: * DynamicTimeSeriesCollection.java 29: * -------------------------------- 30: * (C) Copyright 2002-2005, by I. H. Thomae and Contributors. 31: * 32: * Original Author: I. H. Thomae (ithomae@ists.dartmouth.edu); 33: * Contributor(s): David Gilbert (for Object Refinery Limited); 34: * 35: * $Id: DynamicTimeSeriesCollection.java,v 1.11.2.1 2005/10/25 21:35:24 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 22-Nov-2002 : Initial version completed 40: * Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc 41: * (using cached values for min, max, and range); also added 42: * getOldestIndex() and getNewestIndex() ftns so client classes 43: * can use this class as the master "index authority". 44: * 22-Jan-2003 : Made this class stand on its own, rather than extending 45: * class FastTimeSeriesCollection 46: * 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG); 47: * 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG); 48: * 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG); 49: * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG); 50: * 05-May-2004 : Now extends AbstractIntervalXYDataset. This also required a 51: * change to the return type of the getY() method - I'm slightly 52: * unsure of the implications of this, so it might require some 53: * further amendment (DG); 54: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 55: * getYValue() (DG); 56: * 11-Jan-2004 : Removed deprecated code in preparation for the 1.0.0 57: * release (DG); 58: * 59: */ 60: 61: package org.jfree.data.time; 62: 63: import java.util.Calendar; 64: import java.util.TimeZone; 65: 66: import org.jfree.data.DomainInfo; 67: import org.jfree.data.Range; 68: import org.jfree.data.RangeInfo; 69: import org.jfree.data.general.SeriesChangeEvent; 70: import org.jfree.data.xy.AbstractIntervalXYDataset; 71: import org.jfree.data.xy.IntervalXYDataset; 72: 73: /** 74: * A dynamic dataset. 75: * <p> 76: * Like FastTimeSeriesCollection, this class is a functional replacement 77: * for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes. 78: * FastTimeSeriesCollection is appropriate for a fixed time range; for 79: * real-time applications this subclass adds the ability to append new 80: * data and discard the oldest. 81: * In this class, the arrays used in FastTimeSeriesCollection become FIFO's. 82: * NOTE:As presented here, all data is assumed >= 0, an assumption which is 83: * embodied only in methods associated with interface RangeInfo. 84: * 85: * @author Irv Thomae. 86: */ 87: public class DynamicTimeSeriesCollection extends AbstractIntervalXYDataset 88: implements IntervalXYDataset, 89: DomainInfo, 90: RangeInfo { 91: 92: /** 93: * Useful constant for controlling the x-value returned for a time 94: * period. 95: */ 96: public static final int START = 0; 97: 98: /** 99: * Useful constant for controlling the x-value returned for a time period. 100: */ 101: public static final int MIDDLE = 1; 102: 103: /** 104: * Useful constant for controlling the x-value returned for a time period. 105: */ 106: public static final int END = 2; 107: 108: /** The maximum number of items for each series (can be overridden). */ 109: private int maximumItemCount = 2000; // an arbitrary safe default value 110: 111: /** The history count. */ 112: protected int historyCount; 113: 114: /** Storage for the series keys. */ 115: private Comparable[] seriesKeys; 116: 117: /** The time period class - barely used, and could be removed (DG). */ 118: private Class timePeriodClass = Minute.class; // default value; 119: 120: /** Storage for the x-values. */ 121: protected RegularTimePeriod[] pointsInTime; 122: 123: /** The number of series. */ 124: private int seriesCount; 125: 126: /** 127: * A wrapper for a fixed array of float values. 128: */ 129: protected class ValueSequence { 130: 131: /** Storage for the float values. */ 132: float[] dataPoints; 133: 134: /** 135: * Default constructor: 136: */ 137: public ValueSequence() { 138: this(DynamicTimeSeriesCollection.this.maximumItemCount); 139: } 140: 141: /** 142: * Creates a sequence with the specified length. 143: * 144: * @param length the length. 145: */ 146: public ValueSequence(int length) { 147: this.dataPoints = new float[length]; 148: for (int i = 0; i < length; i++) { 149: this.dataPoints[i] = 0.0f; 150: } 151: } 152: 153: /** 154: * Enters data into the storage array. 155: * 156: * @param index the index. 157: * @param value the value. 158: */ 159: public void enterData(int index, float value) { 160: this.dataPoints[index] = value; 161: } 162: 163: /** 164: * Returns a value from the storage array. 165: * 166: * @param index the index. 167: * 168: * @return The value. 169: */ 170: public float getData(int index) { 171: return this.dataPoints[index]; 172: } 173: } 174: 175: /** An array for storing the objects that represent each series. */ 176: protected ValueSequence[] valueHistory; 177: 178: /** A working calendar (to recycle) */ 179: protected Calendar workingCalendar; 180: 181: /** 182: * The position within a time period to return as the x-value (START, 183: * MIDDLE or END). 184: */ 185: private int position; 186: 187: /** 188: * A flag that indicates that the domain is 'points in time'. If this flag 189: * is true, only the x-value is used to determine the range of values in 190: * the domain, the start and end x-values are ignored. 191: */ 192: private boolean domainIsPointsInTime; 193: 194: /** index for mapping: points to the oldest valid time & data. */ 195: private int oldestAt; // as a class variable, initializes == 0 196: 197: /** Index of the newest data item. */ 198: private int newestAt; 199: 200: // cached values used for interface DomainInfo: 201: 202: /** the # of msec by which time advances. */ 203: private long deltaTime; 204: 205: /** Cached domain start (for use by DomainInfo). */ 206: private Long domainStart; 207: 208: /** Cached domain end (for use by DomainInfo). */ 209: private Long domainEnd; 210: 211: /** Cached domain range (for use by DomainInfo). */ 212: private Range domainRange; 213: 214: // Cached values used for interface RangeInfo: (note minValue pinned at 0) 215: // A single set of extrema covers the entire SeriesCollection 216: 217: /** The minimum value. */ 218: private Float minValue = new Float(0.0f); 219: 220: /** The maximum value. */ 221: private Float maxValue = null; 222: 223: /** The value range. */ 224: private Range valueRange; // autoinit's to null. 225: 226: /** 227: * Constructs a dataset with capacity for N series, tied to default 228: * timezone. 229: * 230: * @param nSeries the number of series to be accommodated. 231: * @param nMoments the number of TimePeriods to be spanned. 232: */ 233: public DynamicTimeSeriesCollection(int nSeries, int nMoments) { 234: 235: this(nSeries, nMoments, new Millisecond(), TimeZone.getDefault()); 236: this.newestAt = nMoments - 1; 237: 238: } 239: 240: /** 241: * Constructs an empty dataset, tied to a specific timezone. 242: * 243: * @param nSeries the number of series to be accommodated 244: * @param nMoments the number of TimePeriods to be spanned 245: * @param zone the timezone. 246: */ 247: public DynamicTimeSeriesCollection(int nSeries, int nMoments, 248: TimeZone zone) { 249: this(nSeries, nMoments, new Millisecond(), zone); 250: this.newestAt = nMoments - 1; 251: } 252: 253: /** 254: * Creates a new dataset. 255: * 256: * @param nSeries the number of series. 257: * @param nMoments the number of items per series. 258: * @param timeSample a time period sample. 259: */ 260: public DynamicTimeSeriesCollection(int nSeries, 261: int nMoments, 262: RegularTimePeriod timeSample) { 263: this(nSeries, nMoments, timeSample, TimeZone.getDefault()); 264: } 265: 266: /** 267: * Creates a new dataset. 268: * 269: * @param nSeries the number of series. 270: * @param nMoments the number of items per series. 271: * @param timeSample a time period sample. 272: * @param zone the time zone. 273: */ 274: public DynamicTimeSeriesCollection(int nSeries, 275: int nMoments, 276: RegularTimePeriod timeSample, 277: TimeZone zone) { 278: 279: // the first initialization must precede creation of the ValueSet array: 280: this.maximumItemCount = nMoments; // establishes length of each array 281: this.historyCount = nMoments; 282: this.seriesKeys = new Comparable[nSeries]; 283: // initialize the members of "seriesNames" array so they won't be null: 284: for (int i = 0; i < nSeries; i++) { 285: this.seriesKeys[i] = ""; 286: } 287: this.newestAt = nMoments - 1; 288: this.valueHistory = new ValueSequence[nSeries]; 289: this.timePeriodClass = timeSample.getClass(); 290: 291: /// Expand the following for all defined TimePeriods: 292: if (this.timePeriodClass == Second.class) { 293: this.pointsInTime = new Second[nMoments]; 294: } 295: else if (this.timePeriodClass == Minute.class) { 296: this.pointsInTime = new Minute[nMoments]; 297: } 298: else if (this.timePeriodClass == Hour.class) { 299: this.pointsInTime = new Hour[nMoments]; 300: } 301: /// .. etc.... 302: this.workingCalendar = Calendar.getInstance(zone); 303: this.position = START; 304: this.domainIsPointsInTime = true; 305: } 306: 307: /** 308: * Fill the pointsInTime with times using TimePeriod.next(): 309: * Will silently return if the time array was already populated. 310: * 311: * Also computes the data cached for later use by 312: * methods implementing the DomainInfo interface: 313: * 314: * @param start the start. 315: * 316: * @return ??. 317: */ 318: public synchronized long setTimeBase(RegularTimePeriod start) { 319: 320: if (this.pointsInTime[0] == null) { 321: this.pointsInTime[0] = start; 322: for (int i = 1; i < this.historyCount; i++) { 323: this.pointsInTime[i] = this.pointsInTime[i - 1].next(); 324: } 325: } 326: long oldestL = this.pointsInTime[0].getFirstMillisecond( 327: this.workingCalendar 328: ); 329: long nextL = this.pointsInTime[1].getFirstMillisecond( 330: this.workingCalendar 331: ); 332: this.deltaTime = nextL - oldestL; 333: this.oldestAt = 0; 334: this.newestAt = this.historyCount - 1; 335: findDomainLimits(); 336: return this.deltaTime; 337: 338: } 339: 340: /** 341: * Finds the domain limits. Note: this doesn't need to be synchronized 342: * because it's called from within another method that already is. 343: */ 344: protected void findDomainLimits() { 345: 346: long startL = getOldestTime().getFirstMillisecond(this.workingCalendar); 347: long endL; 348: if (this.domainIsPointsInTime) { 349: endL = getNewestTime().getFirstMillisecond(this.workingCalendar); 350: } 351: else { 352: endL = getNewestTime().getLastMillisecond(this.workingCalendar); 353: } 354: this.domainStart = new Long(startL); 355: this.domainEnd = new Long(endL); 356: this.domainRange = new Range(startL, endL); 357: 358: } 359: 360: /** 361: * Returns the x position type (START, MIDDLE or END). 362: * 363: * @return The x position type. 364: */ 365: public int getPosition() { 366: return this.position; 367: } 368: 369: /** 370: * Sets the x position type (START, MIDDLE or END). 371: * 372: * @param position The x position type. 373: */ 374: public void setPosition(int position) { 375: this.position = position; 376: } 377: 378: /** 379: * Adds a series to the dataset. Only the y-values are supplied, the 380: * x-values are specified elsewhere. 381: * 382: * @param values the y-values. 383: * @param seriesNumber the series index (zero-based). 384: * @param seriesKey the series key. 385: * 386: * Use this as-is during setup only, or add the synchronized keyword around 387: * the copy loop. 388: */ 389: public void addSeries(float[] values, 390: int seriesNumber, Comparable seriesKey) { 391: 392: invalidateRangeInfo(); 393: int i; 394: if (values == null) { 395: throw new IllegalArgumentException("TimeSeriesDataset.addSeries(): " 396: + "cannot add null array of values."); 397: } 398: if (seriesNumber >= this.valueHistory.length) { 399: throw new IllegalArgumentException("TimeSeriesDataset.addSeries(): " 400: + "cannot add more series than specified in c'tor"); 401: } 402: if (this.valueHistory[seriesNumber] == null) { 403: this.valueHistory[seriesNumber] 404: = new ValueSequence(this.historyCount); 405: this.seriesCount++; 406: } 407: // But if that series array already exists, just overwrite its contents 408: 409: // Avoid IndexOutOfBoundsException: 410: int srcLength = values.length; 411: int copyLength = this.historyCount; 412: boolean fillNeeded = false; 413: if (srcLength < this.historyCount) { 414: fillNeeded = true; 415: copyLength = srcLength; 416: } 417: //{ 418: for (i = 0; i < copyLength; i++) { // deep copy from values[], caller 419: // can safely discard that array 420: this.valueHistory[seriesNumber].enterData(i, values[i]); 421: } 422: if (fillNeeded) { 423: for (i = copyLength; i < this.historyCount; i++) { 424: this.valueHistory[seriesNumber].enterData(i, 0.0f); 425: } 426: } 427: //} 428: if (seriesKey != null) { 429: this.seriesKeys[seriesNumber] = seriesKey; 430: } 431: fireSeriesChanged(); 432: 433: } 434: 435: /** 436: * Sets the name of a series. If planning to add values individually. 437: * 438: * @param seriesNumber the series. 439: * @param key the new key. 440: */ 441: public void setSeriesKey(int seriesNumber, Comparable key) { 442: this.seriesKeys[seriesNumber] = key; 443: } 444: 445: /** 446: * Adds a value to a series. 447: * 448: * @param seriesNumber the series index. 449: * @param index ??. 450: * @param value the value. 451: */ 452: public void addValue(int seriesNumber, int index, float value) { 453: 454: invalidateRangeInfo(); 455: if (seriesNumber >= this.valueHistory.length) { 456: throw new IllegalArgumentException( 457: "TimeSeriesDataset.addValue(): series #" 458: + seriesNumber + "unspecified in c'tor" 459: ); 460: } 461: if (this.valueHistory[seriesNumber] == null) { 462: this.valueHistory[seriesNumber] 463: = new ValueSequence(this.historyCount); 464: this.seriesCount++; 465: } 466: // But if that series array already exists, just overwrite its contents 467: //synchronized(this) 468: //{ 469: this.valueHistory[seriesNumber].enterData(index, value); 470: //} 471: fireSeriesChanged(); 472: } 473: 474: /** 475: * Returns the number of series in the collection. 476: * 477: * @return The series count. 478: */ 479: public int getSeriesCount() { 480: return this.seriesCount; 481: } 482: 483: /** 484: * Returns the number of items in a series. 485: * <p> 486: * For this implementation, all series have the same number of items. 487: * 488: * @param series the series index (zero-based). 489: * 490: * @return The item count. 491: */ 492: public int getItemCount(int series) { // all arrays equal length, 493: // so ignore argument: 494: return this.historyCount; 495: } 496: 497: // Methods for managing the FIFO's: 498: 499: /** 500: * Re-map an index, for use in retrieving data. 501: * 502: * @param toFetch the index. 503: * 504: * @return The translated index. 505: */ 506: protected int translateGet(int toFetch) { 507: if (this.oldestAt == 0) { 508: return toFetch; // no translation needed 509: } 510: // else [implicit here] 511: int newIndex = toFetch + this.oldestAt; 512: if (newIndex >= this.historyCount) { 513: newIndex -= this.historyCount; 514: } 515: return newIndex; 516: } 517: 518: /** 519: * Returns the actual index to a time offset by "delta" from newestAt. 520: * 521: * @param delta the delta. 522: * 523: * @return The offset. 524: */ 525: public int offsetFromNewest(int delta) { 526: return wrapOffset(this.newestAt + delta); 527: } 528: 529: /** 530: * ?? 531: * 532: * @param delta ?? 533: * 534: * @return The offset. 535: */ 536: public int offsetFromOldest(int delta) { 537: return wrapOffset(this.oldestAt + delta); 538: } 539: 540: /** 541: * ?? 542: * 543: * @param protoIndex the index. 544: * 545: * @return The offset. 546: */ 547: protected int wrapOffset(int protoIndex) { 548: int tmp = protoIndex; 549: if (tmp >= this.historyCount) { 550: tmp -= this.historyCount; 551: } 552: else if (tmp < 0) { 553: tmp += this.historyCount; 554: } 555: return tmp; 556: } 557: 558: /** 559: * Adjust the array offset as needed when a new time-period is added: 560: * Increments the indices "oldestAt" and "newestAt", mod(array length), 561: * zeroes the series values at newestAt, returns the new TimePeriod. 562: * 563: * @return The new time period. 564: */ 565: public synchronized RegularTimePeriod advanceTime() { 566: RegularTimePeriod nextInstant = this.pointsInTime[this.newestAt].next(); 567: this.newestAt = this.oldestAt; // newestAt takes value previously held 568: // by oldestAT 569: /*** 570: * The next 10 lines or so should be expanded if data can be negative 571: ***/ 572: // if the oldest data contained a maximum Y-value, invalidate the stored 573: // Y-max and Y-range data: 574: boolean extremaChanged = false; 575: float oldMax = 0.0f; 576: if (this.maxValue != null) { 577: oldMax = this.maxValue.floatValue(); 578: } 579: for (int s = 0; s < getSeriesCount(); s++) { 580: if (this.valueHistory[s].getData(this.oldestAt) == oldMax) { 581: extremaChanged = true; 582: } 583: if (extremaChanged) { 584: break; 585: } 586: } /*** If data can be < 0, add code here to check the minimum **/ 587: if (extremaChanged) { 588: invalidateRangeInfo(); 589: } 590: // wipe the next (about to be used) set of data slots 591: float wiper = (float) 0.0; 592: for (int s = 0; s < getSeriesCount(); s++) { 593: this.valueHistory[s].enterData(this.newestAt, wiper); 594: } 595: // Update the array of TimePeriods: 596: this.pointsInTime[this.newestAt] = nextInstant; 597: // Now advance "oldestAt", wrapping at end of the array 598: this.oldestAt++; 599: if (this.oldestAt >= this.historyCount) { 600: this.oldestAt = 0; 601: } 602: // Update the domain limits: 603: long startL = this.domainStart.longValue(); //(time is kept in msec) 604: this.domainStart = new Long(startL + this.deltaTime); 605: long endL = this.domainEnd.longValue(); 606: this.domainEnd = new Long(endL + this.deltaTime); 607: this.domainRange = new Range(startL, endL); 608: fireSeriesChanged(); 609: return nextInstant; 610: } 611: 612: // If data can be < 0, the next 2 methods should be modified 613: 614: /** 615: * Invalidates the range info. 616: */ 617: public void invalidateRangeInfo() { 618: this.maxValue = null; 619: this.valueRange = null; 620: } 621: 622: /** 623: * Returns the maximum value. 624: * 625: * @return The maximum value. 626: */ 627: protected double findMaxValue() { 628: double max = 0.0f; 629: for (int s = 0; s < getSeriesCount(); s++) { 630: for (int i = 0; i < this.historyCount; i++) { 631: double tmp = getYValue(s, i); 632: if (tmp > max) { 633: max = tmp; 634: } 635: } 636: } 637: return max; 638: } 639: 640: /** End, positive-data-only code **/ 641: 642: /** 643: * Returns the index of the oldest data item. 644: * 645: * @return The index. 646: */ 647: public int getOldestIndex() { 648: return this.oldestAt; 649: } 650: 651: /** 652: * Returns the index of the newest data item. 653: * 654: * @return The index. 655: */ 656: public int getNewestIndex() { 657: return this.newestAt; 658: } 659: 660: // appendData() writes new data at the index position given by newestAt/ 661: // When adding new data dynamically, use advanceTime(), followed by this: 662: /** 663: * Appends new data. 664: * 665: * @param newData the data. 666: */ 667: public void appendData(float[] newData) { 668: int nDataPoints = newData.length; 669: if (nDataPoints > this.valueHistory.length) { 670: throw new IllegalArgumentException( 671: "More data than series to put them in" 672: ); 673: } 674: int s; // index to select the "series" 675: for (s = 0; s < nDataPoints; s++) { 676: // check whether the "valueHistory" array member exists; if not, 677: // create them: 678: if (this.valueHistory[s] == null) { 679: this.valueHistory[s] = new ValueSequence(this.historyCount); 680: } 681: this.valueHistory[s].enterData(this.newestAt, newData[s]); 682: } 683: fireSeriesChanged(); 684: } 685: 686: /** 687: * Appends data at specified index, for loading up with data from file(s). 688: * 689: * @param newData the data 690: * @param insertionIndex the index value at which to put it 691: * @param refresh value of n in "refresh the display on every nth call" 692: * (ignored if <= 0 ) 693: */ 694: public void appendData(float[] newData, int insertionIndex, int refresh) { 695: int nDataPoints = newData.length; 696: if (nDataPoints > this.valueHistory.length) { 697: throw new IllegalArgumentException( 698: "More data than series to put them " + "in" 699: ); 700: } 701: for (int s = 0; s < nDataPoints; s++) { 702: if (this.valueHistory[s] == null) { 703: this.valueHistory[s] = new ValueSequence(this.historyCount); 704: } 705: this.valueHistory[s].enterData(insertionIndex, newData[s]); 706: } 707: if (refresh > 0) { 708: insertionIndex++; 709: if (insertionIndex % refresh == 0) { 710: fireSeriesChanged(); 711: } 712: } 713: } 714: 715: /** 716: * Returns the newest time. 717: * 718: * @return The newest time. 719: */ 720: public RegularTimePeriod getNewestTime() { 721: return this.pointsInTime[this.newestAt]; 722: } 723: 724: /** 725: * Returns the oldest time. 726: * 727: * @return The oldest time. 728: */ 729: public RegularTimePeriod getOldestTime() { 730: return this.pointsInTime[this.oldestAt]; 731: } 732: 733: /** 734: * Returns the x-value. 735: * 736: * @param series the series index (zero-based). 737: * @param item the item index (zero-based). 738: * 739: * @return The value. 740: */ 741: // getXxx() ftns can ignore the "series" argument: 742: // Don't synchronize this!! Instead, synchronize the loop that calls it. 743: public Number getX(int series, int item) { 744: RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 745: return new Long(getX(tp)); 746: } 747: 748: /** 749: * Returns the y-value. 750: * 751: * @param series the series index (zero-based). 752: * @param item the item index (zero-based). 753: * 754: * @return The value. 755: */ 756: public double getYValue(int series, int item) { 757: // Don't synchronize this!! 758: // Instead, synchronize the loop that calls it. 759: ValueSequence values = this.valueHistory[series]; 760: return values.getData(translateGet(item)); 761: } 762: 763: /** 764: * Returns the y-value. 765: * 766: * @param series the series index (zero-based). 767: * @param item the item index (zero-based). 768: * 769: * @return The value. 770: */ 771: public Number getY(int series, int item) { 772: return new Float(getYValue(series, item)); 773: } 774: 775: /** 776: * Returns the start x-value. 777: * 778: * @param series the series index (zero-based). 779: * @param item the item index (zero-based). 780: * 781: * @return The value. 782: */ 783: public Number getStartX(int series, int item) { 784: RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 785: return new Long(tp.getFirstMillisecond(this.workingCalendar)); 786: } 787: 788: /** 789: * Returns the end x-value. 790: * 791: * @param series the series index (zero-based). 792: * @param item the item index (zero-based). 793: * 794: * @return The value. 795: */ 796: public Number getEndX(int series, int item) { 797: RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 798: return new Long(tp.getLastMillisecond(this.workingCalendar)); 799: } 800: 801: /** 802: * Returns the start y-value. 803: * 804: * @param series the series index (zero-based). 805: * @param item the item index (zero-based). 806: * 807: * @return The value. 808: */ 809: public Number getStartY(int series, int item) { 810: return getY(series, item); 811: } 812: 813: /** 814: * Returns the end y-value. 815: * 816: * @param series the series index (zero-based). 817: * @param item the item index (zero-based). 818: * 819: * @return The value. 820: */ 821: public Number getEndY(int series, int item) { 822: return getY(series, item); 823: } 824: 825: /* // "Extras" found useful when analyzing/verifying class behavior: 826: public Number getUntranslatedXValue(int series, int item) 827: { 828: return super.getXValue(series, item); 829: } 830: 831: public float getUntranslatedY(int series, int item) 832: { 833: return super.getY(series, item); 834: } */ 835: 836: /** 837: * Returns the key for a series. 838: * 839: * @param series the series index (zero-based). 840: * 841: * @return The key. 842: */ 843: public Comparable getSeriesKey(int series) { 844: return this.seriesKeys[series]; 845: } 846: 847: /** 848: * Sends a {@link SeriesChangeEvent} to all registered listeners. 849: */ 850: protected void fireSeriesChanged() { 851: seriesChanged(new SeriesChangeEvent(this)); 852: } 853: 854: // The next 3 functions override the base-class implementation of 855: // the DomainInfo interface. Using saved limits (updated by 856: // each updateTime() call), improves performance. 857: // 858: 859: /** 860: * Returns the minimum x-value in the dataset. 861: * 862: * @param includeInterval a flag that determines whether or not the 863: * x-interval is taken into account. 864: * 865: * @return The minimum value. 866: */ 867: public double getDomainLowerBound(boolean includeInterval) { 868: return this.domainStart.doubleValue(); 869: // a Long kept updated by advanceTime() 870: } 871: 872: /** 873: * Returns the maximum x-value in the dataset. 874: * 875: * @param includeInterval a flag that determines whether or not the 876: * x-interval is taken into account. 877: * 878: * @return The maximum value. 879: */ 880: public double getDomainUpperBound(boolean includeInterval) { 881: return this.domainEnd.doubleValue(); 882: // a Long kept updated by advanceTime() 883: } 884: 885: /** 886: * Returns the range of the values in this dataset's domain. 887: * 888: * @param includeInterval a flag that determines whether or not the 889: * x-interval is taken into account. 890: * 891: * @return The range. 892: */ 893: public Range getDomainBounds(boolean includeInterval) { 894: if (this.domainRange == null) { 895: findDomainLimits(); 896: } 897: return this.domainRange; 898: } 899: 900: /** 901: * Returns the x-value for a time period. 902: * 903: * @param period the period. 904: * 905: * @return The x-value. 906: */ 907: private long getX(RegularTimePeriod period) { 908: switch (this.position) { 909: case (START) : 910: return period.getFirstMillisecond(this.workingCalendar); 911: case (MIDDLE) : 912: return period.getMiddleMillisecond(this.workingCalendar); 913: case (END) : 914: return period.getLastMillisecond(this.workingCalendar); 915: default: 916: return period.getMiddleMillisecond(this.workingCalendar); 917: } 918: } 919: 920: // The next 3 functions implement the RangeInfo interface. 921: // Using saved limits (updated by each updateTime() call) significantly 922: // improves performance. WARNING: this code makes the simplifying 923: // assumption that data is never negative. Expand as needed for the 924: // general case. 925: 926: /** 927: * Returns the minimum range value. 928: * 929: * @param includeInterval a flag that determines whether or not the 930: * y-interval is taken into account. 931: * 932: * @return The minimum range value. 933: */ 934: public double getRangeLowerBound(boolean includeInterval) { 935: double result = Double.NaN; 936: if (this.minValue != null) { 937: result = this.minValue.doubleValue(); 938: } 939: return result; 940: } 941: 942: /** 943: * Returns the maximum range value. 944: * 945: * @param includeInterval a flag that determines whether or not the 946: * y-interval is taken into account. 947: * 948: * @return The maximum range value. 949: */ 950: public double getRangeUpperBound(boolean includeInterval) { 951: double result = Double.NaN; 952: if (this.maxValue != null) { 953: result = this.maxValue.doubleValue(); 954: } 955: return result; 956: } 957: 958: /** 959: * Returns the value range. 960: * 961: * @param includeInterval a flag that determines whether or not the 962: * y-interval is taken into account. 963: * 964: * @return The range. 965: */ 966: public Range getRangeBounds(boolean includeInterval) { 967: if (this.valueRange == null) { 968: double max = getRangeUpperBound(includeInterval); 969: this.valueRange = new Range(0.0, max); 970: } 971: return this.valueRange; 972: } 973: 974: }