mirror of
https://github.com/nasa/openmct.git
synced 2025-06-14 21:28:12 +00:00
[Plot] Begin separating out plot line handling
Begin separating out plot line buffer from the rest of plot; managing this buffer separately will aid in merging realtime and hsitorical data, WTD-806.
This commit is contained in:
102
platform/features/plot/src/elements/PlotLine.js
Normal file
102
platform/features/plot/src/elements/PlotLine.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*global define,Float32Array*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./PlotSeriesWindow'],
|
||||||
|
function (PlotSeriesWindow) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
function PlotLine(initialSize, maxPoints) {
|
||||||
|
var buffer,
|
||||||
|
length = 0,
|
||||||
|
timeWindow;
|
||||||
|
|
||||||
|
// Binary search the buffer to find the index where
|
||||||
|
// a point with this timestamp should be inserted.
|
||||||
|
// After is a flag indicating whether it is preferred
|
||||||
|
// to insert after or before its nearest timestamp
|
||||||
|
function searchBuffer(timestamp, after) {
|
||||||
|
// Binary search for an appropriate insertion index.
|
||||||
|
function binSearch(min, max) {
|
||||||
|
var mid = Math.floor((min + max) / 2),
|
||||||
|
ts;
|
||||||
|
|
||||||
|
if (max < min) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts = buffer[mid * 2];
|
||||||
|
|
||||||
|
// Check for an exact match...
|
||||||
|
if (ts === timestamp) {
|
||||||
|
// This is a case where we'll need to
|
||||||
|
// split up what we want to insert.
|
||||||
|
return mid + after ? -1 : 1;
|
||||||
|
} else {
|
||||||
|
// Found our index?
|
||||||
|
if (max === min) {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
// Otherwise, search recursively
|
||||||
|
if (ts < timestamp) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Booleanize
|
||||||
|
after = !!after;
|
||||||
|
|
||||||
|
return binSearch(0, length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertSeriesWindow(seriesWindow) {
|
||||||
|
var startIndex = findStartIndex(),
|
||||||
|
endIndex = findEndIndex();
|
||||||
|
|
||||||
|
if (startIndex === endIndex) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Split it up, and add the two halves
|
||||||
|
seriesWindow.split().forEach(insertSeriesWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow(series, domain, range) {
|
||||||
|
// TODO: Enforce time window, too!
|
||||||
|
return new PlotSeriesWindow(
|
||||||
|
series,
|
||||||
|
domain,
|
||||||
|
range,
|
||||||
|
0,
|
||||||
|
series.getPointCount()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addData: function (domainValue, rangeValue) {
|
||||||
|
// Should append to buffer
|
||||||
|
},
|
||||||
|
addSeries: function (series, domain, range) {
|
||||||
|
// Should try to add via insertion if a
|
||||||
|
// clear insertion point is available;
|
||||||
|
// if not, should split and add each half.
|
||||||
|
// Insertion operation also needs to factor out
|
||||||
|
// redundant timestamps, for overlapping data
|
||||||
|
insertSeriesWindow(createWindow(series, domain, range));
|
||||||
|
},
|
||||||
|
setTimeWindow: function (start, end) {
|
||||||
|
timeWindow = [ start, end ];
|
||||||
|
},
|
||||||
|
clearTimeWindow: function () {
|
||||||
|
timeWindow = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotLine;
|
||||||
|
}
|
||||||
|
);
|
107
platform/features/plot/src/elements/PlotLineBuffer.js
Normal file
107
platform/features/plot/src/elements/PlotLineBuffer.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*global define,Float32Array*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function PlotLineBuffer(domainOffset, initialSize, maxSize) {
|
||||||
|
var buffer = new Float32Array(initialSize * 2),
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
// Binary search for an insertion index
|
||||||
|
function binSearch(value, min, max) {
|
||||||
|
var mid = Math.floor((min + max) / 2),
|
||||||
|
found = buffer[mid * 2];
|
||||||
|
|
||||||
|
// Collisions are not wanted
|
||||||
|
if (found === value) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if we're down to a single index,
|
||||||
|
// we've found our insertion point
|
||||||
|
if (min >= max) {
|
||||||
|
// Compare the found timestamp with the search
|
||||||
|
// value to decide if we'll insert after or before.
|
||||||
|
return min + ((found < value) ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, do the recursive step
|
||||||
|
if (found < value) {
|
||||||
|
return binSearch(value, mid + 1, max);
|
||||||
|
} else {
|
||||||
|
return binSearch(value, min, mid - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the size of the buffer
|
||||||
|
function doubleBufferSize() {
|
||||||
|
var sz = Math.min(maxSize, buffer.length * 2),
|
||||||
|
canDouble = sz > buffer.length,
|
||||||
|
doubled = canDouble && new Float32Array(sz);
|
||||||
|
|
||||||
|
if (canDouble) {
|
||||||
|
doubled.set(buffer); // Copy contents of original
|
||||||
|
buffer = doubled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canDouble;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getBuffer: function () {
|
||||||
|
return buffer;
|
||||||
|
},
|
||||||
|
insert: function (series, index) {
|
||||||
|
var sz = series.getPointCount(),
|
||||||
|
free = (buffer.length / 2) - length,
|
||||||
|
i;
|
||||||
|
|
||||||
|
// Don't allow append after the end; that doesn't make sense
|
||||||
|
if (index > length) {
|
||||||
|
index = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize if necessary
|
||||||
|
if (sz > free) {
|
||||||
|
if (!doubleBufferSize()) {
|
||||||
|
// TODO: Figure out which data to discard
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert data into the set
|
||||||
|
for (i = 0; i < series.getPointCount(); i += 1) {
|
||||||
|
buffer[(i + index) * 2] =
|
||||||
|
series.getDomainValue(i) - domainOffset;
|
||||||
|
buffer[(i + index) * 2 + 1] =
|
||||||
|
series.getRangeValue(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the length
|
||||||
|
length += sz;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Find an index for inserting data with this
|
||||||
|
* timestamp. The second argument indicates whether
|
||||||
|
* we are searching for insert-before or insert-after
|
||||||
|
* positions.
|
||||||
|
* Timestamps are meant to be unique, so if a collision
|
||||||
|
* occurs, this will return -1.
|
||||||
|
* @param {number} timestamp timestamp to insert
|
||||||
|
* @returns {number} the index for insertion (or -1)
|
||||||
|
*/
|
||||||
|
findInsertionIndex: function (timestamp) {
|
||||||
|
return binSearch(
|
||||||
|
timestamp - domainOffset,
|
||||||
|
0,
|
||||||
|
length - 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotLineBuffer;
|
||||||
|
}
|
||||||
|
);
|
45
platform/features/plot/src/elements/PlotSeriesWindow.js
Normal file
45
platform/features/plot/src/elements/PlotSeriesWindow.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*global define*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a window on a telemetry data series, to support
|
||||||
|
* insertion into a plot line.
|
||||||
|
*/
|
||||||
|
function PlotSeriesWindow(series, domain, range, start, end) {
|
||||||
|
return {
|
||||||
|
getPointCount: function () {
|
||||||
|
return end - start;
|
||||||
|
},
|
||||||
|
getDomainValue: function (index) {
|
||||||
|
return series.getDomainValue(index - start, domain);
|
||||||
|
},
|
||||||
|
getRangeValue: function (index) {
|
||||||
|
return series.getRangeValue(index - start, range);
|
||||||
|
},
|
||||||
|
split: function () {
|
||||||
|
var mid = Math.floor((end + start) / 2);
|
||||||
|
return end > start ?
|
||||||
|
[
|
||||||
|
new PlotSeriesWindow(
|
||||||
|
series,
|
||||||
|
domain,
|
||||||
|
range,
|
||||||
|
start,
|
||||||
|
mid
|
||||||
|
),
|
||||||
|
new PlotSeriesWindow(
|
||||||
|
series,
|
||||||
|
domain,
|
||||||
|
range,
|
||||||
|
mid + 1,
|
||||||
|
end
|
||||||
|
)
|
||||||
|
] : [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
@ -133,8 +133,23 @@ define(
|
|||||||
// Update historical data for this domain object
|
// Update historical data for this domain object
|
||||||
function setHistorical(domainObject) {
|
function setHistorical(domainObject) {
|
||||||
var id = domainObject.getId(),
|
var id = domainObject.getId(),
|
||||||
|
// Buffer to expand
|
||||||
buffer = buffers[id],
|
buffer = buffers[id],
|
||||||
endIndex = realtimeIndex[id] || 0;
|
// Index where historical data ends (and realtime begins)
|
||||||
|
endIndex = realtimeIndex[id] || 0,
|
||||||
|
// Look up the data series
|
||||||
|
series = subscription.getSeries(domainObject),
|
||||||
|
// Get its length
|
||||||
|
seriesLength = series ? series.getPointCount() : 0,
|
||||||
|
// Get the current buffer length...
|
||||||
|
length = lengths[id] || 0,
|
||||||
|
// As well as the length of the realtime segment
|
||||||
|
realtimeLength = length - endIndex,
|
||||||
|
// Determine the new total length of the existing
|
||||||
|
// realtime + new historical segment.
|
||||||
|
totalLength =
|
||||||
|
Math.min(seriesLength + realtimeLength, maxPoints),
|
||||||
|
seriesFit = Math.max(0, totalLength - realtimeLength);
|
||||||
|
|
||||||
// Make sure the buffer is big enough
|
// Make sure the buffer is big enough
|
||||||
|
|
||||||
|
72
platform/features/plot/test/elements/PlotLineBufferSpec.js
Normal file
72
platform/features/plot/test/elements/PlotLineBufferSpec.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../src/elements/PlotLineBuffer"],
|
||||||
|
function (PlotLineBuffer) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var TEST_INITIAL_SIZE = 10,
|
||||||
|
TEST_MAX_SIZE = 40,
|
||||||
|
TEST_DOMAIN_OFFSET = 42;
|
||||||
|
|
||||||
|
describe("A plot line buffer", function () {
|
||||||
|
var mockSeries,
|
||||||
|
testDomainValues,
|
||||||
|
testRangeValues,
|
||||||
|
buffer;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testDomainValues = [ 1, 3, 7, 9, 14, 15 ];
|
||||||
|
testRangeValues = [ 8, 0, 3, 9, 8, 11 ];
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
"series",
|
||||||
|
['getPointCount', 'getDomainValue', 'getRangeValue']
|
||||||
|
);
|
||||||
|
mockSeries.getPointCount.andCallFake(function () {
|
||||||
|
return testDomainValues.length;
|
||||||
|
});
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
return testDomainValues[i];
|
||||||
|
});
|
||||||
|
mockSeries.getRangeValue.andCallFake(function (i) {
|
||||||
|
return testRangeValues[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
buffer = new PlotLineBuffer(
|
||||||
|
TEST_DOMAIN_OFFSET,
|
||||||
|
TEST_INITIAL_SIZE,
|
||||||
|
TEST_MAX_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start with some data in there
|
||||||
|
buffer.insert(mockSeries, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows insertion of series data", function () {
|
||||||
|
// Convert to a regular array for checking.
|
||||||
|
// Verify that domain/ranges were interleaved and
|
||||||
|
// that domain offset was adjusted for.
|
||||||
|
expect(
|
||||||
|
Array.prototype.slice.call(buffer.getBuffer()).slice(0, 12)
|
||||||
|
).toEqual([ -41, 8, -39, 0, -35, 3, -33, 9, -28, 8, -27, 11]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("finds insertion indexes", function () {
|
||||||
|
expect(buffer.findInsertionIndex(0)).toEqual(0);
|
||||||
|
expect(buffer.findInsertionIndex(2)).toEqual(1);
|
||||||
|
expect(buffer.findInsertionIndex(5)).toEqual(2);
|
||||||
|
expect(buffer.findInsertionIndex(10)).toEqual(4);
|
||||||
|
expect(buffer.findInsertionIndex(14.5)).toEqual(5);
|
||||||
|
expect(buffer.findInsertionIndex(20)).toEqual(6);
|
||||||
|
|
||||||
|
// 9 is already in there, disallow insertion
|
||||||
|
expect(buffer.findInsertionIndex(9)).toEqual(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -6,6 +6,7 @@
|
|||||||
"SubPlot",
|
"SubPlot",
|
||||||
"SubPlotFactory",
|
"SubPlotFactory",
|
||||||
"elements/PlotAxis",
|
"elements/PlotAxis",
|
||||||
|
"elements/PlotLineBuffer",
|
||||||
"elements/PlotPalette",
|
"elements/PlotPalette",
|
||||||
"elements/PlotPanZoomStack",
|
"elements/PlotPanZoomStack",
|
||||||
"elements/PlotPanZoomStackGroup",
|
"elements/PlotPanZoomStackGroup",
|
||||||
|
Reference in New Issue
Block a user