mirror of
https://github.com/nasa/openmct.git
synced 2025-05-31 22:50:49 +00:00
Merge branch 'open806' into open-master
This commit is contained in:
commit
617691ab24
@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"key": "PlotController",
|
"key": "PlotController",
|
||||||
"implementation": "PlotController.js",
|
"implementation": "PlotController.js",
|
||||||
"depends": [ "$scope", "telemetryFormatter", "telemetrySubscriber" ]
|
"depends": [ "$scope", "telemetryFormatter", "telemetryHandler" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,13 @@ define(
|
|||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function PlotController($scope, telemetryFormatter, telemetrySubscriber) {
|
function PlotController($scope, telemetryFormatter, telemetryHandler) {
|
||||||
var subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
var subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||||
modeOptions = new PlotModeOptions([], subPlotFactory),
|
modeOptions = new PlotModeOptions([], subPlotFactory),
|
||||||
subplots = [],
|
subplots = [],
|
||||||
cachedObjects = [],
|
cachedObjects = [],
|
||||||
updater,
|
updater,
|
||||||
subscription,
|
handle,
|
||||||
domainOffset;
|
domainOffset;
|
||||||
|
|
||||||
// Populate the scope with axis information (specifically, options
|
// Populate the scope with axis information (specifically, options
|
||||||
@ -77,7 +77,7 @@ define(
|
|||||||
// new subscription.) This will clear the plot.
|
// new subscription.) This will clear the plot.
|
||||||
function recreateUpdater() {
|
function recreateUpdater() {
|
||||||
updater = new PlotUpdater(
|
updater = new PlotUpdater(
|
||||||
subscription,
|
handle,
|
||||||
($scope.axes[0].active || {}).key,
|
($scope.axes[0].active || {}).key,
|
||||||
($scope.axes[1].active || {}).key
|
($scope.axes[1].active || {}).key
|
||||||
);
|
);
|
||||||
@ -85,8 +85,8 @@ define(
|
|||||||
|
|
||||||
// Handle new telemetry data in this plot
|
// Handle new telemetry data in this plot
|
||||||
function updateValues() {
|
function updateValues() {
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
setupModes(subscription.getTelemetryObjects());
|
setupModes(handle.getTelemetryObjects());
|
||||||
}
|
}
|
||||||
if (updater) {
|
if (updater) {
|
||||||
updater.update();
|
updater.update();
|
||||||
@ -95,29 +95,44 @@ define(
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display new historical data as it becomes available
|
||||||
|
function addHistoricalData(domainObject, series) {
|
||||||
|
updater.addHistorical(domainObject, series);
|
||||||
|
modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue a new request for historical telemetry
|
||||||
|
function requestTelemetry() {
|
||||||
|
if (handle && updater) {
|
||||||
|
handle.request({}, addHistoricalData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new subscription; telemetrySubscriber gets
|
// Create a new subscription; telemetrySubscriber gets
|
||||||
// to do the meaningful work here.
|
// to do the meaningful work here.
|
||||||
function subscribe(domainObject) {
|
function subscribe(domainObject) {
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
subscription.unsubscribe();
|
handle.unsubscribe();
|
||||||
}
|
}
|
||||||
subscription = domainObject && telemetrySubscriber.subscribe(
|
handle = domainObject && telemetryHandler.handle(
|
||||||
domainObject,
|
domainObject,
|
||||||
updateValues,
|
updateValues,
|
||||||
true // Lossless
|
true // Lossless
|
||||||
);
|
);
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
setupModes(subscription.getTelemetryObjects());
|
setupModes(handle.getTelemetryObjects());
|
||||||
setupAxes(subscription.getMetadata());
|
setupAxes(handle.getMetadata());
|
||||||
recreateUpdater();
|
recreateUpdater();
|
||||||
|
requestTelemetry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the current subscription (called when scope is destroyed)
|
// Release the current subscription (called when scope is destroyed)
|
||||||
function releaseSubscription() {
|
function releaseSubscription() {
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
subscription.unsubscribe();
|
handle.unsubscribe();
|
||||||
subscription = undefined;
|
handle = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
94
platform/features/plot/src/elements/PlotLine.js
Normal file
94
platform/features/plot/src/elements/PlotLine.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*global define,Float32Array*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./PlotSeriesWindow'],
|
||||||
|
function (PlotSeriesWindow) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
function PlotLine(buffer) {
|
||||||
|
|
||||||
|
// Insert a time-windowed data series into the buffer
|
||||||
|
function insertSeriesWindow(seriesWindow) {
|
||||||
|
var count = seriesWindow.getPointCount();
|
||||||
|
|
||||||
|
function doInsert() {
|
||||||
|
var firstTimestamp = seriesWindow.getDomainValue(0),
|
||||||
|
lastTimestamp = seriesWindow.getDomainValue(count - 1),
|
||||||
|
startIndex = buffer.findInsertionIndex(firstTimestamp),
|
||||||
|
endIndex = buffer.findInsertionIndex(lastTimestamp);
|
||||||
|
|
||||||
|
// Does the whole series fit in between two adjacent indexes?
|
||||||
|
if ((startIndex === endIndex) && startIndex > -1) {
|
||||||
|
// Insert it in between
|
||||||
|
buffer.insert(seriesWindow, startIndex);
|
||||||
|
} else {
|
||||||
|
// Split it up, and add the two halves
|
||||||
|
seriesWindow.split().forEach(insertSeriesWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only insert if there are points to insert
|
||||||
|
if (count > 0) {
|
||||||
|
doInsert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow(series, domain, range) {
|
||||||
|
return new PlotSeriesWindow(
|
||||||
|
series,
|
||||||
|
domain,
|
||||||
|
range,
|
||||||
|
0,
|
||||||
|
series.getPointCount()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Add a point to this plot line.
|
||||||
|
* @param {number} domainValue the domain value
|
||||||
|
* @param {number} rangeValue the range value
|
||||||
|
*/
|
||||||
|
addPoint: function (domainValue, rangeValue) {
|
||||||
|
var index;
|
||||||
|
// Make sure we got real/useful values here...
|
||||||
|
if (domainValue !== undefined && rangeValue !== undefined) {
|
||||||
|
index = buffer.findInsertionIndex(domainValue);
|
||||||
|
|
||||||
|
// Already in the buffer? Skip insertion
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the point
|
||||||
|
if (!buffer.insertPoint(domainValue, rangeValue, index)) {
|
||||||
|
// If insertion failed, trim from the beginning...
|
||||||
|
buffer.trim(1);
|
||||||
|
// ...and try again.
|
||||||
|
buffer.insertPoint(domainValue, rangeValue, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Add a series of telemetry data to this plot line.
|
||||||
|
* @param {TelemetrySeries} series the data series
|
||||||
|
* @param {string} [domain] the key indicating which domain
|
||||||
|
* to use when looking up data from this series
|
||||||
|
* @param {string} [range] the key indicating which range
|
||||||
|
* to use when looking up data from this series
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotLine;
|
||||||
|
}
|
||||||
|
);
|
240
platform/features/plot/src/elements/PlotLineBuffer.js
Normal file
240
platform/features/plot/src/elements/PlotLineBuffer.js
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/*global define,Float32Array*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the buffer used to draw a plot.
|
||||||
|
* @param {number} domainOffset number to subtract from domain values
|
||||||
|
* @param {number} initialSize initial buffer size
|
||||||
|
* @param {number} maxSize maximum buffer size
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function PlotLineBuffer(domainOffset, initialSize, maxSize) {
|
||||||
|
var buffer = new Float32Array(initialSize * 2),
|
||||||
|
rangeExtrema = [ Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY ],
|
||||||
|
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 * 2, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease the size of the buffer
|
||||||
|
function halveBufferSize() {
|
||||||
|
var sz = Math.max(initialSize * 2, buffer.length / 2),
|
||||||
|
canHalve = sz < buffer.length;
|
||||||
|
|
||||||
|
if (canHalve) {
|
||||||
|
buffer = new Float32Array(buffer.subarray(0, sz));
|
||||||
|
}
|
||||||
|
|
||||||
|
return canHalve;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a value in the buffer
|
||||||
|
function setValue(index, domainValue, rangeValue) {
|
||||||
|
buffer[index * 2] = domainValue - domainOffset;
|
||||||
|
buffer[index * 2 + 1] = rangeValue;
|
||||||
|
// Track min/max of range values (min/max for
|
||||||
|
// domain values can be read directly from buffer)
|
||||||
|
rangeExtrema[0] = Math.min(rangeExtrema[0], rangeValue);
|
||||||
|
rangeExtrema[1] = Math.max(rangeExtrema[1], rangeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the WebGL-displayable buffer of points to plot.
|
||||||
|
* @returns {Float32Array} displayable buffer for this line
|
||||||
|
*/
|
||||||
|
getBuffer: function () {
|
||||||
|
return buffer;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the number of points stored in this buffer.
|
||||||
|
* @returns {number} the number of points stored
|
||||||
|
*/
|
||||||
|
getLength: function () {
|
||||||
|
return length;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the min/max range values that are currently in this
|
||||||
|
* buffer. Unlike range extrema, these will change as the
|
||||||
|
* buffer gets trimmed.
|
||||||
|
* @returns {number[]} min, max domain values
|
||||||
|
*/
|
||||||
|
getDomainExtrema: function () {
|
||||||
|
// Since these are ordered in the buffer, assume
|
||||||
|
// these are the values at the first and last index
|
||||||
|
return [
|
||||||
|
buffer[0] + domainOffset,
|
||||||
|
buffer[length * 2 - 2] + domainOffset
|
||||||
|
];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the min/max range values that have been observed for this
|
||||||
|
* buffer. Note that these values may have been trimmed out at
|
||||||
|
* some point.
|
||||||
|
* @returns {number[]} min, max range values
|
||||||
|
*/
|
||||||
|
getRangeExtrema: function () {
|
||||||
|
return rangeExtrema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Remove values from this buffer.
|
||||||
|
* Normally, values are removed from the start
|
||||||
|
* of the buffer; a truthy value in the second argument
|
||||||
|
* will cause values to be removed from the end.
|
||||||
|
* @param {number} count number of values to remove
|
||||||
|
* @param {boolean} [fromEnd] true if the most recent
|
||||||
|
* values should be removed
|
||||||
|
*/
|
||||||
|
trim: function (count, fromEnd) {
|
||||||
|
// If we're removing values from the start...
|
||||||
|
if (!fromEnd) {
|
||||||
|
// ...do so by shifting buffer contents over
|
||||||
|
buffer.set(buffer.subarray(2 * count));
|
||||||
|
}
|
||||||
|
// Reduce used buffer size accordingly
|
||||||
|
length -= count;
|
||||||
|
// Finally, if less than half of the buffer is being
|
||||||
|
// used, free up some memory.
|
||||||
|
if (length < buffer.length / 4) {
|
||||||
|
halveBufferSize();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Insert data from the provided series at the specified
|
||||||
|
* index. If this would exceed the buffer's maximum capacity,
|
||||||
|
* this operation fails and the buffer is unchanged.
|
||||||
|
* @param {TelemetrySeries} series the series to insert
|
||||||
|
* @param {number} index the index at which to insert this
|
||||||
|
* series
|
||||||
|
* @returns {boolean} true if insertion succeeded; otherwise
|
||||||
|
* false
|
||||||
|
*/
|
||||||
|
insert: function (series, index) {
|
||||||
|
var sz = series.getPointCount(),
|
||||||
|
i;
|
||||||
|
|
||||||
|
// Don't allow append after the end; that doesn't make sense
|
||||||
|
index = Math.min(index, length);
|
||||||
|
|
||||||
|
// Resize if necessary
|
||||||
|
while (sz > ((buffer.length / 2) - length)) {
|
||||||
|
if (!doubleBufferSize()) {
|
||||||
|
// Can't make room for this, insertion fails
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift data over if necessary
|
||||||
|
if (index < length) {
|
||||||
|
buffer.set(
|
||||||
|
buffer.subarray(index * 2, length * 2),
|
||||||
|
(index + sz) * 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert data into the set
|
||||||
|
for (i = 0; i < sz; i += 1) {
|
||||||
|
setValue(
|
||||||
|
i + index,
|
||||||
|
series.getDomainValue(i),
|
||||||
|
series.getRangeValue(i)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase the length
|
||||||
|
length += sz;
|
||||||
|
|
||||||
|
// Indicate that insertion was successful
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Append a single data point.
|
||||||
|
*/
|
||||||
|
insertPoint: function (domainValue, rangeValue, index) {
|
||||||
|
// Don't allow
|
||||||
|
index = Math.min(length, index);
|
||||||
|
|
||||||
|
// Ensure there is space for this point
|
||||||
|
if (length >= (buffer.length / 2)) {
|
||||||
|
if (!doubleBufferSize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the data in the buffer
|
||||||
|
setValue(length, domainValue, rangeValue);
|
||||||
|
|
||||||
|
// Update length
|
||||||
|
length += 1;
|
||||||
|
|
||||||
|
// Indicate that this was successful
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
var value = timestamp - domainOffset;
|
||||||
|
|
||||||
|
// Handle empty buffer case and check for an
|
||||||
|
// append opportunity (which is most common case for
|
||||||
|
// real-time data so is optimized-for) before falling
|
||||||
|
// back to a binary search for the insertion point.
|
||||||
|
return (length < 1) ? 0 :
|
||||||
|
(value > buffer[length * 2 - 2]) ? length :
|
||||||
|
binSearch(value, 0, length - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotLineBuffer;
|
||||||
|
}
|
||||||
|
);
|
47
platform/features/plot/src/elements/PlotSeriesWindow.js
Normal file
47
platform/features/plot/src/elements/PlotSeriesWindow.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*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) > 1) ?
|
||||||
|
[
|
||||||
|
new PlotSeriesWindow(
|
||||||
|
series,
|
||||||
|
domain,
|
||||||
|
range,
|
||||||
|
start,
|
||||||
|
mid
|
||||||
|
),
|
||||||
|
new PlotSeriesWindow(
|
||||||
|
series,
|
||||||
|
domain,
|
||||||
|
range,
|
||||||
|
mid,
|
||||||
|
end
|
||||||
|
)
|
||||||
|
] : [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotSeriesWindow;
|
||||||
|
}
|
||||||
|
);
|
@ -5,7 +5,8 @@
|
|||||||
* the conversion from data API to displayable buffers.
|
* the conversion from data API to displayable buffers.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
function () {
|
['./PlotLine', './PlotLineBuffer'],
|
||||||
|
function (PlotLine, PlotLineBuffer) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var MAX_POINTS = 86400,
|
var MAX_POINTS = 86400,
|
||||||
@ -17,117 +18,182 @@ define(
|
|||||||
* Float32Array for each trace, and tracks the boundaries of the
|
* Float32Array for each trace, and tracks the boundaries of the
|
||||||
* data sets (since this is convenient to do during the same pass).
|
* data sets (since this is convenient to do during the same pass).
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {Telemetry[]} datas telemetry data objects
|
* @param {TelemetryHandle} handle the handle to telemetry access
|
||||||
* @param {string} domain the key to use when looking up domain values
|
* @param {string} domain the key to use when looking up domain values
|
||||||
* @param {string} range the key to use when looking up range values
|
* @param {string} range the key to use when looking up range values
|
||||||
*/
|
*/
|
||||||
function PlotUpdater(subscription, domain, range, maxPoints) {
|
function PlotUpdater(handle, domain, range, maxPoints) {
|
||||||
var max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
var ids = [],
|
||||||
min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
lines = {},
|
||||||
x,
|
dimensions = [0, 0],
|
||||||
y,
|
origin = [0, 0],
|
||||||
domainOffset,
|
domainExtrema,
|
||||||
buffers = {},
|
rangeExtrema,
|
||||||
lengths = {},
|
bufferArray = [],
|
||||||
lengthArray = [],
|
domainOffset;
|
||||||
bufferArray = [];
|
|
||||||
|
|
||||||
// Double the size of a Float32Array
|
// Look up a domain object's id (for mapping, below)
|
||||||
function doubleSize(buffer) {
|
function getId(domainObject) {
|
||||||
var doubled = new Float32Array(buffer.length * 2);
|
return domainObject.getId();
|
||||||
doubled.set(buffer); // Copy contents of original
|
|
||||||
return doubled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure there is enough space in a buffer to accomodate a
|
// Check if this set of ids matches the current set of ids
|
||||||
// new point at the specified index. This will updates buffers[id]
|
// (used to detect if line preparation can be skipped)
|
||||||
// if necessary.
|
function idsMatch(nextIds) {
|
||||||
function ensureBufferSize(buffer, id, index) {
|
return nextIds.map(function (id, index) {
|
||||||
// Check if we don't have enough room
|
return ids[index] === id;
|
||||||
if (index > (buffer.length / 2 - 1)) {
|
}).reduce(function (a, b) {
|
||||||
// If we don't, can we expand?
|
return a && b;
|
||||||
if (index < maxPoints) {
|
}, true);
|
||||||
// Double the buffer size
|
|
||||||
buffer = buffers[id] = doubleSize(buffer);
|
|
||||||
} else {
|
|
||||||
// Just shift the existing buffer
|
|
||||||
buffer.set(buffer.subarray(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data to the plot.
|
// Prepare plot lines for this group of telemetry objects
|
||||||
function addData(obj) {
|
function prepareLines(telemetryObjects) {
|
||||||
var id = obj.getId(),
|
var nextIds = telemetryObjects.map(getId),
|
||||||
index = lengths[id] || 0,
|
next = {};
|
||||||
buffer = buffers[id],
|
|
||||||
domainValue = subscription.getDomainValue(obj, domain),
|
|
||||||
rangeValue = subscription.getRangeValue(obj, range);
|
|
||||||
|
|
||||||
// If we don't already have a data buffer for that ID,
|
// Detect if we already have everything we need prepared
|
||||||
// make one.
|
if (ids.length === nextIds.length && idsMatch(nextIds)) {
|
||||||
if (!buffer) {
|
// Nothing to prepare, move on
|
||||||
buffer = new Float32Array(INITIAL_SIZE);
|
return;
|
||||||
buffers[id] = buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure there's data to add, and then add it
|
// Built up a set of ids. Note that we can only
|
||||||
if (domainValue !== undefined && rangeValue !== undefined &&
|
// create plot lines after our domain offset has
|
||||||
(index < 1 || domainValue !== buffer[index * 2 - 2])) {
|
// been determined.
|
||||||
// Use the first observed domain value as a domainOffset
|
if (domainOffset !== undefined) {
|
||||||
domainOffset = domainOffset !== undefined ?
|
// Update list of ids in use
|
||||||
domainOffset : domainValue;
|
ids = nextIds;
|
||||||
// Ensure there is space for the new buffer
|
|
||||||
buffer = ensureBufferSize(buffer, id, index);
|
// Create buffers for these objects
|
||||||
// Account for shifting that may have occurred
|
bufferArray = ids.map(function (id) {
|
||||||
index = Math.min(index, maxPoints - 1);
|
var buffer = new PlotLineBuffer(
|
||||||
// Update the buffer
|
domainOffset,
|
||||||
buffer[index * 2] = domainValue - domainOffset;
|
INITIAL_SIZE,
|
||||||
buffer[index * 2 + 1] = rangeValue;
|
maxPoints
|
||||||
// Update length
|
);
|
||||||
lengths[id] = Math.min(index + 1, maxPoints);
|
next[id] = lines[id] || new PlotLine(buffer);
|
||||||
// Observe max/min range values
|
return buffer;
|
||||||
max[1] = Math.max(max[1], rangeValue);
|
});
|
||||||
min[1] = Math.min(min[1], rangeValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer;
|
// If there are no more lines, clear the domain offset
|
||||||
|
if (Object.keys(next).length < 1) {
|
||||||
|
domainOffset = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update to the current set of lines
|
||||||
|
lines = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update min/max domain values for these objects
|
// Initialize the domain offset, based on these observed values
|
||||||
function updateDomainExtrema(objects) {
|
function initializeDomainOffset(values) {
|
||||||
max[0] = Number.NEGATIVE_INFINITY;
|
domainOffset =
|
||||||
min[0] = Number.POSITIVE_INFINITY;
|
((domainOffset === undefined) && (values.length > 0)) ?
|
||||||
objects.forEach(function (obj) {
|
(values.reduce(function (a, b) {
|
||||||
var id = obj.getId(),
|
return (a || 0) + (b || 0);
|
||||||
buffer = buffers[id],
|
}, 0) / values.length) :
|
||||||
length = lengths[id],
|
domainOffset;
|
||||||
low = buffer[0] + domainOffset,
|
}
|
||||||
high = buffer[length * 2 - 2] + domainOffset;
|
|
||||||
max[0] = Math.max(high, max[0]);
|
// Used in the reduce step of updateExtrema
|
||||||
min[0] = Math.min(low, min[0]);
|
function reduceExtrema(a, b) {
|
||||||
});
|
return [ Math.min(a[0], b[0]), Math.max(a[1], b[1]) ];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a domain/range extrema to plot dimensions
|
||||||
|
function dimensionsOf(extrema) {
|
||||||
|
return extrema[1] - extrema[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a domain/range extrema to a plot origin
|
||||||
|
function originOf(extrema) {
|
||||||
|
return extrema[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dimensions and origin based on extrema of plots
|
||||||
|
function updateExtrema() {
|
||||||
|
if (bufferArray.length > 0) {
|
||||||
|
domainExtrema = bufferArray.map(function (lineBuffer) {
|
||||||
|
return lineBuffer.getDomainExtrema();
|
||||||
|
}).reduce(reduceExtrema);
|
||||||
|
|
||||||
|
rangeExtrema = bufferArray.map(function (lineBuffer) {
|
||||||
|
return lineBuffer.getRangeExtrema();
|
||||||
|
}).reduce(reduceExtrema);
|
||||||
|
|
||||||
|
dimensions = (rangeExtrema[0] === rangeExtrema[1]) ?
|
||||||
|
[dimensionsOf(domainExtrema), 2.0 ] :
|
||||||
|
[dimensionsOf(domainExtrema), dimensionsOf(rangeExtrema)];
|
||||||
|
origin = [originOf(domainExtrema), originOf(rangeExtrema)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add latest data for this domain object
|
||||||
|
function addPointFor(domainObject) {
|
||||||
|
var line = lines[domainObject.getId()];
|
||||||
|
if (line) {
|
||||||
|
line.addPoint(
|
||||||
|
handle.getDomainValue(domainObject, domain),
|
||||||
|
handle.getRangeValue(domainObject, range)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new telemetry data
|
// Handle new telemetry data
|
||||||
function update() {
|
function update() {
|
||||||
var objects = subscription.getTelemetryObjects();
|
var objects = handle.getTelemetryObjects();
|
||||||
bufferArray = objects.map(addData);
|
|
||||||
lengthArray = objects.map(function (obj) {
|
// Initialize domain offset if necessary
|
||||||
return lengths[obj.getId()];
|
if (domainOffset === undefined) {
|
||||||
});
|
initializeDomainOffset(objects.map(function (obj) {
|
||||||
updateDomainExtrema(objects);
|
return handle.getDomainValue(obj, domain);
|
||||||
|
}).filter(function (value) {
|
||||||
|
return typeof value === 'number';
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure lines are available
|
||||||
|
prepareLines(objects);
|
||||||
|
|
||||||
|
// Add new data
|
||||||
|
objects.forEach(addPointFor);
|
||||||
|
|
||||||
|
// Finally, update extrema
|
||||||
|
updateExtrema();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare buffers and related state for this object
|
// Add historical data for this domain object
|
||||||
function prepare(telemetryObject) {
|
function setHistorical(domainObject, series) {
|
||||||
var id = telemetryObject.getId();
|
var count = series ? series.getPointCount() : 0,
|
||||||
lengths[id] = 0;
|
line;
|
||||||
buffers[id] = new Float32Array(INITIAL_SIZE);
|
|
||||||
lengthArray.push(lengths[id]);
|
// Nothing to do if it's an empty series
|
||||||
bufferArray.push(buffers[id]);
|
if (count < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize domain offset if necessary
|
||||||
|
if (domainOffset === undefined) {
|
||||||
|
initializeDomainOffset([
|
||||||
|
series.getDomainValue(0, domain),
|
||||||
|
series.getDomainValue(count - 1, domain)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure lines are available
|
||||||
|
prepareLines(handle.getTelemetryObjects());
|
||||||
|
|
||||||
|
// Look up the line for this domain object
|
||||||
|
line = lines[domainObject.getId()];
|
||||||
|
|
||||||
|
// ...and put the data into it.
|
||||||
|
if (line) {
|
||||||
|
line.addSeries(series, domain, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, update extrema
|
||||||
|
updateExtrema();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a default MAX_POINTS if none is provided
|
// Use a default MAX_POINTS if none is provided
|
||||||
@ -136,7 +202,7 @@ define(
|
|||||||
// Initially prepare state for these objects.
|
// Initially prepare state for these objects.
|
||||||
// Note that this may be an empty array at this time,
|
// Note that this may be an empty array at this time,
|
||||||
// so we also need to check during update cycles.
|
// so we also need to check during update cycles.
|
||||||
subscription.getTelemetryObjects().forEach(prepare);
|
update();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@ -146,10 +212,7 @@ define(
|
|||||||
* @returns {number[]} the dimensions which bound this data set
|
* @returns {number[]} the dimensions which bound this data set
|
||||||
*/
|
*/
|
||||||
getDimensions: function () {
|
getDimensions: function () {
|
||||||
// Pad range if necessary
|
return dimensions;
|
||||||
return (max[1] === min[1]) ?
|
|
||||||
[max[0] - min[0], 2.0 ] :
|
|
||||||
[max[0] - min[0], max[1] - min[1]];
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Get the origin of this data set's boundary.
|
* Get the origin of this data set's boundary.
|
||||||
@ -160,7 +223,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
getOrigin: function () {
|
getOrigin: function () {
|
||||||
// Pad range if necessary
|
// Pad range if necessary
|
||||||
return (max[1] === min[1]) ? [ min[0], min[1] - 1.0 ] : min;
|
return origin;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Get the domain offset; this offset will have been subtracted
|
* Get the domain offset; this offset will have been subtracted
|
||||||
@ -189,23 +252,17 @@ define(
|
|||||||
*
|
*
|
||||||
* @returns {Float32Array[]} the buffers for these traces
|
* @returns {Float32Array[]} the buffers for these traces
|
||||||
*/
|
*/
|
||||||
getBuffers: function () {
|
getLineBuffers: function () {
|
||||||
return bufferArray;
|
return bufferArray;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* Get the number of points in the buffer with the specified
|
|
||||||
* index. Buffers are padded to minimize memory allocations,
|
|
||||||
* so user code will need this information to know how much
|
|
||||||
* data to plot.
|
|
||||||
* @returns {number} the number of points in this buffer
|
|
||||||
*/
|
|
||||||
getLength: function (index) {
|
|
||||||
return lengthArray[index] || 0;
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Update with latest data.
|
* Update with latest data.
|
||||||
*/
|
*/
|
||||||
update: update
|
update: update,
|
||||||
|
/**
|
||||||
|
* Fill in historical data.
|
||||||
|
*/
|
||||||
|
addHistorical: setHistorical
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,11 +34,11 @@ define(
|
|||||||
subplot.setDomainOffset(prepared.getDomainOffset());
|
subplot.setDomainOffset(prepared.getDomainOffset());
|
||||||
|
|
||||||
// Draw the buffers. Select color by index.
|
// Draw the buffers. Select color by index.
|
||||||
subplot.getDrawingObject().lines = prepared.getBuffers().map(function (buf, i) {
|
subplot.getDrawingObject().lines = prepared.getLineBuffers().map(function (buf, i) {
|
||||||
return {
|
return {
|
||||||
buffer: buf,
|
buffer: buf.getBuffer(),
|
||||||
color: PlotPalette.getFloatColor(i),
|
color: PlotPalette.getFloatColor(i),
|
||||||
points: prepared.getLength(i)
|
points: buf.getLength()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
function plotTelemetryTo(subplot, prepared, index) {
|
function plotTelemetryTo(subplot, prepared, index) {
|
||||||
var buffer = prepared.getBuffers()[index];
|
var buffer = prepared.getLineBuffers()[index];
|
||||||
|
|
||||||
// Track the domain offset, used to bias domain values
|
// Track the domain offset, used to bias domain values
|
||||||
// to minimize loss of precision when converted to 32-bit
|
// to minimize loss of precision when converted to 32-bit
|
||||||
@ -33,9 +33,9 @@ define(
|
|||||||
// Draw the buffers. Always use the 0th color, because there
|
// Draw the buffers. Always use the 0th color, because there
|
||||||
// is one line per plot.
|
// is one line per plot.
|
||||||
subplot.getDrawingObject().lines = [{
|
subplot.getDrawingObject().lines = [{
|
||||||
buffer: buffer,
|
buffer: buffer.getBuffer(),
|
||||||
color: PlotPalette.getFloatColor(0),
|
color: PlotPalette.getFloatColor(0),
|
||||||
points: prepared.getLength(index)
|
points: buffer.getLength()
|
||||||
}];
|
}];
|
||||||
|
|
||||||
subplot.update();
|
subplot.update();
|
||||||
|
@ -11,9 +11,10 @@ define(
|
|||||||
describe("The plot controller", function () {
|
describe("The plot controller", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
mockFormatter,
|
mockFormatter,
|
||||||
mockSubscriber,
|
mockHandler,
|
||||||
mockSubscription,
|
mockHandle,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
|
mockSeries,
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
|
|
||||||
@ -30,28 +31,33 @@ define(
|
|||||||
"domainObject",
|
"domainObject",
|
||||||
[ "getId", "getModel", "getCapability" ]
|
[ "getId", "getModel", "getCapability" ]
|
||||||
);
|
);
|
||||||
mockSubscriber = jasmine.createSpyObj(
|
mockHandler = jasmine.createSpyObj(
|
||||||
"telemetrySubscriber",
|
"telemetrySubscriber",
|
||||||
["subscribe"]
|
["handle"]
|
||||||
);
|
);
|
||||||
mockSubscription = jasmine.createSpyObj(
|
mockHandle = jasmine.createSpyObj(
|
||||||
"subscription",
|
"subscription",
|
||||||
[
|
[
|
||||||
"unsubscribe",
|
"unsubscribe",
|
||||||
"getTelemetryObjects",
|
"getTelemetryObjects",
|
||||||
"getMetadata",
|
"getMetadata",
|
||||||
"getDomainValue",
|
"getDomainValue",
|
||||||
"getRangeValue"
|
"getRangeValue",
|
||||||
|
"request"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
['getPointCount', 'getDomainValue', 'getRangeValue']
|
||||||
|
);
|
||||||
|
|
||||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
mockHandler.handle.andReturn(mockHandle);
|
||||||
mockSubscription.getTelemetryObjects.andReturn([mockDomainObject]);
|
mockHandle.getTelemetryObjects.andReturn([mockDomainObject]);
|
||||||
mockSubscription.getMetadata.andReturn([{}]);
|
mockHandle.getMetadata.andReturn([{}]);
|
||||||
mockSubscription.getDomainValue.andReturn(123);
|
mockHandle.getDomainValue.andReturn(123);
|
||||||
mockSubscription.getRangeValue.andReturn(42);
|
mockHandle.getRangeValue.andReturn(42);
|
||||||
|
|
||||||
controller = new PlotController(mockScope, mockFormatter, mockSubscriber);
|
controller = new PlotController(mockScope, mockFormatter, mockHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("provides plot colors", function () {
|
it("provides plot colors", function () {
|
||||||
@ -71,7 +77,7 @@ define(
|
|||||||
// Make an object available
|
// Make an object available
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
// Should have subscribed
|
// Should have subscribed
|
||||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
expect(mockHandler.handle).toHaveBeenCalledWith(
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
jasmine.any(Function),
|
jasmine.any(Function),
|
||||||
true // Lossless
|
true // Lossless
|
||||||
@ -92,7 +98,7 @@ define(
|
|||||||
expect(controller.getSubPlots().length > 0).toBeTruthy();
|
expect(controller.getSubPlots().length > 0).toBeTruthy();
|
||||||
|
|
||||||
// Broadcast data
|
// Broadcast data
|
||||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
mockHandler.handle.mostRecentCall.args[1]();
|
||||||
|
|
||||||
controller.getSubPlots().forEach(function (subplot) {
|
controller.getSubPlots().forEach(function (subplot) {
|
||||||
expect(subplot.getDrawingObject().lines)
|
expect(subplot.getDrawingObject().lines)
|
||||||
@ -104,17 +110,17 @@ define(
|
|||||||
// Make an object available
|
// Make an object available
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
// Verify precondition - shouldn't unsubscribe yet
|
// Verify precondition - shouldn't unsubscribe yet
|
||||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||||
// Remove the domain object
|
// Remove the domain object
|
||||||
mockScope.$watch.mostRecentCall.args[1](undefined);
|
mockScope.$watch.mostRecentCall.args[1](undefined);
|
||||||
// Should have unsubscribed
|
// Should have unsubscribed
|
||||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("changes modes depending on number of objects", function () {
|
it("changes modes depending on number of objects", function () {
|
||||||
// Act like one object is available
|
// Act like one object is available
|
||||||
mockSubscription.getTelemetryObjects.andReturn([
|
mockHandle.getTelemetryObjects.andReturn([
|
||||||
mockDomainObject
|
mockDomainObject
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -124,7 +130,7 @@ define(
|
|||||||
expect(controller.getModeOptions().length).toEqual(1);
|
expect(controller.getModeOptions().length).toEqual(1);
|
||||||
|
|
||||||
// Act like one object is available
|
// Act like one object is available
|
||||||
mockSubscription.getTelemetryObjects.andReturn([
|
mockHandle.getTelemetryObjects.andReturn([
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockDomainObject
|
mockDomainObject
|
||||||
@ -174,17 +180,26 @@ define(
|
|||||||
expect(controller.isRequestPending()).toBeFalsy();
|
expect(controller.isRequestPending()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requests historical telemetry", function () {
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockHandle.request).toHaveBeenCalled();
|
||||||
|
mockHandle.request.mostRecentCall.args[1](
|
||||||
|
mockDomainObject,
|
||||||
|
mockSeries
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("unsubscribes when destroyed", function () {
|
it("unsubscribes when destroyed", function () {
|
||||||
// Make an object available
|
// Make an object available
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
// Make sure $destroy is what's listened for
|
// Make sure $destroy is what's listened for
|
||||||
expect(mockScope.$on.mostRecentCall.args[0]).toEqual('$destroy');
|
expect(mockScope.$on.mostRecentCall.args[0]).toEqual('$destroy');
|
||||||
// Also verify precondition
|
// Also verify precondition
|
||||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||||
// Destroy the scope
|
// Destroy the scope
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
// Should have unsubscribed
|
// Should have unsubscribed
|
||||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
151
platform/features/plot/test/elements/PlotLineBufferSpec.js
Normal file
151
platform/features/plot/test/elements/PlotLineBufferSpec.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*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]);
|
||||||
|
expect(buffer.getLength()).toEqual(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows insertion in the middle", function () {
|
||||||
|
var head = [ -41, 8, -39, 0, -35, 3 ],
|
||||||
|
tail = [ -33, 9, -28, 8, -27, 11];
|
||||||
|
buffer.insert(mockSeries, 3);
|
||||||
|
expect(
|
||||||
|
Array.prototype.slice.call(buffer.getBuffer()).slice(0, 24)
|
||||||
|
).toEqual(head.concat(head).concat(tail).concat(tail));
|
||||||
|
expect(buffer.getLength()).toEqual(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows values to be trimmed from the start", function () {
|
||||||
|
buffer.trim(2);
|
||||||
|
expect(buffer.getLength()).toEqual(4);
|
||||||
|
expect(
|
||||||
|
Array.prototype.slice.call(buffer.getBuffer()).slice(0, 8)
|
||||||
|
).toEqual([ -35, 3, -33, 9, -28, 8, -27, 11]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("expands buffer when needed to accommodate more data", function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
// Initial underlying buffer should be twice initial size...
|
||||||
|
// (Since each pair will take up two elements)
|
||||||
|
expect(buffer.getBuffer().length).toEqual(20);
|
||||||
|
|
||||||
|
// Should be able to insert 6 series of 6 points each
|
||||||
|
// (After that, we'll hit the test max of 40)
|
||||||
|
for (i = 1; i < 15; i += 1) {
|
||||||
|
expect(buffer.insertPoint(i * 10, Math.sin(i), i))
|
||||||
|
.toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer should have expanded in the process
|
||||||
|
expect(buffer.getBuffer().length).toEqual(40);
|
||||||
|
|
||||||
|
// Push to maximum size just to make sure...
|
||||||
|
for (i = 1; i < 150; i += 1) {
|
||||||
|
buffer.insertPoint(i * 10, Math.sin(i), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(buffer.getBuffer().length).toEqual(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ensures a maximum size", function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
// Should be able to insert 6 series of 6 points each
|
||||||
|
// (After that, we'll hit the test max of 40)
|
||||||
|
for (i = 1; i < 6; i += 1) {
|
||||||
|
expect(buffer.getLength()).toEqual(6 * i);
|
||||||
|
expect(buffer.insert(mockSeries, Number.POSITIVE_INFINITY))
|
||||||
|
.toBeTruthy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be maxed out now
|
||||||
|
expect(buffer.getLength()).toEqual(36);
|
||||||
|
expect(buffer.insert(mockSeries, Number.POSITIVE_INFINITY))
|
||||||
|
.toBeFalsy();
|
||||||
|
expect(buffer.getLength()).toEqual(36);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reduces buffer size when space is no longer needed", function () {
|
||||||
|
// Check that actual buffer is sized to the initial size
|
||||||
|
// (double TEST_INITIAL_SIZE, since two elements are needed per
|
||||||
|
// point; one for domain, one for range)
|
||||||
|
expect(buffer.getBuffer().length).toEqual(20);
|
||||||
|
// Should have 6 elements now... grow to 24
|
||||||
|
buffer.insert(mockSeries, Number.POSITIVE_INFINITY);
|
||||||
|
buffer.insert(mockSeries, Number.POSITIVE_INFINITY);
|
||||||
|
buffer.insert(mockSeries, Number.POSITIVE_INFINITY);
|
||||||
|
// This should have doubled the actual buffer size
|
||||||
|
expect(buffer.getBuffer().length).toEqual(80);
|
||||||
|
// Remove some values
|
||||||
|
buffer.trim(20);
|
||||||
|
// Actual buffer size should have been reduced accordingly
|
||||||
|
expect(buffer.getBuffer().length).toBeLessThan(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
114
platform/features/plot/test/elements/PlotLineSpec.js
Normal file
114
platform/features/plot/test/elements/PlotLineSpec.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/elements/PlotLine"],
|
||||||
|
function (PlotLine) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A plot line", function () {
|
||||||
|
var mockBuffer,
|
||||||
|
mockSeries,
|
||||||
|
testDomainBuffer,
|
||||||
|
testRangeBuffer,
|
||||||
|
testSeries,
|
||||||
|
line;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testDomainBuffer = [];
|
||||||
|
testRangeBuffer = [];
|
||||||
|
testSeries = [];
|
||||||
|
|
||||||
|
mockBuffer = jasmine.createSpyObj(
|
||||||
|
'buffer',
|
||||||
|
['findInsertionIndex', 'insert', 'insertPoint', 'trim']
|
||||||
|
);
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
['getPointCount', 'getDomainValue', 'getRangeValue']
|
||||||
|
);
|
||||||
|
|
||||||
|
mockSeries.getPointCount.andCallFake(function () {
|
||||||
|
return testSeries.length;
|
||||||
|
});
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
return (testSeries[i] || [])[0];
|
||||||
|
});
|
||||||
|
mockSeries.getRangeValue.andCallFake(function (i) {
|
||||||
|
return (testSeries[i] || [])[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function like PlotLineBuffer, to aid in testability
|
||||||
|
mockBuffer.findInsertionIndex.andCallFake(function (v) {
|
||||||
|
var index = 0;
|
||||||
|
if (testDomainBuffer.indexOf(v) !== -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while ((index < testDomainBuffer.length) &&
|
||||||
|
(testDomainBuffer[index] < v)) {
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
});
|
||||||
|
mockBuffer.insert.andCallFake(function (series, index) {
|
||||||
|
var domains = [], ranges = [], i;
|
||||||
|
for (i = 0; i < series.getPointCount(); i += 1) {
|
||||||
|
domains.push(series.getDomainValue(i));
|
||||||
|
ranges.push(series.getRangeValue(i));
|
||||||
|
}
|
||||||
|
testDomainBuffer = testDomainBuffer.slice(0, index)
|
||||||
|
.concat(domains)
|
||||||
|
.concat(testDomainBuffer.slice(index));
|
||||||
|
testRangeBuffer = testRangeBuffer.slice(0, index)
|
||||||
|
.concat(ranges)
|
||||||
|
.concat(testRangeBuffer.slice(index));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
mockBuffer.insertPoint.andCallFake(function (dv, rv, index) {
|
||||||
|
testDomainBuffer.splice(index, 0, dv);
|
||||||
|
testRangeBuffer.splice(index, 0, rv);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
line = new PlotLine(mockBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows single point insertion", function () {
|
||||||
|
line.addPoint(100, 200);
|
||||||
|
line.addPoint(50, 42);
|
||||||
|
line.addPoint(150, 12321);
|
||||||
|
// Should have managed insertion index choices to get to...
|
||||||
|
expect(testDomainBuffer).toEqual([50, 100, 150]);
|
||||||
|
expect(testRangeBuffer).toEqual([42, 200, 12321]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows series insertion", function () {
|
||||||
|
testSeries = [ [ 50, 42 ], [ 100, 200 ], [ 150, 12321 ] ];
|
||||||
|
line.addSeries(mockSeries);
|
||||||
|
// Should have managed insertion index choices to get to...
|
||||||
|
expect(testDomainBuffer).toEqual([50, 100, 150]);
|
||||||
|
expect(testRangeBuffer).toEqual([42, 200, 12321]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("splits series insertion when necessary", function () {
|
||||||
|
testSeries = [ [ 50, 42 ], [ 100, 200 ], [ 150, 12321 ] ];
|
||||||
|
line.addPoint(75, 1);
|
||||||
|
line.addSeries(mockSeries);
|
||||||
|
// Should have managed insertion index choices to get to...
|
||||||
|
expect(testDomainBuffer).toEqual([50, 75, 100, 150]);
|
||||||
|
expect(testRangeBuffer).toEqual([42, 1, 200, 12321]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("attempts to remove points when insertion fails", function () {
|
||||||
|
// Verify precondition - normally doesn't try to trim
|
||||||
|
line.addPoint(1, 2);
|
||||||
|
expect(mockBuffer.trim).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// But if insertPoint fails, it should trim
|
||||||
|
mockBuffer.insertPoint.andReturn(false);
|
||||||
|
line.addPoint(2, 3);
|
||||||
|
expect(mockBuffer.trim).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
74
platform/features/plot/test/elements/PlotSeriesWindowSpec.js
Normal file
74
platform/features/plot/test/elements/PlotSeriesWindowSpec.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/elements/PlotSeriesWindow"],
|
||||||
|
function (PlotSeriesWindow) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A plot's window on a telemetry series", function () {
|
||||||
|
var mockSeries,
|
||||||
|
testSeries,
|
||||||
|
window;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testSeries = [
|
||||||
|
[ 0, 42 ],
|
||||||
|
[ 10, 1 ],
|
||||||
|
[ 20, 4 ],
|
||||||
|
[ 30, 9 ],
|
||||||
|
[ 40, 3 ]
|
||||||
|
];
|
||||||
|
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
['getPointCount', 'getDomainValue', 'getRangeValue']
|
||||||
|
);
|
||||||
|
|
||||||
|
mockSeries.getPointCount.andCallFake(function () {
|
||||||
|
return testSeries.length;
|
||||||
|
});
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
return testSeries[i][0];
|
||||||
|
});
|
||||||
|
mockSeries.getRangeValue.andCallFake(function (i) {
|
||||||
|
return testSeries[i][1];
|
||||||
|
});
|
||||||
|
|
||||||
|
window = new PlotSeriesWindow(
|
||||||
|
mockSeries,
|
||||||
|
"testDomain",
|
||||||
|
"testRange",
|
||||||
|
1,
|
||||||
|
testSeries.length
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a window upon a data series", function () {
|
||||||
|
expect(window.getPointCount()).toEqual(4);
|
||||||
|
expect(window.getDomainValue(0)).toEqual(10);
|
||||||
|
expect(window.getRangeValue(0)).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("looks up using specific domain/range keys", function () {
|
||||||
|
window.getDomainValue(0);
|
||||||
|
window.getRangeValue(0);
|
||||||
|
expect(mockSeries.getDomainValue)
|
||||||
|
.toHaveBeenCalledWith(1, 'testDomain');
|
||||||
|
expect(mockSeries.getRangeValue)
|
||||||
|
.toHaveBeenCalledWith(1, 'testRange');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can be split into smaller windows", function () {
|
||||||
|
var windows = window.split();
|
||||||
|
expect(windows.length).toEqual(2);
|
||||||
|
expect(windows[0].getPointCount()).toEqual(2);
|
||||||
|
expect(windows[1].getPointCount()).toEqual(2);
|
||||||
|
expect(windows[0].getDomainValue(0)).toEqual(10);
|
||||||
|
expect(windows[1].getDomainValue(0)).toEqual(30);
|
||||||
|
expect(windows[0].getRangeValue(0)).toEqual(1);
|
||||||
|
expect(windows[1].getRangeValue(0)).toEqual(9);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -14,6 +14,7 @@ define(
|
|||||||
testRange,
|
testRange,
|
||||||
testDomainValues,
|
testDomainValues,
|
||||||
testRangeValues,
|
testRangeValues,
|
||||||
|
mockSeries,
|
||||||
updater;
|
updater;
|
||||||
|
|
||||||
function makeMockDomainObject(id) {
|
function makeMockDomainObject(id) {
|
||||||
@ -33,6 +34,10 @@ define(
|
|||||||
"subscription",
|
"subscription",
|
||||||
[ "getDomainValue", "getRangeValue", "getTelemetryObjects" ]
|
[ "getDomainValue", "getRangeValue", "getTelemetryObjects" ]
|
||||||
);
|
);
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
['getPointCount', 'getDomainValue', 'getRangeValue']
|
||||||
|
);
|
||||||
testDomain = "testDomain";
|
testDomain = "testDomain";
|
||||||
testRange = "testRange";
|
testRange = "testRange";
|
||||||
testDomainValues = { a: 3, b: 7, c: 13 };
|
testDomainValues = { a: 3, b: 7, c: 13 };
|
||||||
@ -55,57 +60,14 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("provides one buffer per telemetry object", function () {
|
it("provides one buffer per telemetry object", function () {
|
||||||
expect(updater.getBuffers().length).toEqual(3);
|
expect(updater.getLineBuffers().length).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("changes buffer count if telemetry object counts change", function () {
|
it("changes buffer count if telemetry object counts change", function () {
|
||||||
mockSubscription.getTelemetryObjects
|
mockSubscription.getTelemetryObjects
|
||||||
.andReturn([makeMockDomainObject('a')]);
|
.andReturn([makeMockDomainObject('a')]);
|
||||||
updater.update();
|
updater.update();
|
||||||
expect(updater.getBuffers().length).toEqual(1);
|
expect(updater.getLineBuffers().length).toEqual(1);
|
||||||
});
|
|
||||||
|
|
||||||
it("maintains a buffer of received telemetry", function () {
|
|
||||||
// Count should be large enough to trigger a buffer resize
|
|
||||||
var count = 750,
|
|
||||||
i;
|
|
||||||
|
|
||||||
// Increment values exposed by subscription
|
|
||||||
function increment() {
|
|
||||||
Object.keys(testDomainValues).forEach(function (k) {
|
|
||||||
testDomainValues[k] += 1;
|
|
||||||
testRangeValues[k] += 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate a lot of telemetry updates
|
|
||||||
for (i = 0; i < count; i += 1) {
|
|
||||||
updater.update();
|
|
||||||
expect(updater.getLength(0)).toEqual(i + 1);
|
|
||||||
expect(updater.getLength(1)).toEqual(i + 1);
|
|
||||||
expect(updater.getLength(2)).toEqual(i + 1);
|
|
||||||
increment();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain offset should be lowest domain value
|
|
||||||
expect(updater.getDomainOffset()).toEqual(3);
|
|
||||||
|
|
||||||
// Test against initial values, offset by count,
|
|
||||||
// as was the case during each update
|
|
||||||
for (i = 0; i < count; i += 1) {
|
|
||||||
expect(updater.getBuffers()[0][i * 2])
|
|
||||||
.toEqual(3 + i - 3);
|
|
||||||
expect(updater.getBuffers()[0][i * 2 + 1])
|
|
||||||
.toEqual(123 + i);
|
|
||||||
expect(updater.getBuffers()[1][i * 2])
|
|
||||||
.toEqual(7 + i - 3);
|
|
||||||
expect(updater.getBuffers()[1][i * 2 + 1])
|
|
||||||
.toEqual(456 + i);
|
|
||||||
expect(updater.getBuffers()[2][i * 2])
|
|
||||||
.toEqual(13 + i - 3);
|
|
||||||
expect(updater.getBuffers()[2][i * 2 + 1])
|
|
||||||
.toEqual(789 + i);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can handle delayed telemetry object availability", function () {
|
it("can handle delayed telemetry object availability", function () {
|
||||||
@ -124,7 +86,7 @@ define(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Should have 0 buffers for 0 objects
|
// Should have 0 buffers for 0 objects
|
||||||
expect(updater.getBuffers().length).toEqual(0);
|
expect(updater.getLineBuffers().length).toEqual(0);
|
||||||
|
|
||||||
// Restore the three objects the test subscription would
|
// Restore the three objects the test subscription would
|
||||||
// normally have.
|
// normally have.
|
||||||
@ -132,30 +94,79 @@ define(
|
|||||||
updater.update();
|
updater.update();
|
||||||
|
|
||||||
// Should have 3 buffers for 3 objects
|
// Should have 3 buffers for 3 objects
|
||||||
expect(updater.getBuffers().length).toEqual(3);
|
expect(updater.getLineBuffers().length).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts historical telemetry updates", function () {
|
||||||
|
var mockObject = mockSubscription.getTelemetryObjects()[0];
|
||||||
|
|
||||||
it("shifts buffer upon expansion", function () {
|
mockSeries.getPointCount.andReturn(3);
|
||||||
// Count should be large enough to hit buffer's max size
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
var count = 1400,
|
return 1000 + i * 1000;
|
||||||
i;
|
});
|
||||||
|
mockSeries.getRangeValue.andReturn(10);
|
||||||
|
|
||||||
// Initial update; should have 3 in first position
|
// PlotLine & PlotLineBuffer are tested for most of the
|
||||||
// (a's initial domain value)
|
// details here, so just check for some expected side
|
||||||
|
// effect; in this case, should see more points in the buffer
|
||||||
|
expect(updater.getLineBuffers()[0].getLength()).toEqual(1);
|
||||||
|
updater.addHistorical(mockObject, mockSeries);
|
||||||
|
expect(updater.getLineBuffers()[0].getLength()).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears the domain offset if no objects are present", function () {
|
||||||
|
mockSubscription.getTelemetryObjects.andReturn([]);
|
||||||
updater.update();
|
updater.update();
|
||||||
expect(updater.getBuffers()[0][1]).toEqual(123);
|
expect(updater.getDomainOffset()).toBeUndefined();
|
||||||
|
|
||||||
// Simulate a lot of telemetry updates
|
|
||||||
for (i = 0; i < count; i += 1) {
|
|
||||||
testDomainValues.a += 1;
|
|
||||||
testRangeValues.a += 1;
|
|
||||||
updater.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value at front of the buffer should have been pushed out
|
|
||||||
expect(updater.getBuffers()[0][1]).not.toEqual(123);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles empty historical telemetry updates", function () {
|
||||||
|
// General robustness check for when a series is empty
|
||||||
|
var mockObject = mockSubscription.getTelemetryObjects()[0];
|
||||||
|
|
||||||
|
mockSeries.getPointCount.andReturn(0);
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
return 1000 + i * 1000;
|
||||||
|
});
|
||||||
|
mockSeries.getRangeValue.andReturn(10);
|
||||||
|
|
||||||
|
// PlotLine & PlotLineBuffer are tested for most of the
|
||||||
|
// details here, so just check for some expected side
|
||||||
|
// effect; in this case, should see more points in the buffer
|
||||||
|
expect(updater.getLineBuffers()[0].getLength()).toEqual(1);
|
||||||
|
updater.addHistorical(mockObject, mockSeries);
|
||||||
|
expect(updater.getLineBuffers()[0].getLength()).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can initialize domain offset from historical telemetry", function () {
|
||||||
|
var tmp = mockSubscription.getTelemetryObjects();
|
||||||
|
|
||||||
|
mockSubscription.getTelemetryObjects.andReturn([]);
|
||||||
|
|
||||||
|
// Reinstantiate with the empty subscription
|
||||||
|
updater = new PlotUpdater(
|
||||||
|
mockSubscription,
|
||||||
|
testDomain,
|
||||||
|
testRange
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore subscription, provide some historical data
|
||||||
|
mockSubscription.getTelemetryObjects.andReturn(tmp);
|
||||||
|
mockSeries.getPointCount.andReturn(3);
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
return 1000 + i * 1000;
|
||||||
|
});
|
||||||
|
mockSeries.getRangeValue.andReturn(10);
|
||||||
|
|
||||||
|
// PlotLine & PlotLineBuffer are tested for most of the
|
||||||
|
// details here, so just check for some expected side
|
||||||
|
// effect; in this case, should see more points in the buffer
|
||||||
|
expect(updater.getDomainOffset()).toBeUndefined();
|
||||||
|
updater.addHistorical(tmp[0], mockSeries);
|
||||||
|
expect(updater.getDomainOffset()).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -57,18 +57,30 @@ define(
|
|||||||
// Prepared telemetry data
|
// Prepared telemetry data
|
||||||
mockPrepared = jasmine.createSpyObj(
|
mockPrepared = jasmine.createSpyObj(
|
||||||
"prepared",
|
"prepared",
|
||||||
[ "getDomainOffset", "getOrigin", "getDimensions", "getBuffers", "getLength" ]
|
[
|
||||||
|
"getDomainOffset",
|
||||||
|
"getOrigin",
|
||||||
|
"getDimensions",
|
||||||
|
"getLineBuffers"
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
mockSubPlotFactory.createSubPlot.andCallFake(createMockSubPlot);
|
mockSubPlotFactory.createSubPlot.andCallFake(createMockSubPlot);
|
||||||
|
|
||||||
// Act as if we have three buffers full of data
|
// Act as if we have three buffers full of data
|
||||||
testBuffers = [["a"], ["b"], ["c"]];
|
testBuffers = ['a', 'b', 'c'].map(function (id) {
|
||||||
mockPrepared.getBuffers.andReturn(testBuffers);
|
var mockBuffer = jasmine.createSpyObj(
|
||||||
|
'buffer-' + id,
|
||||||
|
['getBuffer', 'getLength']
|
||||||
|
);
|
||||||
|
mockBuffer.getBuffer.andReturn([id]);
|
||||||
|
mockBuffer.getLength.andReturn(3);
|
||||||
|
return mockBuffer;
|
||||||
|
});
|
||||||
|
mockPrepared.getLineBuffers.andReturn(testBuffers);
|
||||||
mockPrepared.getDomainOffset.andReturn(1234);
|
mockPrepared.getDomainOffset.andReturn(1234);
|
||||||
mockPrepared.getOrigin.andReturn([10, 10]);
|
mockPrepared.getOrigin.andReturn([10, 10]);
|
||||||
mockPrepared.getDimensions.andReturn([500, 500]);
|
mockPrepared.getDimensions.andReturn([500, 500]);
|
||||||
mockPrepared.getLength.andReturn(3);
|
|
||||||
|
|
||||||
// Clear out drawing objects
|
// Clear out drawing objects
|
||||||
testDrawingObjects = [];
|
testDrawingObjects = [];
|
||||||
@ -104,7 +116,7 @@ define(
|
|||||||
// Make sure the right buffer was drawn to the
|
// Make sure the right buffer was drawn to the
|
||||||
// right subplot.
|
// right subplot.
|
||||||
testDrawingObject.lines.forEach(function (line, j) {
|
testDrawingObject.lines.forEach(function (line, j) {
|
||||||
expect(line.buffer).toEqual(testBuffers[j]);
|
expect(line.buffer).toEqual(testBuffers[j].getBuffer());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -57,18 +57,25 @@ define(
|
|||||||
// Prepared telemetry data
|
// Prepared telemetry data
|
||||||
mockPrepared = jasmine.createSpyObj(
|
mockPrepared = jasmine.createSpyObj(
|
||||||
"prepared",
|
"prepared",
|
||||||
[ "getDomainOffset", "getOrigin", "getDimensions", "getBuffers", "getLength" ]
|
[ "getDomainOffset", "getOrigin", "getDimensions", "getLineBuffers" ]
|
||||||
);
|
);
|
||||||
|
|
||||||
mockSubPlotFactory.createSubPlot.andCallFake(createMockSubPlot);
|
mockSubPlotFactory.createSubPlot.andCallFake(createMockSubPlot);
|
||||||
|
|
||||||
// Act as if we have three buffers full of data
|
// Act as if we have three buffers full of data
|
||||||
testBuffers = [["a"], ["b"], ["c"]];
|
testBuffers = ['a', 'b', 'c'].map(function (id) {
|
||||||
mockPrepared.getBuffers.andReturn(testBuffers);
|
var mockBuffer = jasmine.createSpyObj(
|
||||||
|
'buffer-' + id,
|
||||||
|
['getBuffer', 'getLength']
|
||||||
|
);
|
||||||
|
mockBuffer.getBuffer.andReturn([id]);
|
||||||
|
mockBuffer.getLength.andReturn(3);
|
||||||
|
return mockBuffer;
|
||||||
|
});
|
||||||
|
mockPrepared.getLineBuffers.andReturn(testBuffers);
|
||||||
mockPrepared.getDomainOffset.andReturn(1234);
|
mockPrepared.getDomainOffset.andReturn(1234);
|
||||||
mockPrepared.getOrigin.andReturn([10, 10]);
|
mockPrepared.getOrigin.andReturn([10, 10]);
|
||||||
mockPrepared.getDimensions.andReturn([500, 500]);
|
mockPrepared.getDimensions.andReturn([500, 500]);
|
||||||
mockPrepared.getLength.andReturn(3);
|
|
||||||
|
|
||||||
// Objects that will be drawn to in sub-plots
|
// Objects that will be drawn to in sub-plots
|
||||||
testDrawingObjects = [];
|
testDrawingObjects = [];
|
||||||
@ -104,7 +111,7 @@ define(
|
|||||||
// Make sure the right buffer was drawn to the
|
// Make sure the right buffer was drawn to the
|
||||||
// right subplot.
|
// right subplot.
|
||||||
expect(testDrawingObject.lines[0].buffer)
|
expect(testDrawingObject.lines[0].buffer)
|
||||||
.toEqual(testBuffers[i]);
|
.toEqual(testBuffers[i].getBuffer());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,11 +6,14 @@
|
|||||||
"SubPlot",
|
"SubPlot",
|
||||||
"SubPlotFactory",
|
"SubPlotFactory",
|
||||||
"elements/PlotAxis",
|
"elements/PlotAxis",
|
||||||
|
"elements/PlotLine",
|
||||||
|
"elements/PlotLineBuffer",
|
||||||
"elements/PlotPalette",
|
"elements/PlotPalette",
|
||||||
"elements/PlotPanZoomStack",
|
"elements/PlotPanZoomStack",
|
||||||
"elements/PlotPanZoomStackGroup",
|
"elements/PlotPanZoomStackGroup",
|
||||||
"elements/PlotPosition",
|
"elements/PlotPosition",
|
||||||
"elements/PlotPreparer",
|
"elements/PlotPreparer",
|
||||||
|
"elements/PlotSeriesWindow",
|
||||||
"elements/PlotTickGenerator",
|
"elements/PlotTickGenerator",
|
||||||
"elements/PlotUpdater",
|
"elements/PlotUpdater",
|
||||||
"modes/PlotModeOptions",
|
"modes/PlotModeOptions",
|
||||||
|
@ -43,6 +43,11 @@
|
|||||||
"key": "telemetrySubscriber",
|
"key": "telemetrySubscriber",
|
||||||
"implementation": "TelemetrySubscriber.js",
|
"implementation": "TelemetrySubscriber.js",
|
||||||
"depends": [ "$q", "$timeout" ]
|
"depends": [ "$q", "$timeout" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "telemetryHandler",
|
||||||
|
"implementation": "TelemetryHandler.js",
|
||||||
|
"depends": [ "$q", "telemetrySubscriber" ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
|
45
platform/telemetry/src/TelemetryDelegator.js
Normal file
45
platform/telemetry/src/TelemetryDelegator.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to handle telemetry delegation associated with a
|
||||||
|
* given domain object.
|
||||||
|
*/
|
||||||
|
function TelemetryDelegator($q) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Promise telemetry-providing objects associated with
|
||||||
|
* this domain object (either the domain object itself,
|
||||||
|
* or the objects it delegates)
|
||||||
|
* @returns {Promise.<DomainObject[]>} domain objects with
|
||||||
|
* a telemetry capability
|
||||||
|
*/
|
||||||
|
promiseTelemetryObjects: function (domainObject) {
|
||||||
|
// If object has been cleared, there are no relevant
|
||||||
|
// telemetry-providing domain objects.
|
||||||
|
if (!domainObject) {
|
||||||
|
return $q.when([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try delegation first, and attach the
|
||||||
|
// object itself if it has a telemetry capability.
|
||||||
|
return $q.when(domainObject.useCapability(
|
||||||
|
"delegation",
|
||||||
|
"telemetry"
|
||||||
|
)).then(function (result) {
|
||||||
|
var head = domainObject.hasCapability("telemetry") ?
|
||||||
|
[ domainObject ] : [],
|
||||||
|
tail = result || [];
|
||||||
|
return head.concat(tail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryDelegator;
|
||||||
|
}
|
||||||
|
);
|
91
platform/telemetry/src/TelemetryHandle.js
Normal file
91
platform/telemetry/src/TelemetryHandle.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A telemetry handle acts as a helper in issuing requests for
|
||||||
|
* new telemetry as well as subscribing to real-time updates
|
||||||
|
* for the same telemetry series. This is exposed through the
|
||||||
|
* `telemetryHandler` service.
|
||||||
|
* @param $q Angular's $q, for promises
|
||||||
|
* @param {TelemetrySubscription} subscription a subscription
|
||||||
|
* to supplied telemetry
|
||||||
|
*/
|
||||||
|
function TelemetryHandle($q, subscription) {
|
||||||
|
var seriesMap = {},
|
||||||
|
self = Object.create(subscription);
|
||||||
|
|
||||||
|
// Request a telemetry series for this specific object
|
||||||
|
function requestSeries(telemetryObject, request, callback) {
|
||||||
|
var id = telemetryObject.getId(),
|
||||||
|
telemetry = telemetryObject.getCapability('telemetry');
|
||||||
|
|
||||||
|
function receiveSeries(series) {
|
||||||
|
// Store it for subsequent lookup
|
||||||
|
seriesMap[id] = series;
|
||||||
|
// Notify callback of new series data, if there is one
|
||||||
|
if (callback) {
|
||||||
|
callback(telemetryObject, series);
|
||||||
|
}
|
||||||
|
// Pass it along for promise-chaining
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue the request via the object's telemetry capability
|
||||||
|
return telemetry.requestData(request).then(receiveSeries);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the most recently obtained telemetry data series associated
|
||||||
|
* with this domain object.
|
||||||
|
* @param {DomainObject} the domain object which has telemetry
|
||||||
|
* data associated with it
|
||||||
|
* @return {TelemetrySeries} the most recent telemetry series
|
||||||
|
* (or undefined if there is not one)
|
||||||
|
*/
|
||||||
|
self.getSeries = function (domainObject) {
|
||||||
|
var id = domainObject.getId();
|
||||||
|
return seriesMap[id];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the request duration.
|
||||||
|
* @param {object|number} request the duration of historical
|
||||||
|
* data to look at; or, the request to issue
|
||||||
|
* @param {Function} [callback] a callback that will be
|
||||||
|
* invoked as new data becomes available, with the
|
||||||
|
* domain object for which new data is available.
|
||||||
|
*/
|
||||||
|
self.request = function (request, callback) {
|
||||||
|
// Issue (and handle) the new request from this object
|
||||||
|
function issueRequest(telemetryObject) {
|
||||||
|
return requestSeries(telemetryObject, request, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the request to all telemetry objects
|
||||||
|
function issueRequests(telemetryObjects) {
|
||||||
|
return $q.all(telemetryObjects.map(issueRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request is a simple number, treat it as a duration
|
||||||
|
request = (typeof request === 'number') ?
|
||||||
|
{ duration: request } : request;
|
||||||
|
|
||||||
|
// Look up telemetry-providing objects from the subscription,
|
||||||
|
// then issue new requests.
|
||||||
|
return subscription.promiseTelemetryObjects()
|
||||||
|
.then(issueRequests);
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryHandle;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
33
platform/telemetry/src/TelemetryHandler.js
Normal file
33
platform/telemetry/src/TelemetryHandler.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./TelemetryHandle'],
|
||||||
|
function (TelemetryHandle) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TelemetryRequester provides an easy interface to request
|
||||||
|
* telemetry associated with a set of domain objects.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param $q Angular's $q
|
||||||
|
*/
|
||||||
|
function TelemetryHandler($q, telemetrySubscriber) {
|
||||||
|
return {
|
||||||
|
handle: function (domainObject, callback, lossless) {
|
||||||
|
var subscription = telemetrySubscriber.subscribe(
|
||||||
|
domainObject,
|
||||||
|
callback,
|
||||||
|
lossless
|
||||||
|
);
|
||||||
|
|
||||||
|
return new TelemetryHandle($q, subscription);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryHandler;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
@ -1,8 +1,8 @@
|
|||||||
/*global define*/
|
/*global define*/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
['./TelemetryQueue', './TelemetryTable'],
|
['./TelemetryQueue', './TelemetryTable', './TelemetryDelegator'],
|
||||||
function (TelemetryQueue, TelemetryTable) {
|
function (TelemetryQueue, TelemetryTable, TelemetryDelegator) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
@ -31,7 +31,9 @@ define(
|
|||||||
* the callback once, with access to the latest data
|
* the callback once, with access to the latest data
|
||||||
*/
|
*/
|
||||||
function TelemetrySubscription($q, $timeout, domainObject, callback, lossless) {
|
function TelemetrySubscription($q, $timeout, domainObject, callback, lossless) {
|
||||||
var unsubscribePromise,
|
var delegator = new TelemetryDelegator($q),
|
||||||
|
unsubscribePromise,
|
||||||
|
telemetryObjectPromise,
|
||||||
latestValues = {},
|
latestValues = {},
|
||||||
telemetryObjects = [],
|
telemetryObjects = [],
|
||||||
pool = lossless ? new TelemetryQueue() : new TelemetryTable(),
|
pool = lossless ? new TelemetryQueue() : new TelemetryTable(),
|
||||||
@ -42,23 +44,7 @@ define(
|
|||||||
// This will either be the object in view, or object that
|
// This will either be the object in view, or object that
|
||||||
// this object delegates its telemetry capability to.
|
// this object delegates its telemetry capability to.
|
||||||
function promiseRelevantObjects(domainObject) {
|
function promiseRelevantObjects(domainObject) {
|
||||||
// If object has been cleared, there are no relevant
|
return delegator.promiseTelemetryObjects(domainObject);
|
||||||
// telemetry-providing domain objects.
|
|
||||||
if (!domainObject) {
|
|
||||||
return $q.when([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, try delegation first, and attach the
|
|
||||||
// object itself if it has a telemetry capability.
|
|
||||||
return $q.when(domainObject.useCapability(
|
|
||||||
"delegation",
|
|
||||||
"telemetry"
|
|
||||||
)).then(function (result) {
|
|
||||||
var head = domainObject.hasCapability("telemetry") ?
|
|
||||||
[ domainObject ] : [],
|
|
||||||
tail = result || [];
|
|
||||||
return head.concat(tail);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateValuesFromPool() {
|
function updateValuesFromPool() {
|
||||||
@ -152,8 +138,8 @@ define(
|
|||||||
// will be unsubscribe functions. (This must be a promise
|
// will be unsubscribe functions. (This must be a promise
|
||||||
// because delegation is supported, and retrieving delegate
|
// because delegation is supported, and retrieving delegate
|
||||||
// telemetry-capable objects may be an asynchronous operation.)
|
// telemetry-capable objects may be an asynchronous operation.)
|
||||||
unsubscribePromise =
|
telemetryObjectPromise = promiseRelevantObjects(domainObject);
|
||||||
promiseRelevantObjects(domainObject)
|
unsubscribePromise = telemetryObjectPromise
|
||||||
.then(cacheObjectReferences)
|
.then(cacheObjectReferences)
|
||||||
.then(subscribeAll);
|
.then(subscribeAll);
|
||||||
|
|
||||||
@ -239,6 +225,17 @@ define(
|
|||||||
*/
|
*/
|
||||||
getMetadata: function () {
|
getMetadata: function () {
|
||||||
return metadatas;
|
return metadatas;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get a promise for all telemetry-providing objects
|
||||||
|
* associated with this subscription.
|
||||||
|
* @returns {Promise.<DomainObject[]>} a promise for
|
||||||
|
* telemetry-providing objects
|
||||||
|
*/
|
||||||
|
promiseTelemetryObjects: function () {
|
||||||
|
// Unsubscribe promise is available after objects
|
||||||
|
// are loaded.
|
||||||
|
return telemetryObjectPromise;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
12
platform/telemetry/test/TelemetryDelegatorSpec.js
Normal file
12
platform/telemetry/test/TelemetryDelegatorSpec.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/TelemetryDelegator"],
|
||||||
|
function (TelemetryDelegator) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The telemetry delegator", function () {
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
88
platform/telemetry/test/TelemetryHandleSpec.js
Normal file
88
platform/telemetry/test/TelemetryHandleSpec.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/TelemetryHandle"],
|
||||||
|
function (TelemetryHandle) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("A telemetry handle", function () {
|
||||||
|
var mockQ,
|
||||||
|
mockSubscription,
|
||||||
|
mockDomainObject,
|
||||||
|
mockTelemetry,
|
||||||
|
mockSeries,
|
||||||
|
mockCallback,
|
||||||
|
handle;
|
||||||
|
|
||||||
|
function asPromise(v) {
|
||||||
|
return (v || {}).then ? v : {
|
||||||
|
then: function (callback) {
|
||||||
|
return asPromise(callback(v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockQ = jasmine.createSpyObj('$q', ['when', 'all']);
|
||||||
|
mockSubscription = jasmine.createSpyObj(
|
||||||
|
'subscription',
|
||||||
|
['unsubscribe', 'getTelemetryObjects', 'promiseTelemetryObjects']
|
||||||
|
);
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['getId', 'getCapability']
|
||||||
|
);
|
||||||
|
mockTelemetry = jasmine.createSpyObj(
|
||||||
|
'telemetry',
|
||||||
|
['requestData']
|
||||||
|
);
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
['getPointCount', 'getDomainValue', 'getRangeValue']
|
||||||
|
);
|
||||||
|
mockCallback = jasmine.createSpy('callback');
|
||||||
|
|
||||||
|
// Simulate $q.all, at least for asPromise-provided promises
|
||||||
|
mockQ.all.andCallFake(function (values) {
|
||||||
|
return values.map(function (v) {
|
||||||
|
var r;
|
||||||
|
asPromise(v).then(function (value) { r = value; });
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
mockQ.when.andCallFake(asPromise);
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockSubscription.promiseTelemetryObjects
|
||||||
|
.andReturn(asPromise([mockDomainObject]));
|
||||||
|
mockDomainObject.getId.andReturn('testId');
|
||||||
|
mockDomainObject.getCapability.andReturn(mockTelemetry);
|
||||||
|
mockTelemetry.requestData.andReturn(asPromise(mockSeries));
|
||||||
|
|
||||||
|
handle = new TelemetryHandle(mockQ, mockSubscription);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes subscription API", function () {
|
||||||
|
// Should still expose methods from the provided subscription
|
||||||
|
expect(handle.unsubscribe)
|
||||||
|
.toBe(mockSubscription.unsubscribe);
|
||||||
|
expect(handle.getTelemetryObjects)
|
||||||
|
.toBe(mockSubscription.getTelemetryObjects);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides an interface for historical requests", function () {
|
||||||
|
handle.request({}, mockCallback);
|
||||||
|
expect(mockCallback).toHaveBeenCalledWith(
|
||||||
|
mockDomainObject,
|
||||||
|
mockSeries
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides the latest series for domain objects", function () {
|
||||||
|
handle.request({});
|
||||||
|
expect(handle.getSeries(mockDomainObject))
|
||||||
|
.toEqual(mockSeries);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
64
platform/telemetry/test/TelemetryHandlerSpec.js
Normal file
64
platform/telemetry/test/TelemetryHandlerSpec.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/TelemetryHandler"],
|
||||||
|
function (TelemetryHandler) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The telemetry handler", function () {
|
||||||
|
// TelemetryHandler just provides a factory
|
||||||
|
// for TelemetryHandle, so most real testing
|
||||||
|
// should happen there.
|
||||||
|
var mockQ,
|
||||||
|
mockSubscriber,
|
||||||
|
mockDomainObject,
|
||||||
|
mockCallback,
|
||||||
|
mockSubscription,
|
||||||
|
handler;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockQ = jasmine.createSpyObj("$q", ["when"]);
|
||||||
|
mockSubscriber = jasmine.createSpyObj(
|
||||||
|
'telemetrySubscriber',
|
||||||
|
['subscribe']
|
||||||
|
);
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['getId', 'getCapability']
|
||||||
|
);
|
||||||
|
mockCallback = jasmine.createSpy('callback');
|
||||||
|
mockSubscription = jasmine.createSpyObj(
|
||||||
|
'subscription',
|
||||||
|
[
|
||||||
|
'unsubscribe',
|
||||||
|
'getTelemetryObjects',
|
||||||
|
'getRangeValue',
|
||||||
|
'getDomainValue'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||||
|
|
||||||
|
handler = new TelemetryHandler(mockQ, mockSubscriber);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("acts as a factory for subscription objects", function () {
|
||||||
|
var handle = handler.handle(
|
||||||
|
mockDomainObject,
|
||||||
|
mockCallback
|
||||||
|
);
|
||||||
|
// Just verify that this looks like a TelemetrySubscription
|
||||||
|
[
|
||||||
|
"unsubscribe",
|
||||||
|
"getTelemetryObjects",
|
||||||
|
"getRangeValue",
|
||||||
|
"getDomainValue",
|
||||||
|
"request"
|
||||||
|
].forEach(function (method) {
|
||||||
|
expect(handle[method]).toEqual(jasmine.any(Function));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -184,6 +184,14 @@ define(
|
|||||||
it("fires callback when telemetry objects are available", function () {
|
it("fires callback when telemetry objects are available", function () {
|
||||||
expect(mockCallback.calls.length).toEqual(1);
|
expect(mockCallback.calls.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("exposes a promise for telemetry objects", function () {
|
||||||
|
var mockCallback2 = jasmine.createSpy('callback');
|
||||||
|
subscription.promiseTelemetryObjects().then(mockCallback2);
|
||||||
|
|
||||||
|
expect(mockCallback2)
|
||||||
|
.toHaveBeenCalledWith([ mockDomainObject ]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -2,7 +2,10 @@
|
|||||||
"TelemetryAggregator",
|
"TelemetryAggregator",
|
||||||
"TelemetryCapability",
|
"TelemetryCapability",
|
||||||
"TelemetryController",
|
"TelemetryController",
|
||||||
|
"TelemetryDelegator",
|
||||||
"TelemetryFormatter",
|
"TelemetryFormatter",
|
||||||
|
"TelemetryHandle",
|
||||||
|
"TelemetryHandler",
|
||||||
"TelemetryQueue",
|
"TelemetryQueue",
|
||||||
"TelemetrySubscriber",
|
"TelemetrySubscriber",
|
||||||
"TelemetrySubscription",
|
"TelemetrySubscription",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user