blob: d3e24699013b6d1788f7a7151138dd2d1072568d [file] [log] [blame]
'use strict';
class TimeSeries {
_data = [];
values() { return this._data.map((point) => point.value); }
length() { return this._data.length; }
append(item)
{
console.assert(item.series === undefined);
item.series = this;
item.seriesIndex = this._data.length;
this._data.push(item);
}
extendToFuture()
{
if (!this._data.length)
return;
const lastPoint = this._data[this._data.length - 1];
this._data.push({
series: this,
seriesIndex: this._data.length,
time: Date.now() + 365 * 24 * 3600 * 1000,
value: lastPoint.value,
interval: lastPoint.interval,
});
}
valuesBetweenRange(startingIndex, endingIndex)
{
startingIndex = Math.max(startingIndex, 0);
endingIndex = Math.min(endingIndex, this._data.length);
const length = endingIndex - startingIndex;
const values = new Array(length);
for (let i = 0; i < length; ++i)
values[i] = this._data[startingIndex + i].value;
return values;
}
firstPoint() { return this._data.length ? this._data[0] : null; }
lastPoint() { return this._data.length ? this._data[this._data.length - 1] : null; }
previousPoint(point)
{
console.assert(point.series == this);
if (!point.seriesIndex)
return null;
return this._data[point.seriesIndex - 1];
}
nextPoint(point)
{
console.assert(point.series == this);
if (point.seriesIndex + 1 >= this._data.length)
return null;
return this._data[point.seriesIndex + 1];
}
findPointByIndex(index)
{
if (!this._data || index < 0 || index >= this._data.length)
return null;
return this._data[index];
}
findById(id) { return this._data.find((point) => point.id == id); }
findPointAfterTime(time) { return this._data.find((point) => point.time >= time); }
viewBetweenTime(startTime, endTime)
{
const startPoint = this.findPointAfterTime(startTime);
if (!startPoint)
return null;
let endPoint = this.findPointAfterTime(endTime);
if (!endPoint)
endPoint = this.lastPoint();
else if (endPoint.time > endTime)
endPoint = this.previousPoint(endPoint);
return this.viewBetweenPoints(startPoint, endPoint);
}
viewBetweenPoints(firstPoint, lastPoint)
{
console.assert(firstPoint.series == this);
console.assert(lastPoint.series == this);
return new TimeSeriesView(this, firstPoint.seriesIndex, lastPoint.seriesIndex + 1);
}
};
class TimeSeriesView {
_timeSeries;
_values = null;
_length;
_startingIndex;
_afterEndingIndex;
constructor(timeSeries, startingIndex, afterEndingIndex)
{
console.assert(timeSeries instanceof TimeSeries);
console.assert(startingIndex <= afterEndingIndex);
console.assert(afterEndingIndex <= timeSeries._data.length);
this._timeSeries = timeSeries;
this._length = afterEndingIndex - startingIndex;
this._startingIndex = startingIndex;
this._afterEndingIndex = afterEndingIndex;
}
get _data() { return this._timeSeries._data; }
length() { return this._length; }
firstPoint() { return this._length ? this._data[this._startingIndex] : null; }
lastPoint() { return this._length ? this._data[this._afterEndingIndex - 1] : null; }
_findIndexForPoint(point)
{
return point.seriesIndex;
}
nextPoint(point)
{
let index = this._findIndexForPoint(point);
index++;
if (index == this._afterEndingIndex)
return null;
return this._data[index];
}
previousPoint(point)
{
const index = this._findIndexForPoint(point);
if (index == this._startingIndex)
return null;
return this._data[index - 1];
}
findPointByIndex(index)
{
index += this._startingIndex;
if (index < 0 || index >= this._afterEndingIndex)
return null;
return this._data[index];
}
findById(id)
{
for (let point of this) {
if (point.id == id)
return point;
}
return null;
}
values()
{
if (this._values == null) {
this._values = new Array(this._length);
let i = 0;
for (let point of this)
this._values[i++] = point.value;
}
return this._values;
}
filter(callback)
{
const filteredData = [];
let i = 0;
for (let point of this) {
if (callback(point, i))
filteredData.push(point);
i++;
}
return new FilteredTimeSeriesView(this._timeSeries, 0, filteredData.length, filteredData);
}
viewTimeRange(startTime, endTime)
{
const data = this._data;
let startingIndex = null;
let endingIndex = null;
for (let i = this._startingIndex; i < this._afterEndingIndex; i++) {
if (startingIndex == null && data[i].time >= startTime)
startingIndex = i;
if (data[i].time <= endTime)
endingIndex = i;
}
if (startingIndex == null || endingIndex == null)
return this._subRange(0, 0);
return this._subRange(startingIndex, endingIndex + 1);
}
_subRange(startingIndex, afterEndingIndex)
{
return new TimeSeriesView(this._timeSeries, startingIndex, afterEndingIndex);
}
firstPointInTimeRange(startTime, endTime)
{
console.assert(startTime <= endTime);
for (let point of this) {
if (point.time > endTime)
return null;
if (point.time >= startTime)
return point;
}
return null;
}
lastPointInTimeRange(startTime, endTime)
{
console.assert(startTime <= endTime);
for (let point of this._reverse()) {
if (point.time < startTime)
return null;
if (point.time <= endTime)
return point;
}
return null;
}
*[Symbol.iterator]()
{
const data = this._data;
const afterEnd = this._afterEndingIndex;
let i = this._startingIndex;
for (let i = this._startingIndex; i < afterEnd; ++i)
yield data[i];
}
*_reverse()
{
const data = this._data;
const beginning = this._startingIndex;
for (let i = this._afterEndingIndex - 1; i >= beginning; --i)
yield data[i];
}
}
class FilteredTimeSeriesView extends TimeSeriesView {
_filteredData;
_pointIndexMap;
constructor(timeSeries, startingIndex, afterEndingIndex, filteredData)
{
console.assert(afterEndingIndex <= filteredData.length);
super(timeSeries, startingIndex, afterEndingIndex);
this._filteredData = filteredData;
}
get _data() { return this._filteredData; }
_subRange(startingIndex, afterEndingIndex)
{
return new FilteredTimeSeriesView(this._timeSeries, startingIndex, afterEndingIndex, this._filteredData);
}
_findIndexForPoint(point)
{
if (!this._pointIndexMap)
this._buildPointIndexMap();
return this._pointIndexMap.get(point);
}
_buildPointIndexMap()
{
this._pointIndexMap = new Map;
const data = this._data;
const length = data.length;
for (let i = 0; i < length; i++)
this._pointIndexMap.set(data[i], i);
}
}
if (typeof module != 'undefined')
module.exports.TimeSeries = TimeSeries;