001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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 * [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-2008, 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 * Changes 036 * ------- 037 * 22-Nov-2002 : Initial version completed 038 * Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc 039 * (using cached values for min, max, and range); also added 040 * getOldestIndex() and getNewestIndex() ftns so client classes 041 * can use this class as the master "index authority". 042 * 22-Jan-2003 : Made this class stand on its own, rather than extending 043 * class FastTimeSeriesCollection 044 * 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG); 045 * 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG); 046 * 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG); 047 * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG); 048 * 05-May-2004 : Now extends AbstractIntervalXYDataset. This also required a 049 * change to the return type of the getY() method - I'm slightly 050 * unsure of the implications of this, so it might require some 051 * further amendment (DG); 052 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 053 * getYValue() (DG); 054 * 11-Jan-2004 : Removed deprecated code in preparation for the 1.0.0 055 * release (DG); 056 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 057 * 058 */ 059 060package org.jfree.data.time; 061 062import java.util.Calendar; 063import java.util.TimeZone; 064 065import org.jfree.data.DomainInfo; 066import org.jfree.data.Range; 067import org.jfree.data.RangeInfo; 068import org.jfree.data.general.SeriesChangeEvent; 069import org.jfree.data.xy.AbstractIntervalXYDataset; 070import org.jfree.data.xy.IntervalXYDataset; 071 072/** 073 * A dynamic dataset. 074 * <p> 075 * Like FastTimeSeriesCollection, this class is a functional replacement 076 * for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes. 077 * FastTimeSeriesCollection is appropriate for a fixed time range; for 078 * real-time applications this subclass adds the ability to append new 079 * data and discard the oldest. 080 * In this class, the arrays used in FastTimeSeriesCollection become FIFO's. 081 * NOTE:As presented here, all data is assumed >= 0, an assumption which is 082 * embodied only in methods associated with interface RangeInfo. 083 */ 084public class DynamicTimeSeriesCollection extends AbstractIntervalXYDataset 085 implements IntervalXYDataset, 086 DomainInfo, 087 RangeInfo { 088 089 /** 090 * Useful constant for controlling the x-value returned for a time 091 * period. 092 */ 093 public static final int START = 0; 094 095 /** 096 * Useful constant for controlling the x-value returned for a time period. 097 */ 098 public static final int MIDDLE = 1; 099 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 @Override 477 public int getSeriesCount() { 478 return this.seriesCount; 479 } 480 481 /** 482 * Returns the number of items in a series. 483 * <p> 484 * For this implementation, all series have the same number of items. 485 * 486 * @param series the series index (zero-based). 487 * 488 * @return The item count. 489 */ 490 @Override 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 @Override 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 @Override 757 public double getYValue(int series, int item) { 758 // Don't synchronize this!! 759 // Instead, synchronize the loop that calls it. 760 ValueSequence values = this.valueHistory[series]; 761 return values.getData(translateGet(item)); 762 } 763 764 /** 765 * Returns the y-value. 766 * 767 * @param series the series index (zero-based). 768 * @param item the item index (zero-based). 769 * 770 * @return The value. 771 */ 772 @Override 773 public Number getY(int series, int item) { 774 return new Float(getYValue(series, item)); 775 } 776 777 /** 778 * Returns the start x-value. 779 * 780 * @param series the series index (zero-based). 781 * @param item the item index (zero-based). 782 * 783 * @return The value. 784 */ 785 @Override 786 public Number getStartX(int series, int item) { 787 RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 788 return new Long(tp.getFirstMillisecond(this.workingCalendar)); 789 } 790 791 /** 792 * Returns the end x-value. 793 * 794 * @param series the series index (zero-based). 795 * @param item the item index (zero-based). 796 * 797 * @return The value. 798 */ 799 @Override 800 public Number getEndX(int series, int item) { 801 RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 802 return new Long(tp.getLastMillisecond(this.workingCalendar)); 803 } 804 805 /** 806 * Returns the start y-value. 807 * 808 * @param series the series index (zero-based). 809 * @param item the item index (zero-based). 810 * 811 * @return The value. 812 */ 813 @Override 814 public Number getStartY(int series, int item) { 815 return getY(series, item); 816 } 817 818 /** 819 * Returns the end y-value. 820 * 821 * @param series the series index (zero-based). 822 * @param item the item index (zero-based). 823 * 824 * @return The value. 825 */ 826 @Override 827 public Number getEndY(int series, int item) { 828 return getY(series, item); 829 } 830 831 /* // "Extras" found useful when analyzing/verifying class behavior: 832 public Number getUntranslatedXValue(int series, int item) 833 { 834 return super.getXValue(series, item); 835 } 836 837 public float getUntranslatedY(int series, int item) 838 { 839 return super.getY(series, item); 840 } */ 841 842 /** 843 * Returns the key for a series. 844 * 845 * @param series the series index (zero-based). 846 * 847 * @return The key. 848 */ 849 @Override 850 public Comparable getSeriesKey(int series) { 851 return this.seriesKeys[series]; 852 } 853 854 /** 855 * Sends a {@link SeriesChangeEvent} to all registered listeners. 856 */ 857 protected void fireSeriesChanged() { 858 seriesChanged(new SeriesChangeEvent(this)); 859 } 860 861 // The next 3 functions override the base-class implementation of 862 // the DomainInfo interface. Using saved limits (updated by 863 // each updateTime() call), improves performance. 864 // 865 866 /** 867 * Returns the minimum x-value in the dataset. 868 * 869 * @param includeInterval a flag that determines whether or not the 870 * x-interval is taken into account. 871 * 872 * @return The minimum value. 873 */ 874 @Override 875 public double getDomainLowerBound(boolean includeInterval) { 876 return this.domainStart.doubleValue(); 877 // a Long kept updated by advanceTime() 878 } 879 880 /** 881 * Returns the maximum x-value in the dataset. 882 * 883 * @param includeInterval a flag that determines whether or not the 884 * x-interval is taken into account. 885 * 886 * @return The maximum value. 887 */ 888 @Override 889 public double getDomainUpperBound(boolean includeInterval) { 890 return this.domainEnd.doubleValue(); 891 // a Long kept updated by advanceTime() 892 } 893 894 /** 895 * Returns the range of the values in this dataset's domain. 896 * 897 * @param includeInterval a flag that determines whether or not the 898 * x-interval is taken into account. 899 * 900 * @return The range. 901 */ 902 @Override 903 public Range getDomainBounds(boolean includeInterval) { 904 if (this.domainRange == null) { 905 findDomainLimits(); 906 } 907 return this.domainRange; 908 } 909 910 /** 911 * Returns the x-value for a time period. 912 * 913 * @param period the period. 914 * 915 * @return The x-value. 916 */ 917 private long getX(RegularTimePeriod period) { 918 switch (this.position) { 919 case (START) : 920 return period.getFirstMillisecond(this.workingCalendar); 921 case (MIDDLE) : 922 return period.getMiddleMillisecond(this.workingCalendar); 923 case (END) : 924 return period.getLastMillisecond(this.workingCalendar); 925 default: 926 return period.getMiddleMillisecond(this.workingCalendar); 927 } 928 } 929 930 // The next 3 functions implement the RangeInfo interface. 931 // Using saved limits (updated by each updateTime() call) significantly 932 // improves performance. WARNING: this code makes the simplifying 933 // assumption that data is never negative. Expand as needed for the 934 // general case. 935 936 /** 937 * Returns the minimum range value. 938 * 939 * @param includeInterval a flag that determines whether or not the 940 * y-interval is taken into account. 941 * 942 * @return The minimum range value. 943 */ 944 @Override 945 public double getRangeLowerBound(boolean includeInterval) { 946 double result = Double.NaN; 947 if (this.minValue != null) { 948 result = this.minValue.doubleValue(); 949 } 950 return result; 951 } 952 953 /** 954 * Returns the maximum range value. 955 * 956 * @param includeInterval a flag that determines whether or not the 957 * y-interval is taken into account. 958 * 959 * @return The maximum range value. 960 */ 961 @Override 962 public double getRangeUpperBound(boolean includeInterval) { 963 double result = Double.NaN; 964 if (this.maxValue != null) { 965 result = this.maxValue.doubleValue(); 966 } 967 return result; 968 } 969 970 /** 971 * Returns the value range. 972 * 973 * @param includeInterval a flag that determines whether or not the 974 * y-interval is taken into account. 975 * 976 * @return The range. 977 */ 978 @Override 979 public Range getRangeBounds(boolean includeInterval) { 980 if (this.valueRange == null) { 981 double max = getRangeUpperBound(includeInterval); 982 this.valueRange = new Range(0.0, max); 983 } 984 return this.valueRange; 985 } 986 987}