001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------------------------- 028 * DynamicTimeSeriesCollection.java 029 * -------------------------------- 030 * (C) Copyright 2002-2005, by I. H. Thomae and Contributors. 031 * 032 * Original Author: I. H. Thomae (ithomae@ists.dartmouth.edu); 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * $Id: DynamicTimeSeriesCollection.java,v 1.11.2.1 2005/10/25 21:35:24 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 22-Nov-2002 : Initial version completed 040 * Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc 041 * (using cached values for min, max, and range); also added 042 * getOldestIndex() and getNewestIndex() ftns so client classes 043 * can use this class as the master "index authority". 044 * 22-Jan-2003 : Made this class stand on its own, rather than extending 045 * class FastTimeSeriesCollection 046 * 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG); 047 * 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG); 048 * 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG); 049 * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG); 050 * 05-May-2004 : Now extends AbstractIntervalXYDataset. This also required a 051 * change to the return type of the getY() method - I'm slightly 052 * unsure of the implications of this, so it might require some 053 * further amendment (DG); 054 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 055 * getYValue() (DG); 056 * 11-Jan-2004 : Removed deprecated code in preparation for the 1.0.0 057 * release (DG); 058 * 059 */ 060 061 package org.jfree.data.time; 062 063 import java.util.Calendar; 064 import java.util.TimeZone; 065 066 import org.jfree.data.DomainInfo; 067 import org.jfree.data.Range; 068 import org.jfree.data.RangeInfo; 069 import org.jfree.data.general.SeriesChangeEvent; 070 import org.jfree.data.xy.AbstractIntervalXYDataset; 071 import org.jfree.data.xy.IntervalXYDataset; 072 073 /** 074 * A dynamic dataset. 075 * <p> 076 * Like FastTimeSeriesCollection, this class is a functional replacement 077 * for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes. 078 * FastTimeSeriesCollection is appropriate for a fixed time range; for 079 * real-time applications this subclass adds the ability to append new 080 * data and discard the oldest. 081 * In this class, the arrays used in FastTimeSeriesCollection become FIFO's. 082 * NOTE:As presented here, all data is assumed >= 0, an assumption which is 083 * embodied only in methods associated with interface RangeInfo. 084 * 085 * @author Irv Thomae. 086 */ 087 public class DynamicTimeSeriesCollection extends AbstractIntervalXYDataset 088 implements IntervalXYDataset, 089 DomainInfo, 090 RangeInfo { 091 092 /** 093 * Useful constant for controlling the x-value returned for a time 094 * period. 095 */ 096 public static final int START = 0; 097 098 /** 099 * 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 }