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