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