📄 dynamictimeseriescollection.java
字号:
/* ======================================
* JFreeChart : a free Java chart library
* ======================================
*
* Project Info: http://www.jfree.org/jfreechart/index.html
* Project Lead: David Gilbert (david.gilbert@object-refinery.com);
*
* (C) Copyright 2000-2003, by Object Refinery Limited and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* --------------------------------
* DynamicTimeSeriesCollection.java
* --------------------------------
* (C) Copyright 2002, 2003, by I. H. Thomae and Contributors.
*
* Original Author: I. H. Thomae (ithomae@ists.dartmouth.edu);
* Contributor(s): David Gilbert (for Object Refinery Limited);
*
* $Id: DynamicTimeSeriesCollection.java,v 1.5 2003/06/13 15:46:46 mungady Exp $
*
* Changes
* -------
* 22-Nov-2002 : Initial version completed
* Jan 2003 : Optimized advanceTime(), added implemnt'n of RangeInfo intfc
* (using cached values for min, max, and range); also added
* getOldestIndex() and getNewestIndex() ftns so client classes
* can use this class as the master "index authority".
* 22-Jan-2003 : Made this class stand on its own, rather than extending
* class FastTimeSeriesCollection
* 31-Jan-2003 : Changed TimePeriod --> RegularTimePeriod (DG);
* 13-Mar-2003 : Moved to com.jrefinery.data.time package (DG);
* 29-Apr-2003 : Added small change to appendData method, from Irv Thomae (DG);
*
*/
package org.jfree.data.time;
import java.util.Calendar;
import java.util.TimeZone;
import org.jfree.data.AbstractSeriesDataset;
import org.jfree.data.DomainInfo;
import org.jfree.data.IntervalXYDataset;
import org.jfree.data.Range;
import org.jfree.data.RangeInfo;
import org.jfree.data.SeriesChangeEvent;
/**
* A dynamic dataset.
* <p>
* Like FastTimeSeriesCollection, this class is a functional replacement
* for JFreeChart's TimeSeriesCollection _and_ TimeSeries classes.
* FastTimeSeriesCollection is appropriate for a fixed time range; for
* real-time applications this subclass adds the ability to append new
* data and discard the oldest.
* In this class, the arrays used in FastTimeSeriesCollection become FIFO's.
* NOTE:As presented here, all data is assumed >= 0, an assumption which is
* embodied only in methods associated with interface RangeInfo.
*
* @author Irv Thomae.
*/
public class DynamicTimeSeriesCollection extends AbstractSeriesDataset
implements IntervalXYDataset,
DomainInfo,
RangeInfo {
/** Useful constant for controlling the x-value returned for a time period. */
public static final int START = 0;
/** Useful constant for controlling the x-value returned for a time period. */
public static final int MIDDLE = 1;
/** Useful constant for controlling the x-value returned for a time period. */
public static final int END = 2;
/** The maximum number of items for each series (can be overridden). */
private int maximumItemCount = 2000; // an arbitrary safe default value
/** The history count. */
protected int historyCount;
/** Storage for the series names. */
private String[] seriesNames;
/** The time period class - barely used, and could be removed (DG). */
private Class timePeriodClass = Minute.class; // default value;
/** Storage for the x-values. */
protected RegularTimePeriod[] pointsInTime;
/** The number of series. */
private int seriesCount;
/**
* A wrapper for a fixed array of float values.
*/
protected class ValueSequence {
/** Storage for the float values. */
float dataPoints[];
/**
* Default constructor:
*/
public ValueSequence() {
this(DynamicTimeSeriesCollection.this.maximumItemCount);
}
/**
* Creates a sequence with the specified length.
*
* @param length the length.
*/
public ValueSequence(int length) {
dataPoints = new float[length];
for (int i = 0; i < length; i++) {
this.dataPoints[i] = 0.0f;
}
}
/**
* Enters data into the storage array.
*
* @param index the index.
* @param value the value.
*/
public void enterData(int index, float value) {
this.dataPoints[index] = value;
}
/**
* Returns a value from the storage array.
*
* @param index the index.
*
* @return The value.
*/
public float getData(int index) {
return dataPoints[index];
}
}
/** An array for storing the objects that represent each series. */
protected ValueSequence[] valueHistory;
/** A working calendar (to recycle) */
protected Calendar workingCalendar;
/** The position within a time period to return as the x-value (START, MIDDLE or END). */
private int position;
/**
* A flag that indicates that the domain is 'points in time'. If this flag is true, only
* the x-value is used to determine the range of values in the domain, the start and end
* x-values are ignored.
*/
private boolean domainIsPointsInTime;
/** index for mapping: points to the oldest valid time & data. */
private int oldestAt; // as a class variable, initializes == 0
/** Index of the newest data item. */
private int newestAt;
// cached values used for interface DomainInfo:
/** the # of msec by which time advances. */
private long deltaTime;
/** Cached domain start (for use by DomainInfo). */
private Long domainStart;
/** Cached domain end (for use by DomainInfo). */
private Long domainEnd;
/** Cached domain range (for use by DomainInfo). */
private Range domainRange;
// Cached values used for interface RangeInfo: (note minValue pinned at 0)
// A single set of extrema covers the entire SeriesCollection
/** The minimum value. */
private Float minValue = new Float(0.0f);
/** The maximum value. */
private Float maxValue = null;
/** The value range. */
private Range valueRange; // autoinit's to null.
/**
* Constructs a dataset with capacity for N series, tied to default timezone.
*
* @param nSeries the number of series to be accommodated.
* @param nMoments the number of TimePeriods to be spanned.
*/
public DynamicTimeSeriesCollection(int nSeries, int nMoments) {
this(nSeries, nMoments, new Millisecond(), TimeZone.getDefault());
this.newestAt = nMoments - 1;
}
/**
* Constructs an empty dataset, tied to a specific timezone.
*
* @param nSeries the number of series to be accommodated
* @param nMoments the number of TimePeriods to be spanned
* @param zone the timezone.
*/
public DynamicTimeSeriesCollection(int nSeries, int nMoments, TimeZone zone) {
this(nSeries, nMoments, new Millisecond(), zone);
this.newestAt = nMoments - 1;
}
/**
* Creates a new dataset.
*
* @param nSeries the number of series.
* @param nMoments the number of items per series.
* @param timeSample a time period sample.
*/
public DynamicTimeSeriesCollection(int nSeries,
int nMoments,
RegularTimePeriod timeSample) {
this(nSeries, nMoments, timeSample, TimeZone.getDefault());
}
/**
* Creates a new dataset.
*
* @param nSeries the number of series.
* @param nMoments the number of items per series.
* @param timeSample a time period sample.
* @param zone the time zone.
*/
public DynamicTimeSeriesCollection(int nSeries,
int nMoments,
RegularTimePeriod timeSample,
TimeZone zone) {
// the first initialization must precede creation of the ValueSet array:
this.maximumItemCount = nMoments; // establishes length of each array
this.historyCount = nMoments;
this.seriesNames = new String[nSeries];
// initialize the members of "seriesNames" array so they won't be null:
for (int i = 0; i < nSeries; i++) {
seriesNames[i] = "";
}
this.newestAt = nMoments - 1;
this.valueHistory = new ValueSequence[nSeries];
this.timePeriodClass = timeSample.getClass();
/// Expand the following for all defined TimePeriods:
if (timePeriodClass == Second.class) {
this.pointsInTime = new Second[nMoments];
}
else if (timePeriodClass == Minute.class) {
this.pointsInTime = new Minute[nMoments];
}
else if (timePeriodClass == Hour.class) {
this.pointsInTime = new Hour[nMoments];
}
/// .. etc....
this.workingCalendar = Calendar.getInstance(zone);
this.position = START;
this.domainIsPointsInTime = true;
}
/**
* Fill the pointsInTime with times using TimePeriod.next():
* Will silently return if the time array was already populated.
*
* Also computes the data cached for later use by
* methods implementing the DomainInfo interface:
*
* @param start the start.
*
* @return ??.
*/
public synchronized long setTimeBase(RegularTimePeriod start) {
if (pointsInTime[0] == null) {
pointsInTime[0] = start;
for (int i = 1; i < historyCount; i++) {
pointsInTime[i] = pointsInTime[i - 1].next();
}
}
long oldestL = pointsInTime[0].getFirstMillisecond(workingCalendar);
long nextL = pointsInTime[1].getFirstMillisecond(workingCalendar);
deltaTime = nextL - oldestL;
oldestAt = 0;
newestAt = historyCount - 1;
findDomainLimits();
return deltaTime;
}
/**
* Finds the domain limits.
* <p>
* Note: this doesn't need to be synchronized because it's called from within another method
* that already is.
*/
protected void findDomainLimits() {
long startL = getOldestTime().getFirstMillisecond(workingCalendar);
long endL;
if (domainIsPointsInTime) {
endL = getNewestTime().getFirstMillisecond(workingCalendar);
}
else {
endL = getNewestTime().getLastMillisecond(workingCalendar);
}
this.domainStart = new Long(startL);
this.domainEnd = new Long(endL);
this.domainRange = new Range((double) startL, (double) endL);
}
/**
* Returns the x position type (START, MIDDLE or END).
*
* @return The x position type.
*/
public int getPosition() {
return position;
}
/**
* Sets the x position type (START, MIDDLE or END).
*
* @param position The x position type.
*/
public void setPosition(int position) {
this.position = position;
}
/**
* Adds a series to the dataset. Only the y-values are supplied, the x-values are specified
* elsewhere.
*
* @param values the y-values.
* @param seriesNumber the series index (zero-based).
* @param seriesName the seriesName.
*
* Use this as-is during setup only, or add the synchronized keyword around the copy loop.
*/
public void addSeries(float[] values,
int seriesNumber, String seriesName) {
invalidateRangeInfo();
int i;
if (values == null) {
throw new IllegalArgumentException("TimeSeriesDataset.addSeries(...): "
+ "cannot add null array of values.");
}
if (seriesNumber >= valueHistory.length) {
throw new IllegalArgumentException("TimeSeriesDataset.addSeries(...): "
+ "cannot add more series than specified in c'tor");
}
if (valueHistory[seriesNumber] == null) {
valueHistory[seriesNumber] = new ValueSequence(historyCount);
seriesCount++;
} // But if that series array already exists, just overwrite its contents
// Avoid IndexOutOfBoundsException:
int srcLength = values.length;
int copyLength = historyCount;
boolean fillNeeded = false;
if (srcLength < historyCount) {
fillNeeded = true;
copyLength = srcLength;
}
//{
for (i = 0; i < copyLength; i++) { // deep copy from values[], caller can safely discard
// that array
valueHistory[seriesNumber].enterData(i, values[i]);
}
if (fillNeeded) {
for (i = copyLength; i < historyCount; i++) {
valueHistory[seriesNumber].enterData(i, 0.0f);
}
}
//}
if (seriesName != null) {
seriesNames[seriesNumber] = seriesName;
}
fireSeriesChanged();
}
/**
* Sets the name of a series.
* <p>
* If planning to add values individually.
*
* @param seriesNumber the series.
* @param newName the new name.
*/
public void setSeriesName(int seriesNumber, String newName) {
seriesNames[seriesNumber] = newName;
}
/**
* Adds a value to a series.
*
* @param seriesNumber the series index.
* @param index ??.
* @param value the value.
*/
public void addValue(int seriesNumber, int index, float value) {
invalidateRangeInfo();
if (seriesNumber >= valueHistory.length) {
throw new IllegalArgumentException("TimeSeriesDataset.addValue(...): series #"
+ seriesNumber + "unspecified in c'tor");
}
if (valueHistory[seriesNumber] == null) {
valueHistory[seriesNumber] = new ValueSequence(historyCount);
seriesCount++;
} // But if that series array already exists, just overwrite its contents
//synchronized(this)
{
valueHistory[seriesNumber].enterData(index, value);
}
fireSeriesChanged();
}
/**
* Returns the number of series in the collection.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -