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