Source for org.jfree.data.time.DynamicTimeSeriesCollection

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------------------------
  28:  * DynamicTimeSeriesCollection.java
  29:  * --------------------------------
  30:  * (C) Copyright 2002-2007, by I. H. Thomae and Contributors.
  31:  *
  32:  * Original Author:  I. H. Thomae (ithomae@ists.dartmouth.edu);
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: DynamicTimeSeriesCollection.java,v 1.11.2.2 2007/02/02 15:15:09 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 22-Nov-2002 : Initial version completed
  40:  *    Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc
  41:  *               (using cached values for min, max, and range); also added
  42:  *               getOldestIndex() and getNewestIndex() ftns so client classes
  43:  *               can use this class as the master "index authority".
  44:  * 22-Jan-2003 : Made this class stand on its own, rather than extending
  45:  *               class FastTimeSeriesCollection
  46:  * 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG);
  47:  * 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG);
  48:  * 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG);
  49:  * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG);
  50:  * 05-May-2004 : Now extends AbstractIntervalXYDataset.  This also required a
  51:  *               change to the return type of the getY() method - I'm slightly
  52:  *               unsure of the implications of this, so it might require some
  53:  *               further amendment (DG);
  54:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  55:  *               getYValue() (DG);
  56:  * 11-Jan-2004 : Removed deprecated code in preparation for the 1.0.0 
  57:  *               release (DG);
  58:  * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
  59:  * 
  60:  */
  61: 
  62: package org.jfree.data.time;
  63: 
  64: import java.util.Calendar;
  65: import java.util.TimeZone;
  66: 
  67: import org.jfree.data.DomainInfo;
  68: import org.jfree.data.Range;
  69: import org.jfree.data.RangeInfo;
  70: import org.jfree.data.general.SeriesChangeEvent;
  71: import org.jfree.data.xy.AbstractIntervalXYDataset;
  72: import org.jfree.data.xy.IntervalXYDataset;
  73: 
  74: /**
  75:  * A dynamic dataset.
  76:  * <p>
  77:  * Like FastTimeSeriesCollection, this class is a functional replacement
  78:  * for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes.
  79:  * FastTimeSeriesCollection is appropriate for a fixed time range; for
  80:  * real-time applications this subclass adds the ability to append new
  81:  * data and discard the oldest.
  82:  * In this class, the arrays used in FastTimeSeriesCollection become FIFO's.
  83:  * NOTE:As presented here, all data is assumed >= 0, an assumption which is
  84:  * embodied only in methods associated with interface RangeInfo.
  85:  */
  86: public class DynamicTimeSeriesCollection extends AbstractIntervalXYDataset
  87:                                          implements IntervalXYDataset,
  88:                                                     DomainInfo,
  89:                                                     RangeInfo {
  90: 
  91:     /** 
  92:      * Useful constant for controlling the x-value returned for a time 
  93:      * period. 
  94:      */
  95:     public static final int START = 0;
  96: 
  97:     /** 
  98:      * Useful constant for controlling the x-value returned for a time period. 
  99:      */
 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: }