mirror of
https://github.com/nasa/openmct.git
synced 2025-06-15 05:38:12 +00:00
[Plot] Wire in updater
Wire in updater for use in plots (to cache values at the plot level, allowing removal of a global cache to reduce memory consumption for WTD-751.)
This commit is contained in:
@ -5,13 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
"./elements/PlotPreparer",
|
"./elements/PlotUpdater",
|
||||||
"./elements/PlotPalette",
|
"./elements/PlotPalette",
|
||||||
"./elements/PlotAxis",
|
"./elements/PlotAxis",
|
||||||
"./modes/PlotModeOptions",
|
"./modes/PlotModeOptions",
|
||||||
"./SubPlotFactory"
|
"./SubPlotFactory"
|
||||||
],
|
],
|
||||||
function (PlotPreparer, PlotPalette, PlotAxis, PlotModeOptions, SubPlotFactory) {
|
function (PlotUpdater, PlotPalette, PlotAxis, PlotModeOptions, SubPlotFactory) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var AXIS_DEFAULTS = [
|
var AXIS_DEFAULTS = [
|
||||||
@ -34,6 +34,7 @@ define(
|
|||||||
var subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
var subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||||
modeOptions = new PlotModeOptions([], subPlotFactory),
|
modeOptions = new PlotModeOptions([], subPlotFactory),
|
||||||
subplots = [],
|
subplots = [],
|
||||||
|
updater,
|
||||||
subscription,
|
subscription,
|
||||||
domainOffset;
|
domainOffset;
|
||||||
|
|
||||||
@ -46,38 +47,6 @@ define(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond to newly-available telemetry data; update the
|
|
||||||
// drawing area accordingly.
|
|
||||||
function plotTelemetry() {
|
|
||||||
var prepared, datas, telemetry;
|
|
||||||
|
|
||||||
// Get a reference to the TelemetryController
|
|
||||||
telemetry = $scope.telemetry;
|
|
||||||
|
|
||||||
// Nothing to plot without TelemetryController
|
|
||||||
if (!telemetry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure axes have been initialized (we will want to
|
|
||||||
// get the active axis below)
|
|
||||||
if (!$scope.axes) {
|
|
||||||
setupAxes(telemetry.getMetadata());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get data sets
|
|
||||||
datas = telemetry.getResponse();
|
|
||||||
|
|
||||||
// Prepare data sets for rendering
|
|
||||||
prepared = new PlotPreparer(
|
|
||||||
datas,
|
|
||||||
($scope.axes[0].active || {}).key,
|
|
||||||
($scope.axes[1].active || {}).key
|
|
||||||
);
|
|
||||||
|
|
||||||
modeOptions.getModeHandler().plotTelemetry(prepared);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger an update of a specific subplot;
|
// Trigger an update of a specific subplot;
|
||||||
// used in a loop to update all subplots.
|
// used in a loop to update all subplots.
|
||||||
function updateSubplot(subplot) {
|
function updateSubplot(subplot) {
|
||||||
@ -100,19 +69,35 @@ define(
|
|||||||
.forEach(updateSubplot);
|
.forEach(updateSubplot);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateValues() {
|
function recreateUpdater() {
|
||||||
|
updater = new PlotUpdater(
|
||||||
|
subscription,
|
||||||
|
($scope.axes[0].active || {}).key,
|
||||||
|
($scope.axes[1].active || {}).key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValues() {
|
||||||
|
modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
}
|
||||||
subscription = domainObject && telemetrySubscriber.subscribe(
|
subscription = domainObject && telemetrySubscriber.subscribe(
|
||||||
domainObject,
|
domainObject,
|
||||||
updateValues
|
updateValues
|
||||||
);
|
);
|
||||||
|
setupAxes(subscription.getMetadata());
|
||||||
|
recreateUpdater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.$watch('domainObject', subscribe);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Get the color (as a style-friendly string) to use
|
* Get the color (as a style-friendly string) to use
|
||||||
@ -166,7 +151,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
setMode: function (mode) {
|
setMode: function (mode) {
|
||||||
modeOptions.setMode(mode);
|
modeOptions.setMode(mode);
|
||||||
plotTelemetry();
|
updateValues();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Get all individual plots contained within this Plot view.
|
* Get all individual plots contained within this Plot view.
|
||||||
|
178
platform/features/plot/src/elements/PlotUpdater.js
Normal file
178
platform/features/plot/src/elements/PlotUpdater.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*global define,Float32Array*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares data to be rendered in a GL Plot. Handles
|
||||||
|
* the conversion from data API to displayable buffers.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MAX_POINTS = 86400,
|
||||||
|
INITIAL_SIZE = 675; // 1/128 of MAX_POINTS
|
||||||
|
|
||||||
|
function identity(x) { return x; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PlotPreparer is responsible for handling data sets and
|
||||||
|
* preparing them to be rendered. It creates a WebGL-plottable
|
||||||
|
* Float32Array for each trace, and tracks the boundaries of the
|
||||||
|
* data sets (since this is convenient to do during the same pass).
|
||||||
|
* @constructor
|
||||||
|
* @param {Telemetry[]} datas telemetry data objects
|
||||||
|
* @param {string} domain the key to use when looking up domain values
|
||||||
|
* @param {string} range the key to use when looking up range values
|
||||||
|
*/
|
||||||
|
function PlotUpdater(subscription, domain, range) {
|
||||||
|
var max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||||
|
min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
domainOffset,
|
||||||
|
buffers = {},
|
||||||
|
lengths = {},
|
||||||
|
bufferArray;
|
||||||
|
|
||||||
|
function ensureBufferSize(buffer, id, index) {
|
||||||
|
// Check if we don't have enough room
|
||||||
|
if (index > buffer.length / 2) {
|
||||||
|
// If we don't, can we expand?
|
||||||
|
if (index < MAX_POINTS) {
|
||||||
|
// Double the buffer size
|
||||||
|
buffer = buffers[id] =
|
||||||
|
new Float32Array(buffer, 0, buffer.length * 2);
|
||||||
|
} else {
|
||||||
|
// Just shift the existing buffer
|
||||||
|
buffer.copyWithin(0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addData(obj) {
|
||||||
|
var id = obj.getId(),
|
||||||
|
index = lengths[id],
|
||||||
|
buffer = buffers[id],
|
||||||
|
domainValue = subscription.getDomainValue(obj),
|
||||||
|
rangeValue = subscription.getRangeValue(obj);
|
||||||
|
|
||||||
|
if (domainValue !== undefined && rangeValue !== undefined &&
|
||||||
|
(index < 1 || domainValue !== buffer[index * 2 - 2])) {
|
||||||
|
// Use the first observed domain value as a domainOffset
|
||||||
|
domainOffset = domainOffset !== undefined ?
|
||||||
|
domainOffset : domainValue;
|
||||||
|
// Ensure there is space for the new buffer
|
||||||
|
buffer = ensureBufferSize(buffer, id, index);
|
||||||
|
// Account for shifting that may have occurred
|
||||||
|
index = Math.min(index, MAX_POINTS - 2);
|
||||||
|
// Update the buffer
|
||||||
|
buffer[index * 2] = domainValue - domainOffset;
|
||||||
|
buffer[index * 2 + 1] = rangeValue;
|
||||||
|
// Update length
|
||||||
|
lengths[id] = Math.min(index + 1, MAX_POINTS);
|
||||||
|
// Observe max/min range values
|
||||||
|
max[1] = Math.max(max[1], rangeValue);
|
||||||
|
min[1] = Math.min(min[1], rangeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDomainExtrema(objects) {
|
||||||
|
max[0] = Number.NEGATIVE_INFINITY;
|
||||||
|
min[0] = Number.POSITIVE_INFINITY;
|
||||||
|
objects.forEach(function (obj) {
|
||||||
|
var id = obj.getId(),
|
||||||
|
buffer = buffers[id],
|
||||||
|
length = lengths[id],
|
||||||
|
value = buffer[length * 2 - 2] + domainOffset;
|
||||||
|
max[0] = Math.max(value, max[0]);
|
||||||
|
min[0] = Math.min(value, min[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function padRange() {
|
||||||
|
// If range is empty, add some padding
|
||||||
|
if (max[1] === min[1]) {
|
||||||
|
max[1] = max[1] + 1.0;
|
||||||
|
min[1] = min[1] - 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
var objects = subscription.getTelemetryObjects();
|
||||||
|
bufferArray = objects.map(addData);
|
||||||
|
updateDomainExtrema(objects);
|
||||||
|
padRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare(telemetryObject) {
|
||||||
|
var id = telemetryObject.getId();
|
||||||
|
lengths[id] = 0;
|
||||||
|
buffers[id] = new Float32Array(INITIAL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.getTelemetryObjects().forEach(prepare);
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the dimensions which bound all data in the provided
|
||||||
|
* data sets. This is given as a two-element array where the
|
||||||
|
* first element is domain, and second is range.
|
||||||
|
* @returns {number[]} the dimensions which bound this data set
|
||||||
|
*/
|
||||||
|
getDimensions: function () {
|
||||||
|
return [max[0] - min[0], max[1] - min[1]];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the origin of this data set's boundary.
|
||||||
|
* This is given as a two-element array where the
|
||||||
|
* first element is domain, and second is range.
|
||||||
|
* The domain value here is not adjusted by the domain offset.
|
||||||
|
* @returns {number[]} the origin of this data set's boundary
|
||||||
|
*/
|
||||||
|
getOrigin: function () {
|
||||||
|
return min;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the domain offset; this offset will have been subtracted
|
||||||
|
* from all domain values in all buffers returned by this
|
||||||
|
* preparer, in order to minimize loss-of-precision due to
|
||||||
|
* conversion to the 32-bit float format needed by WebGL.
|
||||||
|
* @returns {number} the domain offset
|
||||||
|
*/
|
||||||
|
getDomainOffset: function () {
|
||||||
|
return domainOffset;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get all renderable buffers for this data set. This will
|
||||||
|
* be returned as an array which can be correlated back to
|
||||||
|
* the provided telemetry data objects (from the constructor
|
||||||
|
* call) by index.
|
||||||
|
*
|
||||||
|
* Internally, these are flattened; each buffer contains a
|
||||||
|
* sequence of alternating domain and range values.
|
||||||
|
*
|
||||||
|
* All domain values in all buffers will have been adjusted
|
||||||
|
* from their original values by subtraction of the domain
|
||||||
|
* offset; this minimizes loss-of-precision resulting from
|
||||||
|
* the conversion to 32-bit floats, which may otherwise
|
||||||
|
* cause aliasing artifacts (particularly for timestamps)
|
||||||
|
*
|
||||||
|
* @returns {Float32Array[]} the buffers for these traces
|
||||||
|
*/
|
||||||
|
getBuffers: function () {
|
||||||
|
return buffers;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Update with latest data.
|
||||||
|
*/
|
||||||
|
update: update
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotUpdater;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
@ -30,6 +30,7 @@ define(
|
|||||||
var unsubscribePromise,
|
var unsubscribePromise,
|
||||||
latestValues = {},
|
latestValues = {},
|
||||||
telemetryObjects = [],
|
telemetryObjects = [],
|
||||||
|
metadatas,
|
||||||
updatePending;
|
updatePending;
|
||||||
|
|
||||||
// Look up domain objects which have telemetry capabilities.
|
// Look up domain objects which have telemetry capabilities.
|
||||||
@ -96,6 +97,14 @@ define(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up metadata associated with an object's telemetry
|
||||||
|
function lookupMetadata(domainObject) {
|
||||||
|
var telemetryCapability =
|
||||||
|
domainObject.getCapability("telemetry");
|
||||||
|
return telemetryCapability &&
|
||||||
|
telemetryCapability.getMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare subscriptions to all relevant telemetry-providing
|
// Prepare subscriptions to all relevant telemetry-providing
|
||||||
// domain objects.
|
// domain objects.
|
||||||
function subscribeAll(domainObjects) {
|
function subscribeAll(domainObjects) {
|
||||||
@ -108,6 +117,7 @@ define(
|
|||||||
// to return a non-Promise to simplify usage elsewhere.
|
// to return a non-Promise to simplify usage elsewhere.
|
||||||
function cacheObjectReferences(objects) {
|
function cacheObjectReferences(objects) {
|
||||||
telemetryObjects = objects;
|
telemetryObjects = objects;
|
||||||
|
metadatas = objects.map(lookupMetadata);
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +199,21 @@ define(
|
|||||||
*/
|
*/
|
||||||
getTelemetryObjects: function () {
|
getTelemetryObjects: function () {
|
||||||
return telemetryObjects;
|
return telemetryObjects;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get all telemetry metadata associated with
|
||||||
|
* telemetry-providing domain objects managed by
|
||||||
|
* this controller.
|
||||||
|
*
|
||||||
|
* This will ordered in the
|
||||||
|
* same manner as `getTelemetryObjects()` or
|
||||||
|
* `getResponse()`; that is, the metadata at a
|
||||||
|
* given index will correspond to the telemetry-providing
|
||||||
|
* domain object at the same index.
|
||||||
|
* @returns {Array} an array of metadata objects
|
||||||
|
*/
|
||||||
|
getMetadata: function () {
|
||||||
|
return metadatas;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user