1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, 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 under the terms
  10. * of the GNU Lesser General Public License as published by the Free Software Foundation;
  11. * either version 2.1 of the License, or (at your option) any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  14. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License along with this
  18. * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. *
  21. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  22. * in the United States and other countries.]
  23. *
  24. * --------------------------------
  25. * DynamicTimeSeriesCollection.java
  26. * --------------------------------
  27. * (C) Copyright 2002-2005, by I. H. Thomae and Contributors.
  28. *
  29. * Original Author: I. H. Thomae (ithomae@ists.dartmouth.edu);
  30. * Contributor(s): David Gilbert (for Object Refinery Limited);
  31. *
  32. * $Id: DynamicTimeSeriesCollection.java,v 1.8 2005/01/14 17:29:48 mungady Exp $
  33. *
  34. * Changes
  35. * -------
  36. * 22-Nov-2002 : Initial version completed
  37. * Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc
  38. * (using cached values for min, max, and range); also added
  39. * getOldestIndex() and getNewestIndex() ftns so client classes
  40. * can use this class as the master "index authority".
  41. * 22-Jan-2003 : Made this class stand on its own, rather than extending
  42. * class FastTimeSeriesCollection
  43. * 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG);
  44. * 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG);
  45. * 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG);
  46. * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG);
  47. * 05-May-2004 : Now extends AbstractIntervalXYDataset. This also required a
  48. * change to the return type of the getY() method - I'm slightly
  49. * unsure of the implications of this, so it might require some
  50. * further amendment (DG);
  51. * 15-Jul-2004 : Switched getX() with getXValue() and getY() with getYValue() (DG);
  52. * 11-Jan-2004 : Removed deprecated code in preparation for the 1.0.0 release (DG);
  53. *
  54. */
  55. package org.jfree.data.time;
  56. import java.util.Calendar;
  57. import java.util.TimeZone;
  58. import org.jfree.data.DomainInfo;
  59. import org.jfree.data.Range;
  60. import org.jfree.data.RangeInfo;
  61. import org.jfree.data.general.SeriesChangeEvent;
  62. import org.jfree.data.xy.AbstractIntervalXYDataset;
  63. import org.jfree.data.xy.IntervalXYDataset;
  64. /**
  65. * A dynamic dataset.
  66. * <p>
  67. * Like FastTimeSeriesCollection, this class is a functional replacement
  68. * for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes.
  69. * FastTimeSeriesCollection is appropriate for a fixed time range; for
  70. * real-time applications this subclass adds the ability to append new
  71. * data and discard the oldest.
  72. * In this class, the arrays used in FastTimeSeriesCollection become FIFO's.
  73. * NOTE:As presented here, all data is assumed >= 0, an assumption which is
  74. * embodied only in methods associated with interface RangeInfo.
  75. *
  76. * @author Irv Thomae.
  77. */
  78. public class DynamicTimeSeriesCollection extends AbstractIntervalXYDataset
  79. implements IntervalXYDataset,
  80. DomainInfo,
  81. RangeInfo {
  82. /** Useful constant for controlling the x-value returned for a time period. */
  83. public static final int START = 0;
  84. /** Useful constant for controlling the x-value returned for a time period. */
  85. public static final int MIDDLE = 1;
  86. /** Useful constant for controlling the x-value returned for a time period. */
  87. public static final int END = 2;
  88. /** The maximum number of items for each series (can be overridden). */
  89. private int maximumItemCount = 2000; // an arbitrary safe default value
  90. /** The history count. */
  91. protected int historyCount;
  92. /** Storage for the series names. */
  93. private String[] seriesNames;
  94. /** The time period class - barely used, and could be removed (DG). */
  95. private Class timePeriodClass = Minute.class; // default value;
  96. /** Storage for the x-values. */
  97. protected RegularTimePeriod[] pointsInTime;
  98. /** The number of series. */
  99. private int seriesCount;
  100. /**
  101. * A wrapper for a fixed array of float values.
  102. */
  103. protected class ValueSequence {
  104. /** Storage for the float values. */
  105. float[] dataPoints;
  106. /**
  107. * Default constructor:
  108. */
  109. public ValueSequence() {
  110. this(DynamicTimeSeriesCollection.this.maximumItemCount);
  111. }
  112. /**
  113. * Creates a sequence with the specified length.
  114. *
  115. * @param length the length.
  116. */
  117. public ValueSequence(int length) {
  118. this.dataPoints = new float[length];
  119. for (int i = 0; i < length; i++) {
  120. this.dataPoints[i] = 0.0f;
  121. }
  122. }
  123. /**
  124. * Enters data into the storage array.
  125. *
  126. * @param index the index.
  127. * @param value the value.
  128. */
  129. public void enterData(int index, float value) {
  130. this.dataPoints[index] = value;
  131. }
  132. /**
  133. * Returns a value from the storage array.
  134. *
  135. * @param index the index.
  136. *
  137. * @return The value.
  138. */
  139. public float getData(int index) {
  140. return this.dataPoints[index];
  141. }
  142. }
  143. /** An array for storing the objects that represent each series. */
  144. protected ValueSequence[] valueHistory;
  145. /** A working calendar (to recycle) */
  146. protected Calendar workingCalendar;
  147. /** The position within a time period to return as the x-value (START, MIDDLE or END). */
  148. private int position;
  149. /**
  150. * A flag that indicates that the domain is 'points in time'. If this flag is true, only
  151. * the x-value is used to determine the range of values in the domain, the start and end
  152. * x-values are ignored.
  153. */
  154. private boolean domainIsPointsInTime;
  155. /** index for mapping: points to the oldest valid time & data. */
  156. private int oldestAt; // as a class variable, initializes == 0
  157. /** Index of the newest data item. */
  158. private int newestAt;
  159. // cached values used for interface DomainInfo:
  160. /** the # of msec by which time advances. */
  161. private long deltaTime;
  162. /** Cached domain start (for use by DomainInfo). */
  163. private Long domainStart;
  164. /** Cached domain end (for use by DomainInfo). */
  165. private Long domainEnd;
  166. /** Cached domain range (for use by DomainInfo). */
  167. private Range domainRange;
  168. // Cached values used for interface RangeInfo: (note minValue pinned at 0)
  169. // A single set of extrema covers the entire SeriesCollection
  170. /** The minimum value. */
  171. private Float minValue = new Float(0.0f);
  172. /** The maximum value. */
  173. private Float maxValue = null;
  174. /** The value range. */
  175. private Range valueRange; // autoinit's to null.
  176. /**
  177. * Constructs a dataset with capacity for N series, tied to default timezone.
  178. *
  179. * @param nSeries the number of series to be accommodated.
  180. * @param nMoments the number of TimePeriods to be spanned.
  181. */
  182. public DynamicTimeSeriesCollection(int nSeries, int nMoments) {
  183. this(nSeries, nMoments, new Millisecond(), TimeZone.getDefault());
  184. this.newestAt = nMoments - 1;
  185. }
  186. /**
  187. * Constructs an empty dataset, tied to a specific timezone.
  188. *
  189. * @param nSeries the number of series to be accommodated
  190. * @param nMoments the number of TimePeriods to be spanned
  191. * @param zone the timezone.
  192. */
  193. public DynamicTimeSeriesCollection(int nSeries, int nMoments, TimeZone zone) {
  194. this(nSeries, nMoments, new Millisecond(), zone);
  195. this.newestAt = nMoments - 1;
  196. }
  197. /**
  198. * Creates a new dataset.
  199. *
  200. * @param nSeries the number of series.
  201. * @param nMoments the number of items per series.
  202. * @param timeSample a time period sample.
  203. */
  204. public DynamicTimeSeriesCollection(int nSeries,
  205. int nMoments,
  206. RegularTimePeriod timeSample) {
  207. this(nSeries, nMoments, timeSample, TimeZone.getDefault());
  208. }
  209. /**
  210. * Creates a new dataset.
  211. *
  212. * @param nSeries the number of series.
  213. * @param nMoments the number of items per series.
  214. * @param timeSample a time period sample.
  215. * @param zone the time zone.
  216. */
  217. public DynamicTimeSeriesCollection(int nSeries,
  218. int nMoments,
  219. RegularTimePeriod timeSample,
  220. TimeZone zone) {
  221. // the first initialization must precede creation of the ValueSet array:
  222. this.maximumItemCount = nMoments; // establishes length of each array
  223. this.historyCount = nMoments;
  224. this.seriesNames = new String[nSeries];
  225. // initialize the members of "seriesNames" array so they won't be null:
  226. for (int i = 0; i < nSeries; i++) {
  227. this.seriesNames[i] = "";
  228. }
  229. this.newestAt = nMoments - 1;
  230. this.valueHistory = new ValueSequence[nSeries];
  231. this.timePeriodClass = timeSample.getClass();
  232. /// Expand the following for all defined TimePeriods:
  233. if (this.timePeriodClass == Second.class) {
  234. this.pointsInTime = new Second[nMoments];
  235. }
  236. else if (this.timePeriodClass == Minute.class) {
  237. this.pointsInTime = new Minute[nMoments];
  238. }
  239. else if (this.timePeriodClass == Hour.class) {
  240. this.pointsInTime = new Hour[nMoments];
  241. }
  242. /// .. etc....
  243. this.workingCalendar = Calendar.getInstance(zone);
  244. this.position = START;
  245. this.domainIsPointsInTime = true;
  246. }
  247. /**
  248. * Fill the pointsInTime with times using TimePeriod.next():
  249. * Will silently return if the time array was already populated.
  250. *
  251. * Also computes the data cached for later use by
  252. * methods implementing the DomainInfo interface:
  253. *
  254. * @param start the start.
  255. *
  256. * @return ??.
  257. */
  258. public synchronized long setTimeBase(RegularTimePeriod start) {
  259. if (this.pointsInTime[0] == null) {
  260. this.pointsInTime[0] = start;
  261. for (int i = 1; i < this.historyCount; i++) {
  262. this.pointsInTime[i] = this.pointsInTime[i - 1].next();
  263. }
  264. }
  265. long oldestL = this.pointsInTime[0].getFirstMillisecond(this.workingCalendar);
  266. long nextL = this.pointsInTime[1].getFirstMillisecond(this.workingCalendar);
  267. this.deltaTime = nextL - oldestL;
  268. this.oldestAt = 0;
  269. this.newestAt = this.historyCount - 1;
  270. findDomainLimits();
  271. return this.deltaTime;
  272. }
  273. /**
  274. * Finds the domain limits.
  275. * <p>
  276. * Note: this doesn't need to be synchronized because it's called from within another method
  277. * that already is.
  278. */
  279. protected void findDomainLimits() {
  280. long startL = getOldestTime().getFirstMillisecond(this.workingCalendar);
  281. long endL;
  282. if (this.domainIsPointsInTime) {
  283. endL = getNewestTime().getFirstMillisecond(this.workingCalendar);
  284. }
  285. else {
  286. endL = getNewestTime().getLastMillisecond(this.workingCalendar);
  287. }
  288. this.domainStart = new Long(startL);
  289. this.domainEnd = new Long(endL);
  290. this.domainRange = new Range(startL, endL);
  291. }
  292. /**
  293. * Returns the x position type (START, MIDDLE or END).
  294. *
  295. * @return The x position type.
  296. */
  297. public int getPosition() {
  298. return this.position;
  299. }
  300. /**
  301. * Sets the x position type (START, MIDDLE or END).
  302. *
  303. * @param position The x position type.
  304. */
  305. public void setPosition(int position) {
  306. this.position = position;
  307. }
  308. /**
  309. * Adds a series to the dataset. Only the y-values are supplied, the x-values are specified
  310. * elsewhere.
  311. *
  312. * @param values the y-values.
  313. * @param seriesNumber the series index (zero-based).
  314. * @param seriesName the seriesName.
  315. *
  316. * Use this as-is during setup only, or add the synchronized keyword around the copy loop.
  317. */
  318. public void addSeries(float[] values,
  319. int seriesNumber, String seriesName) {
  320. invalidateRangeInfo();
  321. int i;
  322. if (values == null) {
  323. throw new IllegalArgumentException("TimeSeriesDataset.addSeries(): "
  324. + "cannot add null array of values.");
  325. }
  326. if (seriesNumber >= this.valueHistory.length) {
  327. throw new IllegalArgumentException("TimeSeriesDataset.addSeries(): "
  328. + "cannot add more series than specified in c'tor");
  329. }
  330. if (this.valueHistory[seriesNumber] == null) {
  331. this.valueHistory[seriesNumber] = new ValueSequence(this.historyCount);
  332. this.seriesCount++;
  333. } // But if that series array already exists, just overwrite its contents
  334. // Avoid IndexOutOfBoundsException:
  335. int srcLength = values.length;
  336. int copyLength = this.historyCount;
  337. boolean fillNeeded = false;
  338. if (srcLength < this.historyCount) {
  339. fillNeeded = true;
  340. copyLength = srcLength;
  341. }
  342. //{
  343. for (i = 0; i < copyLength; i++) { // deep copy from values[], caller can safely discard
  344. // that array
  345. this.valueHistory[seriesNumber].enterData(i, values[i]);
  346. }
  347. if (fillNeeded) {
  348. for (i = copyLength; i < this.historyCount; i++) {
  349. this.valueHistory[seriesNumber].enterData(i, 0.0f);
  350. }
  351. }
  352. //}
  353. if (seriesName != null) {
  354. this.seriesNames[seriesNumber] = seriesName;
  355. }
  356. fireSeriesChanged();
  357. }
  358. /**
  359. * Sets the name of a series.
  360. * <p>
  361. * If planning to add values individually.
  362. *
  363. * @param seriesNumber the series.
  364. * @param newName the new name.
  365. */
  366. public void setSeriesName(int seriesNumber, String newName) {
  367. this.seriesNames[seriesNumber] = newName;
  368. }
  369. /**
  370. * Adds a value to a series.
  371. *
  372. * @param seriesNumber the series index.
  373. * @param index ??.
  374. * @param value the value.
  375. */
  376. public void addValue(int seriesNumber, int index, float value) {
  377. invalidateRangeInfo();
  378. if (seriesNumber >= this.valueHistory.length) {
  379. throw new IllegalArgumentException("TimeSeriesDataset.addValue(): series #"
  380. + seriesNumber + "unspecified in c'tor");
  381. }
  382. if (this.valueHistory[seriesNumber] == null) {
  383. this.valueHistory[seriesNumber] = new ValueSequence(this.historyCount);
  384. this.seriesCount++;
  385. } // But if that series array already exists, just overwrite its contents
  386. //synchronized(this)
  387. //{
  388. this.valueHistory[seriesNumber].enterData(index, value);
  389. //}
  390. fireSeriesChanged();
  391. }
  392. /**
  393. * Returns the number of series in the collection.
  394. *
  395. * @return The series count.
  396. */
  397. public int getSeriesCount() {
  398. return this.seriesCount;
  399. }
  400. /**
  401. * Returns the number of items in a series.
  402. * <p>
  403. * For this implementation, all series have the same number of items.
  404. *
  405. * @param series the series index (zero-based).
  406. *
  407. * @return The item count.
  408. */
  409. public int getItemCount(int series) { // all arrays equal length, so ignore argument:
  410. return this.historyCount;
  411. }
  412. // Methods for managing the FIFO's:
  413. /**
  414. * Re-map an index, for use in retrieving data.
  415. *
  416. * @param toFetch the index.
  417. *
  418. * @return The translated index.
  419. */
  420. protected int translateGet(int toFetch) {
  421. if (this.oldestAt == 0) {
  422. return toFetch; // no translation needed
  423. }
  424. // else [implicit here]
  425. int newIndex = toFetch + this.oldestAt;
  426. if (newIndex >= this.historyCount) {
  427. newIndex -= this.historyCount;
  428. }
  429. return newIndex;
  430. }
  431. /**
  432. * Returns the actual index to a time offset by "delta" from newestAt.
  433. *
  434. * @param delta the delta.
  435. *
  436. * @return The offset.
  437. */
  438. public int offsetFromNewest(int delta) {
  439. return wrapOffset(this.newestAt + delta);
  440. }
  441. /**
  442. * ??
  443. *
  444. * @param delta ??
  445. *
  446. * @return The offset.
  447. */
  448. public int offsetFromOldest(int delta) {
  449. return wrapOffset(this.oldestAt + delta);
  450. }
  451. /**
  452. * ??
  453. *
  454. * @param protoIndex the index.
  455. *
  456. * @return The offset.
  457. */
  458. protected int wrapOffset(int protoIndex) {
  459. int tmp = protoIndex;
  460. if (tmp >= this.historyCount) {
  461. tmp -= this.historyCount;
  462. }
  463. else if (tmp < 0) {
  464. tmp += this.historyCount;
  465. }
  466. return tmp;
  467. }
  468. /**
  469. * Adjust the array offset as needed when a new time-period is added:
  470. * Increments the indices "oldestAt" and "newestAt", mod(array length),
  471. * zeroes the series values at newestAt, returns the new TimePeriod.
  472. *
  473. * @return The new time period.
  474. */
  475. public synchronized RegularTimePeriod advanceTime() {
  476. RegularTimePeriod nextInstant = this.pointsInTime[this.newestAt].next();
  477. this.newestAt = this.oldestAt; // newestAt takes value previously held by oldestAT
  478. /*** The next 10 lines or so should be expanded if data can be negative ***/
  479. // if the oldest data contained a maximum Y-value, invalidate the stored
  480. // Y-max and Y-range data:
  481. boolean extremaChanged = false;
  482. float oldMax = 0.0f;
  483. if (this.maxValue != null) {
  484. oldMax = this.maxValue.floatValue();
  485. }
  486. for (int s = 0; s < getSeriesCount(); s++) {
  487. if (this.valueHistory[s].getData(this.oldestAt) == oldMax) {
  488. extremaChanged = true;
  489. }
  490. if (extremaChanged) {
  491. break;
  492. }
  493. } /*** If data can be < 0, add code here to check the minimum **/
  494. if (extremaChanged) {
  495. invalidateRangeInfo();
  496. }
  497. // wipe the next (about to be used) set of data slots
  498. float wiper = (float) 0.0;
  499. for (int s = 0; s < getSeriesCount(); s++) {
  500. this.valueHistory[s].enterData(this.newestAt, wiper);
  501. }
  502. // Update the array of TimePeriods:
  503. this.pointsInTime[this.newestAt] = nextInstant;
  504. // Now advance "oldestAt", wrapping at end of the array
  505. this.oldestAt++;
  506. if (this.oldestAt >= this.historyCount) {
  507. this.oldestAt = 0;
  508. }
  509. // Update the domain limits:
  510. long startL = this.domainStart.longValue(); //(time is kept in msec)
  511. this.domainStart = new Long(startL + this.deltaTime);
  512. long endL = this.domainEnd.longValue();
  513. this.domainEnd = new Long(endL + this.deltaTime);
  514. this.domainRange = new Range(startL, endL);
  515. fireSeriesChanged();
  516. return nextInstant;
  517. }
  518. // If data can be < 0, the next 2 methods should be modified
  519. /**
  520. * Invalidates the range info.
  521. */
  522. public void invalidateRangeInfo() {
  523. this.maxValue = null;
  524. this.valueRange = null;
  525. }
  526. /**
  527. * Returns the maximum value.
  528. *
  529. * @return The maximum value.
  530. */
  531. protected double findMaxValue() {
  532. double max = 0.0f;
  533. for (int s = 0; s < getSeriesCount(); s++) {
  534. for (int i = 0; i < this.historyCount; i++) {
  535. double tmp = getYValue(s, i);
  536. if (tmp > max) {
  537. max = tmp;
  538. }
  539. }
  540. }
  541. return max;
  542. }
  543. /** End, positive-data-only code **/
  544. /**
  545. * Returns the index of the oldest data item.
  546. *
  547. * @return The index.
  548. */
  549. public int getOldestIndex() {
  550. return this.oldestAt;
  551. }
  552. /**
  553. * Returns the index of the newest data item.
  554. *
  555. * @return The index.
  556. */
  557. public int getNewestIndex() {
  558. return this.newestAt;
  559. }
  560. // appendData() writes new data at the index position given by newestAt/
  561. // When adding new data dynamically, use advanceTime(), followed by this:
  562. /**
  563. * Appends new data.
  564. *
  565. * @param newData the data.
  566. */
  567. public void appendData(float[] newData) {
  568. int nDataPoints = newData.length;
  569. if (nDataPoints > this.valueHistory.length) {
  570. throw new IllegalArgumentException(
  571. "DynamicTimeSeriesCollection.appendData(...): more data than series to put them in");
  572. }
  573. int s; // index to select the "series"
  574. for (s = 0; s < nDataPoints; s++) {
  575. // check whether the "valueHistory" array member exists; if not, create them:
  576. if (this.valueHistory[s] == null) {
  577. this.valueHistory[s] = new ValueSequence(this.historyCount);
  578. }
  579. this.valueHistory[s].enterData(this.newestAt, newData[s]);
  580. }
  581. fireSeriesChanged();
  582. }
  583. /**
  584. * Appends data at specified index, for loading up with data from file(s).
  585. *
  586. * @param newData the data
  587. * @param insertionIndex the index value at which to put it
  588. * @param refresh value of n in "refresh the display on every nth call"
  589. * (ignored if <= 0 )
  590. */
  591. public void appendData(float[] newData, int insertionIndex, int refresh) {
  592. int nDataPoints = newData.length;
  593. if (nDataPoints > this.valueHistory.length) {
  594. throw new IllegalArgumentException(
  595. "DynamicTimeSeriesCollection.appendData(): more data than series to put them "
  596. + "in");
  597. }
  598. for (int s = 0; s < nDataPoints; s++) {
  599. if (this.valueHistory[s] == null) {
  600. this.valueHistory[s] = new ValueSequence(this.historyCount);
  601. }
  602. this.valueHistory[s].enterData(insertionIndex, newData[s]);
  603. }
  604. if (refresh > 0) {
  605. insertionIndex++;
  606. if (insertionIndex % refresh == 0) {
  607. fireSeriesChanged();
  608. }
  609. }
  610. }
  611. /**
  612. * Returns the newest time.
  613. *
  614. * @return The newest time.
  615. */
  616. public RegularTimePeriod getNewestTime() {
  617. return this.pointsInTime[this.newestAt];
  618. }
  619. /**
  620. * Returns the oldest time.
  621. *
  622. * @return The oldest time.
  623. */
  624. public RegularTimePeriod getOldestTime() {
  625. return this.pointsInTime[this.oldestAt];
  626. }
  627. /**
  628. * Returns the x-value.
  629. *
  630. * @param series the series index (zero-based).
  631. * @param item the item index (zero-based).
  632. *
  633. * @return The value.
  634. */
  635. // getXxx() ftns can ignore the "series" argument:
  636. // Don't synchronize this!! Instead, synchronize the loop that calls it.
  637. public Number getX(int series, int item) {
  638. RegularTimePeriod tp = this.pointsInTime[translateGet(item)];
  639. return new Long(getX(tp));
  640. }
  641. /**
  642. * Returns the y-value.
  643. *
  644. * @param series the series index (zero-based).
  645. * @param item the item index (zero-based).
  646. *
  647. * @return The value.
  648. */
  649. public double getYValue(int series, int item) { // Don't synchronize this!!
  650. ValueSequence values = this.valueHistory[series]; // Instead, synchronize the loop
  651. return values.getData(translateGet(item)); // that calls it.
  652. }
  653. /**
  654. * Returns the y-value.
  655. *
  656. * @param series the series index (zero-based).
  657. * @param item the item index (zero-based).
  658. *
  659. * @return The value.
  660. */
  661. public Number getY(int series, int item) {
  662. return new Float(getYValue(series, item));
  663. }
  664. /**
  665. * Returns the start x-value.
  666. *
  667. * @param series the series index (zero-based).
  668. * @param item the item index (zero-based).
  669. *
  670. * @return The value.
  671. */
  672. public Number getStartX(int series, int item) {
  673. RegularTimePeriod tp = this.pointsInTime[translateGet(item)];
  674. return new Long(tp.getFirstMillisecond(this.workingCalendar));
  675. }
  676. /**
  677. * Returns the end x-value.
  678. *
  679. * @param series the series index (zero-based).
  680. * @param item the item index (zero-based).
  681. *
  682. * @return The value.
  683. */
  684. public Number getEndX(int series, int item) {
  685. RegularTimePeriod tp = this.pointsInTime[translateGet(item)];
  686. return new Long(tp.getLastMillisecond(this.workingCalendar));
  687. }
  688. /**
  689. * Returns the start y-value.
  690. *
  691. * @param series the series index (zero-based).
  692. * @param item the item index (zero-based).
  693. *
  694. * @return The value.
  695. */
  696. public Number getStartY(int series, int item) {
  697. return getY(series, item);
  698. }
  699. /**
  700. * Returns the end y-value.
  701. *
  702. * @param series the series index (zero-based).
  703. * @param item the item index (zero-based).
  704. *
  705. * @return The value.
  706. */
  707. public Number getEndY(int series, int item) {
  708. return getY(series, item);
  709. }
  710. /* // "Extras" found useful when analyzing/verifying class behavior:
  711. public Number getUntranslatedXValue(int series, int item)
  712. {
  713. return super.getXValue(series, item);
  714. }
  715. public float getUntranslatedY(int series, int item)
  716. {
  717. return super.getY(series, item);
  718. } */
  719. /**
  720. * Returns the name of a series.
  721. *
  722. * @param series the series index (zero-based).
  723. *
  724. * @return The name.
  725. */
  726. public String getSeriesName(int series) {
  727. return this.seriesNames[series];
  728. }
  729. /**
  730. * Sends a {@link SeriesChangeEvent} to all registered listeners.
  731. */
  732. protected void fireSeriesChanged() {
  733. seriesChanged(new SeriesChangeEvent(this));
  734. }
  735. // The next 3 functions override the base-class implementation of
  736. // the DomainInfo interface. Using saved limits (updated by
  737. // each updateTime() call), improves performance.
  738. //
  739. /**
  740. * Returns the minimum x-value in the dataset.
  741. *
  742. * @param includeInterval a flag that determines whether or not the
  743. * x-interval is taken into account.
  744. *
  745. * @return The minimum value.
  746. */
  747. public double getDomainLowerBound(boolean includeInterval) {
  748. return this.domainStart.doubleValue(); // a Long kept updated by advanceTime()
  749. }
  750. /**
  751. * Returns the maximum x-value in the dataset.
  752. *
  753. * @param includeInterval a flag that determines whether or not the
  754. * x-interval is taken into account.
  755. *
  756. * @return The maximum value.
  757. */
  758. public double getDomainUpperBound(boolean includeInterval) {
  759. return this.domainEnd.doubleValue(); // a Long kept updated by advanceTime()
  760. }
  761. /**
  762. * Returns the range of the values in this dataset's domain.
  763. *
  764. * @param includeInterval a flag that determines whether or not the
  765. * x-interval is taken into account.
  766. *
  767. * @return The range.
  768. */
  769. public Range getDomainBounds(boolean includeInterval) {
  770. if (this.domainRange == null) {
  771. findDomainLimits();
  772. }
  773. return this.domainRange;
  774. }
  775. /**
  776. * Returns the x-value for a time period.
  777. *
  778. * @param period the period.
  779. *
  780. * @return The x-value.
  781. */
  782. private long getX(RegularTimePeriod period) {
  783. switch (this.position) {
  784. case (START) : return period.getFirstMillisecond(this.workingCalendar);
  785. case (MIDDLE) : return period.getMiddleMillisecond(this.workingCalendar);
  786. case (END) : return period.getLastMillisecond(this.workingCalendar);
  787. default: return period.getMiddleMillisecond(this.workingCalendar);
  788. }
  789. }
  790. // The next 3 functions implement the RangeInfo interface.
  791. // Using saved limits (updated by each updateTime() call) significantly
  792. // improves performance. WARNING: this code makes the simplifying assumption
  793. // that data is never negative. Expand as needed for the general case.
  794. /**
  795. * Returns the minimum range value.
  796. *
  797. * @param includeInterval a flag that determines whether or not the
  798. * y-interval is taken into account.
  799. *
  800. * @return The minimum range value.
  801. */
  802. public double getRangeLowerBound(boolean includeInterval) {
  803. double result = Double.NaN;
  804. if (this.minValue != null) {
  805. result = this.minValue.doubleValue();
  806. }
  807. return result;
  808. }
  809. /**
  810. * Returns the maximum range value.
  811. *
  812. * @param includeInterval a flag that determines whether or not the
  813. * y-interval is taken into account.
  814. *
  815. * @return The maximum range value.
  816. */
  817. public double getRangeUpperBound(boolean includeInterval) {
  818. double result = Double.NaN;
  819. if (this.maxValue != null) {
  820. result = this.maxValue.doubleValue();
  821. }
  822. return result;
  823. }
  824. /**
  825. * Returns the value range.
  826. *
  827. * @param includeInterval a flag that determines whether or not the
  828. * y-interval is taken into account.
  829. *
  830. * @return The range.
  831. */
  832. public Range getRangeBounds(boolean includeInterval) {
  833. if (this.valueRange == null) {
  834. double max = getRangeUpperBound(includeInterval);
  835. this.valueRange = new Range(0.0, max);
  836. }
  837. return this.valueRange;
  838. }
  839. }