001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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-2007, by I. H. Thomae and Contributors.
031 *
032 * Original Author: I. H. Thomae (ithomae@ists.dartmouth.edu);
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: DynamicTimeSeriesCollection.java,v 1.11.2.2 2007/02/02 15:15:09 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 22-Nov-2002 : Initial version completed
040 * Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc
041 * (using cached values for min, max, and range); also added
042 * getOldestIndex() and getNewestIndex() ftns so client classes
043 * can use this class as the master "index authority".
044 * 22-Jan-2003 : Made this class stand on its own, rather than extending
045 * class FastTimeSeriesCollection
046 * 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG);
047 * 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG);
048 * 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG);
049 * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG);
050 * 05-May-2004 : Now extends AbstractIntervalXYDataset. This also required a
051 * change to the return type of the getY() method - I'm slightly
052 * unsure of the implications of this, so it might require some
053 * further amendment (DG);
054 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
055 * getYValue() (DG);
056 * 11-Jan-2004 : Removed deprecated code in preparation for the 1.0.0
057 * release (DG);
058 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
059 *
060 */
061
062 package org.jfree.data.time;
063
064 import java.util.Calendar;
065 import java.util.TimeZone;
066
067 import org.jfree.data.DomainInfo;
068 import org.jfree.data.Range;
069 import org.jfree.data.RangeInfo;
070 import org.jfree.data.general.SeriesChangeEvent;
071 import org.jfree.data.xy.AbstractIntervalXYDataset;
072 import org.jfree.data.xy.IntervalXYDataset;
073
074 /**
075 * A dynamic dataset.
076 * <p>
077 * Like FastTimeSeriesCollection, this class is a functional replacement
078 * for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes.
079 * FastTimeSeriesCollection is appropriate for a fixed time range; for
080 * real-time applications this subclass adds the ability to append new
081 * data and discard the oldest.
082 * In this class, the arrays used in FastTimeSeriesCollection become FIFO's.
083 * NOTE:As presented here, all data is assumed >= 0, an assumption which is
084 * embodied only in methods associated with interface RangeInfo.
085 */
086 public class DynamicTimeSeriesCollection extends AbstractIntervalXYDataset
087 implements IntervalXYDataset,
088 DomainInfo,
089 RangeInfo {
090
091 /**
092 * Useful constant for controlling the x-value returned for a time
093 * period.
094 */
095 public static final int START = 0;
096
097 /**
098 * Useful constant for controlling the x-value returned for a time period.
099 */
100 public static final int MIDDLE = 1;
101
102 /**
103 * Useful constant for controlling the x-value returned for a time period.
104 */
105 public static final int END = 2;
106
107 /** The maximum number of items for each series (can be overridden). */
108 private int maximumItemCount = 2000; // an arbitrary safe default value
109
110 /** The history count. */
111 protected int historyCount;
112
113 /** Storage for the series keys. */
114 private Comparable[] seriesKeys;
115
116 /** The time period class - barely used, and could be removed (DG). */
117 private Class timePeriodClass = Minute.class; // default value;
118
119 /** Storage for the x-values. */
120 protected RegularTimePeriod[] pointsInTime;
121
122 /** The number of series. */
123 private int seriesCount;
124
125 /**
126 * A wrapper for a fixed array of float values.
127 */
128 protected class ValueSequence {
129
130 /** Storage for the float values. */
131 float[] dataPoints;
132
133 /**
134 * Default constructor:
135 */
136 public ValueSequence() {
137 this(DynamicTimeSeriesCollection.this.maximumItemCount);
138 }
139
140 /**
141 * Creates a sequence with the specified length.
142 *
143 * @param length the length.
144 */
145 public ValueSequence(int length) {
146 this.dataPoints = new float[length];
147 for (int i = 0; i < length; i++) {
148 this.dataPoints[i] = 0.0f;
149 }
150 }
151
152 /**
153 * Enters data into the storage array.
154 *
155 * @param index the index.
156 * @param value the value.
157 */
158 public void enterData(int index, float value) {
159 this.dataPoints[index] = value;
160 }
161
162 /**
163 * Returns a value from the storage array.
164 *
165 * @param index the index.
166 *
167 * @return The value.
168 */
169 public float getData(int index) {
170 return this.dataPoints[index];
171 }
172 }
173
174 /** An array for storing the objects that represent each series. */
175 protected ValueSequence[] valueHistory;
176
177 /** A working calendar (to recycle) */
178 protected Calendar workingCalendar;
179
180 /**
181 * The position within a time period to return as the x-value (START,
182 * MIDDLE or END).
183 */
184 private int position;
185
186 /**
187 * A flag that indicates that the domain is 'points in time'. If this flag
188 * is true, only the x-value is used to determine the range of values in
189 * the domain, the start and end x-values are ignored.
190 */
191 private boolean domainIsPointsInTime;
192
193 /** index for mapping: points to the oldest valid time & data. */
194 private int oldestAt; // as a class variable, initializes == 0
195
196 /** Index of the newest data item. */
197 private int newestAt;
198
199 // cached values used for interface DomainInfo:
200
201 /** the # of msec by which time advances. */
202 private long deltaTime;
203
204 /** Cached domain start (for use by DomainInfo). */
205 private Long domainStart;
206
207 /** Cached domain end (for use by DomainInfo). */
208 private Long domainEnd;
209
210 /** Cached domain range (for use by DomainInfo). */
211 private Range domainRange;
212
213 // Cached values used for interface RangeInfo: (note minValue pinned at 0)
214 // A single set of extrema covers the entire SeriesCollection
215
216 /** The minimum value. */
217 private Float minValue = new Float(0.0f);
218
219 /** The maximum value. */
220 private Float maxValue = null;
221
222 /** The value range. */
223 private Range valueRange; // autoinit's to null.
224
225 /**
226 * Constructs a dataset with capacity for N series, tied to default
227 * timezone.
228 *
229 * @param nSeries the number of series to be accommodated.
230 * @param nMoments the number of TimePeriods to be spanned.
231 */
232 public DynamicTimeSeriesCollection(int nSeries, int nMoments) {
233
234 this(nSeries, nMoments, new Millisecond(), TimeZone.getDefault());
235 this.newestAt = nMoments - 1;
236
237 }
238
239 /**
240 * Constructs an empty dataset, tied to a specific timezone.
241 *
242 * @param nSeries the number of series to be accommodated
243 * @param nMoments the number of TimePeriods to be spanned
244 * @param zone the timezone.
245 */
246 public DynamicTimeSeriesCollection(int nSeries, int nMoments,
247 TimeZone zone) {
248 this(nSeries, nMoments, new Millisecond(), zone);
249 this.newestAt = nMoments - 1;
250 }
251
252 /**
253 * Creates a new dataset.
254 *
255 * @param nSeries the number of series.
256 * @param nMoments the number of items per series.
257 * @param timeSample a time period sample.
258 */
259 public DynamicTimeSeriesCollection(int nSeries,
260 int nMoments,
261 RegularTimePeriod timeSample) {
262 this(nSeries, nMoments, timeSample, TimeZone.getDefault());
263 }
264
265 /**
266 * Creates a new dataset.
267 *
268 * @param nSeries the number of series.
269 * @param nMoments the number of items per series.
270 * @param timeSample a time period sample.
271 * @param zone the time zone.
272 */
273 public DynamicTimeSeriesCollection(int nSeries,
274 int nMoments,
275 RegularTimePeriod timeSample,
276 TimeZone zone) {
277
278 // the first initialization must precede creation of the ValueSet array:
279 this.maximumItemCount = nMoments; // establishes length of each array
280 this.historyCount = nMoments;
281 this.seriesKeys = new Comparable[nSeries];
282 // initialize the members of "seriesNames" array so they won't be null:
283 for (int i = 0; i < nSeries; i++) {
284 this.seriesKeys[i] = "";
285 }
286 this.newestAt = nMoments - 1;
287 this.valueHistory = new ValueSequence[nSeries];
288 this.timePeriodClass = timeSample.getClass();
289
290 /// Expand the following for all defined TimePeriods:
291 if (this.timePeriodClass == Second.class) {
292 this.pointsInTime = new Second[nMoments];
293 }
294 else if (this.timePeriodClass == Minute.class) {
295 this.pointsInTime = new Minute[nMoments];
296 }
297 else if (this.timePeriodClass == Hour.class) {
298 this.pointsInTime = new Hour[nMoments];
299 }
300 /// .. etc....
301 this.workingCalendar = Calendar.getInstance(zone);
302 this.position = START;
303 this.domainIsPointsInTime = true;
304 }
305
306 /**
307 * Fill the pointsInTime with times using TimePeriod.next():
308 * Will silently return if the time array was already populated.
309 *
310 * Also computes the data cached for later use by
311 * methods implementing the DomainInfo interface:
312 *
313 * @param start the start.
314 *
315 * @return ??.
316 */
317 public synchronized long setTimeBase(RegularTimePeriod start) {
318
319 if (this.pointsInTime[0] == null) {
320 this.pointsInTime[0] = start;
321 for (int i = 1; i < this.historyCount; i++) {
322 this.pointsInTime[i] = this.pointsInTime[i - 1].next();
323 }
324 }
325 long oldestL = this.pointsInTime[0].getFirstMillisecond(
326 this.workingCalendar
327 );
328 long nextL = this.pointsInTime[1].getFirstMillisecond(
329 this.workingCalendar
330 );
331 this.deltaTime = nextL - oldestL;
332 this.oldestAt = 0;
333 this.newestAt = this.historyCount - 1;
334 findDomainLimits();
335 return this.deltaTime;
336
337 }
338
339 /**
340 * Finds the domain limits. Note: this doesn't need to be synchronized
341 * because it's called from within another method that already is.
342 */
343 protected void findDomainLimits() {
344
345 long startL = getOldestTime().getFirstMillisecond(this.workingCalendar);
346 long endL;
347 if (this.domainIsPointsInTime) {
348 endL = getNewestTime().getFirstMillisecond(this.workingCalendar);
349 }
350 else {
351 endL = getNewestTime().getLastMillisecond(this.workingCalendar);
352 }
353 this.domainStart = new Long(startL);
354 this.domainEnd = new Long(endL);
355 this.domainRange = new Range(startL, endL);
356
357 }
358
359 /**
360 * Returns the x position type (START, MIDDLE or END).
361 *
362 * @return The x position type.
363 */
364 public int getPosition() {
365 return this.position;
366 }
367
368 /**
369 * Sets the x position type (START, MIDDLE or END).
370 *
371 * @param position The x position type.
372 */
373 public void setPosition(int position) {
374 this.position = position;
375 }
376
377 /**
378 * Adds a series to the dataset. Only the y-values are supplied, the
379 * x-values are specified elsewhere.
380 *
381 * @param values the y-values.
382 * @param seriesNumber the series index (zero-based).
383 * @param seriesKey the series key.
384 *
385 * Use this as-is during setup only, or add the synchronized keyword around
386 * the copy loop.
387 */
388 public void addSeries(float[] values,
389 int seriesNumber, Comparable seriesKey) {
390
391 invalidateRangeInfo();
392 int i;
393 if (values == null) {
394 throw new IllegalArgumentException("TimeSeriesDataset.addSeries(): "
395 + "cannot add null array of values.");
396 }
397 if (seriesNumber >= this.valueHistory.length) {
398 throw new IllegalArgumentException("TimeSeriesDataset.addSeries(): "
399 + "cannot add more series than specified in c'tor");
400 }
401 if (this.valueHistory[seriesNumber] == null) {
402 this.valueHistory[seriesNumber]
403 = new ValueSequence(this.historyCount);
404 this.seriesCount++;
405 }
406 // But if that series array already exists, just overwrite its contents
407
408 // Avoid IndexOutOfBoundsException:
409 int srcLength = values.length;
410 int copyLength = this.historyCount;
411 boolean fillNeeded = false;
412 if (srcLength < this.historyCount) {
413 fillNeeded = true;
414 copyLength = srcLength;
415 }
416 //{
417 for (i = 0; i < copyLength; i++) { // deep copy from values[], caller
418 // can safely discard that array
419 this.valueHistory[seriesNumber].enterData(i, values[i]);
420 }
421 if (fillNeeded) {
422 for (i = copyLength; i < this.historyCount; i++) {
423 this.valueHistory[seriesNumber].enterData(i, 0.0f);
424 }
425 }
426 //}
427 if (seriesKey != null) {
428 this.seriesKeys[seriesNumber] = seriesKey;
429 }
430 fireSeriesChanged();
431
432 }
433
434 /**
435 * Sets the name of a series. If planning to add values individually.
436 *
437 * @param seriesNumber the series.
438 * @param key the new key.
439 */
440 public void setSeriesKey(int seriesNumber, Comparable key) {
441 this.seriesKeys[seriesNumber] = key;
442 }
443
444 /**
445 * Adds a value to a series.
446 *
447 * @param seriesNumber the series index.
448 * @param index ??.
449 * @param value the value.
450 */
451 public void addValue(int seriesNumber, int index, float value) {
452
453 invalidateRangeInfo();
454 if (seriesNumber >= this.valueHistory.length) {
455 throw new IllegalArgumentException(
456 "TimeSeriesDataset.addValue(): series #"
457 + seriesNumber + "unspecified in c'tor"
458 );
459 }
460 if (this.valueHistory[seriesNumber] == null) {
461 this.valueHistory[seriesNumber]
462 = new ValueSequence(this.historyCount);
463 this.seriesCount++;
464 }
465 // But if that series array already exists, just overwrite its contents
466 //synchronized(this)
467 //{
468 this.valueHistory[seriesNumber].enterData(index, value);
469 //}
470 fireSeriesChanged();
471 }
472
473 /**
474 * Returns the number of series in the collection.
475 *
476 * @return The series count.
477 */
478 public int getSeriesCount() {
479 return this.seriesCount;
480 }
481
482 /**
483 * Returns the number of items in a series.
484 * <p>
485 * For this implementation, all series have the same number of items.
486 *
487 * @param series the series index (zero-based).
488 *
489 * @return The item count.
490 */
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 public Number getX(int series, int item) {
743 RegularTimePeriod tp = this.pointsInTime[translateGet(item)];
744 return new Long(getX(tp));
745 }
746
747 /**
748 * Returns the y-value.
749 *
750 * @param series the series index (zero-based).
751 * @param item the item index (zero-based).
752 *
753 * @return The value.
754 */
755 public double getYValue(int series, int item) {
756 // Don't synchronize this!!
757 // Instead, synchronize the loop that calls it.
758 ValueSequence values = this.valueHistory[series];
759 return values.getData(translateGet(item));
760 }
761
762 /**
763 * Returns the y-value.
764 *
765 * @param series the series index (zero-based).
766 * @param item the item index (zero-based).
767 *
768 * @return The value.
769 */
770 public Number getY(int series, int item) {
771 return new Float(getYValue(series, item));
772 }
773
774 /**
775 * Returns the start x-value.
776 *
777 * @param series the series index (zero-based).
778 * @param item the item index (zero-based).
779 *
780 * @return The value.
781 */
782 public Number getStartX(int series, int item) {
783 RegularTimePeriod tp = this.pointsInTime[translateGet(item)];
784 return new Long(tp.getFirstMillisecond(this.workingCalendar));
785 }
786
787 /**
788 * Returns the end x-value.
789 *
790 * @param series the series index (zero-based).
791 * @param item the item index (zero-based).
792 *
793 * @return The value.
794 */
795 public Number getEndX(int series, int item) {
796 RegularTimePeriod tp = this.pointsInTime[translateGet(item)];
797 return new Long(tp.getLastMillisecond(this.workingCalendar));
798 }
799
800 /**
801 * Returns the start y-value.
802 *
803 * @param series the series index (zero-based).
804 * @param item the item index (zero-based).
805 *
806 * @return The value.
807 */
808 public Number getStartY(int series, int item) {
809 return getY(series, item);
810 }
811
812 /**
813 * Returns the end y-value.
814 *
815 * @param series the series index (zero-based).
816 * @param item the item index (zero-based).
817 *
818 * @return The value.
819 */
820 public Number getEndY(int series, int item) {
821 return getY(series, item);
822 }
823
824 /* // "Extras" found useful when analyzing/verifying class behavior:
825 public Number getUntranslatedXValue(int series, int item)
826 {
827 return super.getXValue(series, item);
828 }
829
830 public float getUntranslatedY(int series, int item)
831 {
832 return super.getY(series, item);
833 } */
834
835 /**
836 * Returns the key for a series.
837 *
838 * @param series the series index (zero-based).
839 *
840 * @return The key.
841 */
842 public Comparable getSeriesKey(int series) {
843 return this.seriesKeys[series];
844 }
845
846 /**
847 * Sends a {@link SeriesChangeEvent} to all registered listeners.
848 */
849 protected void fireSeriesChanged() {
850 seriesChanged(new SeriesChangeEvent(this));
851 }
852
853 // The next 3 functions override the base-class implementation of
854 // the DomainInfo interface. Using saved limits (updated by
855 // each updateTime() call), improves performance.
856 //
857
858 /**
859 * Returns the minimum x-value in the dataset.
860 *
861 * @param includeInterval a flag that determines whether or not the
862 * x-interval is taken into account.
863 *
864 * @return The minimum value.
865 */
866 public double getDomainLowerBound(boolean includeInterval) {
867 return this.domainStart.doubleValue();
868 // a Long kept updated by advanceTime()
869 }
870
871 /**
872 * Returns the maximum x-value in the dataset.
873 *
874 * @param includeInterval a flag that determines whether or not the
875 * x-interval is taken into account.
876 *
877 * @return The maximum value.
878 */
879 public double getDomainUpperBound(boolean includeInterval) {
880 return this.domainEnd.doubleValue();
881 // a Long kept updated by advanceTime()
882 }
883
884 /**
885 * Returns the range of the values in this dataset's domain.
886 *
887 * @param includeInterval a flag that determines whether or not the
888 * x-interval is taken into account.
889 *
890 * @return The range.
891 */
892 public Range getDomainBounds(boolean includeInterval) {
893 if (this.domainRange == null) {
894 findDomainLimits();
895 }
896 return this.domainRange;
897 }
898
899 /**
900 * Returns the x-value for a time period.
901 *
902 * @param period the period.
903 *
904 * @return The x-value.
905 */
906 private long getX(RegularTimePeriod period) {
907 switch (this.position) {
908 case (START) :
909 return period.getFirstMillisecond(this.workingCalendar);
910 case (MIDDLE) :
911 return period.getMiddleMillisecond(this.workingCalendar);
912 case (END) :
913 return period.getLastMillisecond(this.workingCalendar);
914 default:
915 return period.getMiddleMillisecond(this.workingCalendar);
916 }
917 }
918
919 // The next 3 functions implement the RangeInfo interface.
920 // Using saved limits (updated by each updateTime() call) significantly
921 // improves performance. WARNING: this code makes the simplifying
922 // assumption that data is never negative. Expand as needed for the
923 // general case.
924
925 /**
926 * Returns the minimum range value.
927 *
928 * @param includeInterval a flag that determines whether or not the
929 * y-interval is taken into account.
930 *
931 * @return The minimum range value.
932 */
933 public double getRangeLowerBound(boolean includeInterval) {
934 double result = Double.NaN;
935 if (this.minValue != null) {
936 result = this.minValue.doubleValue();
937 }
938 return result;
939 }
940
941 /**
942 * Returns the maximum range value.
943 *
944 * @param includeInterval a flag that determines whether or not the
945 * y-interval is taken into account.
946 *
947 * @return The maximum range value.
948 */
949 public double getRangeUpperBound(boolean includeInterval) {
950 double result = Double.NaN;
951 if (this.maxValue != null) {
952 result = this.maxValue.doubleValue();
953 }
954 return result;
955 }
956
957 /**
958 * Returns the value range.
959 *
960 * @param includeInterval a flag that determines whether or not the
961 * y-interval is taken into account.
962 *
963 * @return The range.
964 */
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 }