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
087: AbstractIntervalXYDataset implements IntervalXYDataset,
088: 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 & 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:
233: this (nSeries, nMoments, new Millisecond(), TimeZone
234: .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, int nMoments,
260: RegularTimePeriod timeSample) {
261: this (nSeries, nMoments, timeSample, TimeZone.getDefault());
262: }
263:
264: /**
265: * Creates a new dataset.
266: *
267: * @param nSeries the number of series.
268: * @param nMoments the number of items per series.
269: * @param timeSample a time period sample.
270: * @param zone the time zone.
271: */
272: public DynamicTimeSeriesCollection(int nSeries, int nMoments,
273: RegularTimePeriod timeSample, TimeZone zone) {
274:
275: // the first initialization must precede creation of the ValueSet array:
276: this .maximumItemCount = nMoments; // establishes length of each array
277: this .historyCount = nMoments;
278: this .seriesKeys = new Comparable[nSeries];
279: // initialize the members of "seriesNames" array so they won't be null:
280: for (int i = 0; i < nSeries; i++) {
281: this .seriesKeys[i] = "";
282: }
283: this .newestAt = nMoments - 1;
284: this .valueHistory = new ValueSequence[nSeries];
285: this .timePeriodClass = timeSample.getClass();
286:
287: /// Expand the following for all defined TimePeriods:
288: if (this .timePeriodClass == Second.class) {
289: this .pointsInTime = new Second[nMoments];
290: } else if (this .timePeriodClass == Minute.class) {
291: this .pointsInTime = new Minute[nMoments];
292: } else if (this .timePeriodClass == Hour.class) {
293: this .pointsInTime = new Hour[nMoments];
294: }
295: /// .. etc....
296: this .workingCalendar = Calendar.getInstance(zone);
297: this .position = START;
298: this .domainIsPointsInTime = true;
299: }
300:
301: /**
302: * Fill the pointsInTime with times using TimePeriod.next():
303: * Will silently return if the time array was already populated.
304: *
305: * Also computes the data cached for later use by
306: * methods implementing the DomainInfo interface:
307: *
308: * @param start the start.
309: *
310: * @return ??.
311: */
312: public synchronized long setTimeBase(RegularTimePeriod start) {
313:
314: if (this .pointsInTime[0] == null) {
315: this .pointsInTime[0] = start;
316: for (int i = 1; i < this .historyCount; i++) {
317: this .pointsInTime[i] = this .pointsInTime[i - 1].next();
318: }
319: }
320: long oldestL = this .pointsInTime[0]
321: .getFirstMillisecond(this .workingCalendar);
322: long nextL = this .pointsInTime[1]
323: .getFirstMillisecond(this .workingCalendar);
324: this .deltaTime = nextL - oldestL;
325: this .oldestAt = 0;
326: this .newestAt = this .historyCount - 1;
327: findDomainLimits();
328: return this .deltaTime;
329:
330: }
331:
332: /**
333: * Finds the domain limits. Note: this doesn't need to be synchronized
334: * because it's called from within another method that already is.
335: */
336: protected void findDomainLimits() {
337:
338: long startL = getOldestTime().getFirstMillisecond(
339: this .workingCalendar);
340: long endL;
341: if (this .domainIsPointsInTime) {
342: endL = getNewestTime().getFirstMillisecond(
343: this .workingCalendar);
344: } else {
345: endL = getNewestTime().getLastMillisecond(
346: this .workingCalendar);
347: }
348: this .domainStart = new Long(startL);
349: this .domainEnd = new Long(endL);
350: this .domainRange = new Range(startL, endL);
351:
352: }
353:
354: /**
355: * Returns the x position type (START, MIDDLE or END).
356: *
357: * @return The x position type.
358: */
359: public int getPosition() {
360: return this .position;
361: }
362:
363: /**
364: * Sets the x position type (START, MIDDLE or END).
365: *
366: * @param position The x position type.
367: */
368: public void setPosition(int position) {
369: this .position = position;
370: }
371:
372: /**
373: * Adds a series to the dataset. Only the y-values are supplied, the
374: * x-values are specified elsewhere.
375: *
376: * @param values the y-values.
377: * @param seriesNumber the series index (zero-based).
378: * @param seriesKey the series key.
379: *
380: * Use this as-is during setup only, or add the synchronized keyword around
381: * the copy loop.
382: */
383: public void addSeries(float[] values, int seriesNumber,
384: Comparable seriesKey) {
385:
386: invalidateRangeInfo();
387: int i;
388: if (values == null) {
389: throw new IllegalArgumentException(
390: "TimeSeriesDataset.addSeries(): "
391: + "cannot add null array of values.");
392: }
393: if (seriesNumber >= this .valueHistory.length) {
394: throw new IllegalArgumentException(
395: "TimeSeriesDataset.addSeries(): "
396: + "cannot add more series than specified in c'tor");
397: }
398: if (this .valueHistory[seriesNumber] == null) {
399: this .valueHistory[seriesNumber] = new ValueSequence(
400: this .historyCount);
401: this .seriesCount++;
402: }
403: // But if that series array already exists, just overwrite its contents
404:
405: // Avoid IndexOutOfBoundsException:
406: int srcLength = values.length;
407: int copyLength = this .historyCount;
408: boolean fillNeeded = false;
409: if (srcLength < this .historyCount) {
410: fillNeeded = true;
411: copyLength = srcLength;
412: }
413: //{
414: for (i = 0; i < copyLength; i++) { // deep copy from values[], caller
415: // can safely discard that array
416: this .valueHistory[seriesNumber].enterData(i, values[i]);
417: }
418: if (fillNeeded) {
419: for (i = copyLength; i < this .historyCount; i++) {
420: this .valueHistory[seriesNumber].enterData(i, 0.0f);
421: }
422: }
423: //}
424: if (seriesKey != null) {
425: this .seriesKeys[seriesNumber] = seriesKey;
426: }
427: fireSeriesChanged();
428:
429: }
430:
431: /**
432: * Sets the name of a series. If planning to add values individually.
433: *
434: * @param seriesNumber the series.
435: * @param key the new key.
436: */
437: public void setSeriesKey(int seriesNumber, Comparable key) {
438: this .seriesKeys[seriesNumber] = key;
439: }
440:
441: /**
442: * Adds a value to a series.
443: *
444: * @param seriesNumber the series index.
445: * @param index ??.
446: * @param value the value.
447: */
448: public void addValue(int seriesNumber, int index, float value) {
449:
450: invalidateRangeInfo();
451: if (seriesNumber >= this .valueHistory.length) {
452: throw new IllegalArgumentException(
453: "TimeSeriesDataset.addValue(): series #"
454: + seriesNumber + "unspecified in c'tor");
455: }
456: if (this .valueHistory[seriesNumber] == null) {
457: this .valueHistory[seriesNumber] = new ValueSequence(
458: this .historyCount);
459: this .seriesCount++;
460: }
461: // But if that series array already exists, just overwrite its contents
462: //synchronized(this)
463: //{
464: this .valueHistory[seriesNumber].enterData(index, value);
465: //}
466: fireSeriesChanged();
467: }
468:
469: /**
470: * Returns the number of series in the collection.
471: *
472: * @return The series count.
473: */
474: public int getSeriesCount() {
475: return this .seriesCount;
476: }
477:
478: /**
479: * Returns the number of items in a series.
480: * <p>
481: * For this implementation, all series have the same number of items.
482: *
483: * @param series the series index (zero-based).
484: *
485: * @return The item count.
486: */
487: public int getItemCount(int series) { // all arrays equal length,
488: // so ignore argument:
489: return this .historyCount;
490: }
491:
492: // Methods for managing the FIFO's:
493:
494: /**
495: * Re-map an index, for use in retrieving data.
496: *
497: * @param toFetch the index.
498: *
499: * @return The translated index.
500: */
501: protected int translateGet(int toFetch) {
502: if (this .oldestAt == 0) {
503: return toFetch; // no translation needed
504: }
505: // else [implicit here]
506: int newIndex = toFetch + this .oldestAt;
507: if (newIndex >= this .historyCount) {
508: newIndex -= this .historyCount;
509: }
510: return newIndex;
511: }
512:
513: /**
514: * Returns the actual index to a time offset by "delta" from newestAt.
515: *
516: * @param delta the delta.
517: *
518: * @return The offset.
519: */
520: public int offsetFromNewest(int delta) {
521: return wrapOffset(this .newestAt + delta);
522: }
523:
524: /**
525: * ??
526: *
527: * @param delta ??
528: *
529: * @return The offset.
530: */
531: public int offsetFromOldest(int delta) {
532: return wrapOffset(this .oldestAt + delta);
533: }
534:
535: /**
536: * ??
537: *
538: * @param protoIndex the index.
539: *
540: * @return The offset.
541: */
542: protected int wrapOffset(int protoIndex) {
543: int tmp = protoIndex;
544: if (tmp >= this .historyCount) {
545: tmp -= this .historyCount;
546: } else if (tmp < 0) {
547: tmp += this .historyCount;
548: }
549: return tmp;
550: }
551:
552: /**
553: * Adjust the array offset as needed when a new time-period is added:
554: * Increments the indices "oldestAt" and "newestAt", mod(array length),
555: * zeroes the series values at newestAt, returns the new TimePeriod.
556: *
557: * @return The new time period.
558: */
559: public synchronized RegularTimePeriod advanceTime() {
560: RegularTimePeriod nextInstant = this .pointsInTime[this .newestAt]
561: .next();
562: this .newestAt = this .oldestAt; // newestAt takes value previously held
563: // by oldestAT
564: /***
565: * The next 10 lines or so should be expanded if data can be negative
566: ***/
567: // if the oldest data contained a maximum Y-value, invalidate the stored
568: // Y-max and Y-range data:
569: boolean extremaChanged = false;
570: float oldMax = 0.0f;
571: if (this .maxValue != null) {
572: oldMax = this .maxValue.floatValue();
573: }
574: for (int s = 0; s < getSeriesCount(); s++) {
575: if (this .valueHistory[s].getData(this .oldestAt) == oldMax) {
576: extremaChanged = true;
577: }
578: if (extremaChanged) {
579: break;
580: }
581: }
582: /*** If data can be < 0, add code here to check the minimum **/
583: if (extremaChanged) {
584: invalidateRangeInfo();
585: }
586: // wipe the next (about to be used) set of data slots
587: float wiper = (float) 0.0;
588: for (int s = 0; s < getSeriesCount(); s++) {
589: this .valueHistory[s].enterData(this .newestAt, wiper);
590: }
591: // Update the array of TimePeriods:
592: this .pointsInTime[this .newestAt] = nextInstant;
593: // Now advance "oldestAt", wrapping at end of the array
594: this .oldestAt++;
595: if (this .oldestAt >= this .historyCount) {
596: this .oldestAt = 0;
597: }
598: // Update the domain limits:
599: long startL = this .domainStart.longValue(); //(time is kept in msec)
600: this .domainStart = new Long(startL + this .deltaTime);
601: long endL = this .domainEnd.longValue();
602: this .domainEnd = new Long(endL + this .deltaTime);
603: this .domainRange = new Range(startL, endL);
604: fireSeriesChanged();
605: return nextInstant;
606: }
607:
608: // If data can be < 0, the next 2 methods should be modified
609:
610: /**
611: * Invalidates the range info.
612: */
613: public void invalidateRangeInfo() {
614: this .maxValue = null;
615: this .valueRange = null;
616: }
617:
618: /**
619: * Returns the maximum value.
620: *
621: * @return The maximum value.
622: */
623: protected double findMaxValue() {
624: double max = 0.0f;
625: for (int s = 0; s < getSeriesCount(); s++) {
626: for (int i = 0; i < this .historyCount; i++) {
627: double tmp = getYValue(s, i);
628: if (tmp > max) {
629: max = tmp;
630: }
631: }
632: }
633: return max;
634: }
635:
636: /** End, positive-data-only code **/
637:
638: /**
639: * Returns the index of the oldest data item.
640: *
641: * @return The index.
642: */
643: public int getOldestIndex() {
644: return this .oldestAt;
645: }
646:
647: /**
648: * Returns the index of the newest data item.
649: *
650: * @return The index.
651: */
652: public int getNewestIndex() {
653: return this .newestAt;
654: }
655:
656: // appendData() writes new data at the index position given by newestAt/
657: // When adding new data dynamically, use advanceTime(), followed by this:
658: /**
659: * Appends new data.
660: *
661: * @param newData the data.
662: */
663: public void appendData(float[] newData) {
664: int nDataPoints = newData.length;
665: if (nDataPoints > this .valueHistory.length) {
666: throw new IllegalArgumentException(
667: "More data than series to put them in");
668: }
669: int s; // index to select the "series"
670: for (s = 0; s < nDataPoints; s++) {
671: // check whether the "valueHistory" array member exists; if not,
672: // create them:
673: if (this .valueHistory[s] == null) {
674: this .valueHistory[s] = new ValueSequence(
675: this .historyCount);
676: }
677: this .valueHistory[s].enterData(this .newestAt, newData[s]);
678: }
679: fireSeriesChanged();
680: }
681:
682: /**
683: * Appends data at specified index, for loading up with data from file(s).
684: *
685: * @param newData the data
686: * @param insertionIndex the index value at which to put it
687: * @param refresh value of n in "refresh the display on every nth call"
688: * (ignored if <= 0 )
689: */
690: public void appendData(float[] newData, int insertionIndex,
691: 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: for (int s = 0; s < nDataPoints; s++) {
698: if (this .valueHistory[s] == null) {
699: this .valueHistory[s] = new ValueSequence(
700: 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: }
|