001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-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 060 package org.jfree.data.time; 061 062 import java.util.Calendar; 063 import java.util.TimeZone; 064 065 import org.jfree.data.DomainInfo; 066 import org.jfree.data.Range; 067 import org.jfree.data.RangeInfo; 068 import org.jfree.data.general.SeriesChangeEvent; 069 import org.jfree.data.xy.AbstractIntervalXYDataset; 070 import 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 */ 084 public 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 public int getSeriesCount() { 477 return this.seriesCount; 478 } 479 480 /** 481 * Returns the number of items in a series. 482 * <p> 483 * For this implementation, all series have the same number of items. 484 * 485 * @param series the series index (zero-based). 486 * 487 * @return The item count. 488 */ 489 public int getItemCount(int series) { // all arrays equal length, 490 // so ignore argument: 491 return this.historyCount; 492 } 493 494 // Methods for managing the FIFO's: 495 496 /** 497 * Re-map an index, for use in retrieving data. 498 * 499 * @param toFetch the index. 500 * 501 * @return The translated index. 502 */ 503 protected int translateGet(int toFetch) { 504 if (this.oldestAt == 0) { 505 return toFetch; // no translation needed 506 } 507 // else [implicit here] 508 int newIndex = toFetch + this.oldestAt; 509 if (newIndex >= this.historyCount) { 510 newIndex -= this.historyCount; 511 } 512 return newIndex; 513 } 514 515 /** 516 * Returns the actual index to a time offset by "delta" from newestAt. 517 * 518 * @param delta the delta. 519 * 520 * @return The offset. 521 */ 522 public int offsetFromNewest(int delta) { 523 return wrapOffset(this.newestAt + delta); 524 } 525 526 /** 527 * ?? 528 * 529 * @param delta ?? 530 * 531 * @return The offset. 532 */ 533 public int offsetFromOldest(int delta) { 534 return wrapOffset(this.oldestAt + delta); 535 } 536 537 /** 538 * ?? 539 * 540 * @param protoIndex the index. 541 * 542 * @return The offset. 543 */ 544 protected int wrapOffset(int protoIndex) { 545 int tmp = protoIndex; 546 if (tmp >= this.historyCount) { 547 tmp -= this.historyCount; 548 } 549 else if (tmp < 0) { 550 tmp += this.historyCount; 551 } 552 return tmp; 553 } 554 555 /** 556 * Adjust the array offset as needed when a new time-period is added: 557 * Increments the indices "oldestAt" and "newestAt", mod(array length), 558 * zeroes the series values at newestAt, returns the new TimePeriod. 559 * 560 * @return The new time period. 561 */ 562 public synchronized RegularTimePeriod advanceTime() { 563 RegularTimePeriod nextInstant = this.pointsInTime[this.newestAt].next(); 564 this.newestAt = this.oldestAt; // newestAt takes value previously held 565 // by oldestAT 566 /*** 567 * The next 10 lines or so should be expanded if data can be negative 568 ***/ 569 // if the oldest data contained a maximum Y-value, invalidate the stored 570 // Y-max and Y-range data: 571 boolean extremaChanged = false; 572 float oldMax = 0.0f; 573 if (this.maxValue != null) { 574 oldMax = this.maxValue.floatValue(); 575 } 576 for (int s = 0; s < getSeriesCount(); s++) { 577 if (this.valueHistory[s].getData(this.oldestAt) == oldMax) { 578 extremaChanged = true; 579 } 580 if (extremaChanged) { 581 break; 582 } 583 } /*** If data can be < 0, add code here to check the minimum **/ 584 if (extremaChanged) { 585 invalidateRangeInfo(); 586 } 587 // wipe the next (about to be used) set of data slots 588 float wiper = (float) 0.0; 589 for (int s = 0; s < getSeriesCount(); s++) { 590 this.valueHistory[s].enterData(this.newestAt, wiper); 591 } 592 // Update the array of TimePeriods: 593 this.pointsInTime[this.newestAt] = nextInstant; 594 // Now advance "oldestAt", wrapping at end of the array 595 this.oldestAt++; 596 if (this.oldestAt >= this.historyCount) { 597 this.oldestAt = 0; 598 } 599 // Update the domain limits: 600 long startL = this.domainStart.longValue(); //(time is kept in msec) 601 this.domainStart = new Long(startL + this.deltaTime); 602 long endL = this.domainEnd.longValue(); 603 this.domainEnd = new Long(endL + this.deltaTime); 604 this.domainRange = new Range(startL, endL); 605 fireSeriesChanged(); 606 return nextInstant; 607 } 608 609 // If data can be < 0, the next 2 methods should be modified 610 611 /** 612 * Invalidates the range info. 613 */ 614 public void invalidateRangeInfo() { 615 this.maxValue = null; 616 this.valueRange = null; 617 } 618 619 /** 620 * Returns the maximum value. 621 * 622 * @return The maximum value. 623 */ 624 protected double findMaxValue() { 625 double max = 0.0f; 626 for (int s = 0; s < getSeriesCount(); s++) { 627 for (int i = 0; i < this.historyCount; i++) { 628 double tmp = getYValue(s, i); 629 if (tmp > max) { 630 max = tmp; 631 } 632 } 633 } 634 return max; 635 } 636 637 /** End, positive-data-only code **/ 638 639 /** 640 * Returns the index of the oldest data item. 641 * 642 * @return The index. 643 */ 644 public int getOldestIndex() { 645 return this.oldestAt; 646 } 647 648 /** 649 * Returns the index of the newest data item. 650 * 651 * @return The index. 652 */ 653 public int getNewestIndex() { 654 return this.newestAt; 655 } 656 657 // appendData() writes new data at the index position given by newestAt/ 658 // When adding new data dynamically, use advanceTime(), followed by this: 659 /** 660 * Appends new data. 661 * 662 * @param newData the data. 663 */ 664 public void appendData(float[] newData) { 665 int nDataPoints = newData.length; 666 if (nDataPoints > this.valueHistory.length) { 667 throw new IllegalArgumentException( 668 "More data than series to put them in" 669 ); 670 } 671 int s; // index to select the "series" 672 for (s = 0; s < nDataPoints; s++) { 673 // check whether the "valueHistory" array member exists; if not, 674 // create them: 675 if (this.valueHistory[s] == null) { 676 this.valueHistory[s] = new ValueSequence(this.historyCount); 677 } 678 this.valueHistory[s].enterData(this.newestAt, newData[s]); 679 } 680 fireSeriesChanged(); 681 } 682 683 /** 684 * Appends data at specified index, for loading up with data from file(s). 685 * 686 * @param newData the data 687 * @param insertionIndex the index value at which to put it 688 * @param refresh value of n in "refresh the display on every nth call" 689 * (ignored if <= 0 ) 690 */ 691 public void appendData(float[] newData, int insertionIndex, int refresh) { 692 int nDataPoints = newData.length; 693 if (nDataPoints > this.valueHistory.length) { 694 throw new IllegalArgumentException( 695 "More data than series to put them " + "in" 696 ); 697 } 698 for (int s = 0; s < nDataPoints; s++) { 699 if (this.valueHistory[s] == null) { 700 this.valueHistory[s] = new ValueSequence(this.historyCount); 701 } 702 this.valueHistory[s].enterData(insertionIndex, newData[s]); 703 } 704 if (refresh > 0) { 705 insertionIndex++; 706 if (insertionIndex % refresh == 0) { 707 fireSeriesChanged(); 708 } 709 } 710 } 711 712 /** 713 * Returns the newest time. 714 * 715 * @return The newest time. 716 */ 717 public RegularTimePeriod getNewestTime() { 718 return this.pointsInTime[this.newestAt]; 719 } 720 721 /** 722 * Returns the oldest time. 723 * 724 * @return The oldest time. 725 */ 726 public RegularTimePeriod getOldestTime() { 727 return this.pointsInTime[this.oldestAt]; 728 } 729 730 /** 731 * Returns the x-value. 732 * 733 * @param series the series index (zero-based). 734 * @param item the item index (zero-based). 735 * 736 * @return The value. 737 */ 738 // getXxx() ftns can ignore the "series" argument: 739 // Don't synchronize this!! Instead, synchronize the loop that calls it. 740 public Number getX(int series, int item) { 741 RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 742 return new Long(getX(tp)); 743 } 744 745 /** 746 * Returns the y-value. 747 * 748 * @param series the series index (zero-based). 749 * @param item the item index (zero-based). 750 * 751 * @return The value. 752 */ 753 public double getYValue(int series, int item) { 754 // Don't synchronize this!! 755 // Instead, synchronize the loop that calls it. 756 ValueSequence values = this.valueHistory[series]; 757 return values.getData(translateGet(item)); 758 } 759 760 /** 761 * Returns the y-value. 762 * 763 * @param series the series index (zero-based). 764 * @param item the item index (zero-based). 765 * 766 * @return The value. 767 */ 768 public Number getY(int series, int item) { 769 return new Float(getYValue(series, item)); 770 } 771 772 /** 773 * Returns the start x-value. 774 * 775 * @param series the series index (zero-based). 776 * @param item the item index (zero-based). 777 * 778 * @return The value. 779 */ 780 public Number getStartX(int series, int item) { 781 RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 782 return new Long(tp.getFirstMillisecond(this.workingCalendar)); 783 } 784 785 /** 786 * Returns the end x-value. 787 * 788 * @param series the series index (zero-based). 789 * @param item the item index (zero-based). 790 * 791 * @return The value. 792 */ 793 public Number getEndX(int series, int item) { 794 RegularTimePeriod tp = this.pointsInTime[translateGet(item)]; 795 return new Long(tp.getLastMillisecond(this.workingCalendar)); 796 } 797 798 /** 799 * Returns the start y-value. 800 * 801 * @param series the series index (zero-based). 802 * @param item the item index (zero-based). 803 * 804 * @return The value. 805 */ 806 public Number getStartY(int series, int item) { 807 return getY(series, item); 808 } 809 810 /** 811 * Returns the end y-value. 812 * 813 * @param series the series index (zero-based). 814 * @param item the item index (zero-based). 815 * 816 * @return The value. 817 */ 818 public Number getEndY(int series, int item) { 819 return getY(series, item); 820 } 821 822 /* // "Extras" found useful when analyzing/verifying class behavior: 823 public Number getUntranslatedXValue(int series, int item) 824 { 825 return super.getXValue(series, item); 826 } 827 828 public float getUntranslatedY(int series, int item) 829 { 830 return super.getY(series, item); 831 } */ 832 833 /** 834 * Returns the key for a series. 835 * 836 * @param series the series index (zero-based). 837 * 838 * @return The key. 839 */ 840 public Comparable getSeriesKey(int series) { 841 return this.seriesKeys[series]; 842 } 843 844 /** 845 * Sends a {@link SeriesChangeEvent} to all registered listeners. 846 */ 847 protected void fireSeriesChanged() { 848 seriesChanged(new SeriesChangeEvent(this)); 849 } 850 851 // The next 3 functions override the base-class implementation of 852 // the DomainInfo interface. Using saved limits (updated by 853 // each updateTime() call), improves performance. 854 // 855 856 /** 857 * Returns the minimum x-value in the dataset. 858 * 859 * @param includeInterval a flag that determines whether or not the 860 * x-interval is taken into account. 861 * 862 * @return The minimum value. 863 */ 864 public double getDomainLowerBound(boolean includeInterval) { 865 return this.domainStart.doubleValue(); 866 // a Long kept updated by advanceTime() 867 } 868 869 /** 870 * Returns the maximum x-value in the dataset. 871 * 872 * @param includeInterval a flag that determines whether or not the 873 * x-interval is taken into account. 874 * 875 * @return The maximum value. 876 */ 877 public double getDomainUpperBound(boolean includeInterval) { 878 return this.domainEnd.doubleValue(); 879 // a Long kept updated by advanceTime() 880 } 881 882 /** 883 * Returns the range of the values in this dataset's domain. 884 * 885 * @param includeInterval a flag that determines whether or not the 886 * x-interval is taken into account. 887 * 888 * @return The range. 889 */ 890 public Range getDomainBounds(boolean includeInterval) { 891 if (this.domainRange == null) { 892 findDomainLimits(); 893 } 894 return this.domainRange; 895 } 896 897 /** 898 * Returns the x-value for a time period. 899 * 900 * @param period the period. 901 * 902 * @return The x-value. 903 */ 904 private long getX(RegularTimePeriod period) { 905 switch (this.position) { 906 case (START) : 907 return period.getFirstMillisecond(this.workingCalendar); 908 case (MIDDLE) : 909 return period.getMiddleMillisecond(this.workingCalendar); 910 case (END) : 911 return period.getLastMillisecond(this.workingCalendar); 912 default: 913 return period.getMiddleMillisecond(this.workingCalendar); 914 } 915 } 916 917 // The next 3 functions implement the RangeInfo interface. 918 // Using saved limits (updated by each updateTime() call) significantly 919 // improves performance. WARNING: this code makes the simplifying 920 // assumption that data is never negative. Expand as needed for the 921 // general case. 922 923 /** 924 * Returns the minimum range value. 925 * 926 * @param includeInterval a flag that determines whether or not the 927 * y-interval is taken into account. 928 * 929 * @return The minimum range value. 930 */ 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 public double getRangeUpperBound(boolean includeInterval) { 948 double result = Double.NaN; 949 if (this.maxValue != null) { 950 result = this.maxValue.doubleValue(); 951 } 952 return result; 953 } 954 955 /** 956 * Returns the value range. 957 * 958 * @param includeInterval a flag that determines whether or not the 959 * y-interval is taken into account. 960 * 961 * @return The range. 962 */ 963 public Range getRangeBounds(boolean includeInterval) { 964 if (this.valueRange == null) { 965 double max = getRangeUpperBound(includeInterval); 966 this.valueRange = new Range(0.0, max); 967 } 968 return this.valueRange; 969 } 970 971 }