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