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