📄 dynamictimeseriescollection.java
字号:
/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2004, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * 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. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * -------------------------------- * 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.16 2004/05/07 15:53:39 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); * 19-Sep-2003 : Added new appendData method, from Irv Thomae (DG); * 05-May-2004 : Now extends AbstractIntervalXYDataset. This also required a * change to the return type of the getY() method - I'm slightly * unsure of the implications of this, so it might require some * further amendment (DG); * */package org.jfree.data.time;import java.util.Calendar;import java.util.TimeZone;import org.jfree.data.AbstractIntervalXYDataset;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 AbstractIntervalXYDataset 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(final int length) { this.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(final int index, final float value) { this.dataPoints[index] = value; } /** * Returns a value from the storage array. * * @param index the index. * * @return The value. */ public float getData(final int index) { return this.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(final int nSeries, final 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(final int nSeries, final int nMoments, final 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(final int nSeries, final int nMoments, final 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(final int nSeries, final int nMoments, final RegularTimePeriod timeSample, final 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++) { this.seriesNames[i] = ""; } this.newestAt = nMoments - 1; this.valueHistory = new ValueSequence[nSeries]; this.timePeriodClass = timeSample.getClass(); /// Expand the following for all defined TimePeriods: if (this.timePeriodClass == Second.class) { this.pointsInTime = new Second[nMoments]; } else if (this.timePeriodClass == Minute.class) { this.pointsInTime = new Minute[nMoments]; } else if (this.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(final RegularTimePeriod start) { if (this.pointsInTime[0] == null) { this.pointsInTime[0] = start; for (int i = 1; i < this.historyCount; i++) { this.pointsInTime[i] = this.pointsInTime[i - 1].next(); } } final long oldestL = this.pointsInTime[0].getFirstMillisecond(this.workingCalendar); final long nextL = this.pointsInTime[1].getFirstMillisecond(this.workingCalendar); this.deltaTime = nextL - oldestL; this.oldestAt = 0; this.newestAt = this.historyCount - 1; findDomainLimits(); return this.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() { final long startL = getOldestTime().getFirstMillisecond(this.workingCalendar); final long endL; if (this.domainIsPointsInTime) { endL = getNewestTime().getFirstMillisecond(this.workingCalendar); } else { endL = getNewestTime().getLastMillisecond(this.workingCalendar); } this.domainStart = new Long(startL); this.domainEnd = new Long(endL); this.domainRange = new Range(startL, endL); } /** * Returns the x position type (START, MIDDLE or END). * * @return The x position type. */ public int getPosition() { return this.position; } /** * Sets the x position type (START, MIDDLE or END). * * @param position The x position type. */ public void setPosition(final 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(final float[] values, final int seriesNumber, final String seriesName) { invalidateRangeInfo(); int i; if (values == null) { throw new IllegalArgumentException("TimeSeriesDataset.addSeries(...): " + "cannot add null array of values."); } if (seriesNumber >= this.valueHistory.length) { throw new IllegalArgumentException("TimeSeriesDataset.addSeries(...): " + "cannot add more series than specified in c'tor"); } if (this.valueHistory[seriesNumber] == null) { this.valueHistory[seriesNumber] = new ValueSequence(this.historyCount); this.seriesCount++; } // But if that series array already exists, just overwrite its contents // Avoid IndexOutOfBoundsException: final int srcLength = values.length; int copyLength = this.historyCount; boolean fillNeeded = false; if (srcLength < this.historyCount) { fillNeeded = true; copyLength = srcLength; } //{ for (i = 0; i < copyLength; i++) { // deep copy from values[], caller can safely discard // that array this.valueHistory[seriesNumber].enterData(i, values[i]); } if (fillNeeded) { for (i = copyLength; i < this.historyCount; i++) { this.valueHistory[seriesNumber].enterData(i, 0.0f); } } //} if (seriesName != null) { this.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(final int seriesNumber, final String newName) { this.seriesNames[seriesNumber] = newName; } /** * Adds a value to a series. * * @param seriesNumber the series index. * @param index ??. * @param value the value. */ public void addValue(final int seriesNumber, final int index, final float value) { invalidateRangeInfo(); if (seriesNumber >= this.valueHistory.length) { throw new IllegalArgumentException("TimeSeriesDataset.addValue(...): series #" + seriesNumber + "unspecified in c'tor"); } if (this.valueHistory[seriesNumber] == null) { this.valueHistory[seriesNumber] = new ValueSequence(this.historyCount); this.seriesCount++; } // But if that series array already exists, just overwrite its contents //synchronized(this) //{ this.valueHistory[seriesNumber].enterData(index, value); //} fireSeriesChanged(); } /** * Returns the number of series in the collection. * * @return the series count. */ public int getSeriesCount() { return this.seriesCount; } /**
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -