2015-05-13 16:42:35 -07:00
|
|
|
/*****************************************************************************
|
|
|
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
|
|
* as represented by the Administrator of the National Aeronautics and Space
|
|
|
|
* Administration. All rights reserved.
|
|
|
|
*
|
|
|
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*
|
|
|
|
* Open MCT Web includes source code licensed under additional open source
|
|
|
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
|
|
* this source code distribution or the Licensing information page available
|
|
|
|
* at runtime from the About dialog for additional information.
|
|
|
|
*****************************************************************************/
|
2015-01-29 09:48:53 -08:00
|
|
|
/*global define,Float32Array*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares data to be rendered in a GL Plot. Handles
|
|
|
|
* the conversion from data API to displayable buffers.
|
|
|
|
*/
|
|
|
|
define(
|
2015-04-17 14:00:00 -07:00
|
|
|
['./PlotLine', './PlotLineBuffer'],
|
|
|
|
function (PlotLine, PlotLineBuffer) {
|
2015-01-29 09:48:53 -08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var MAX_POINTS = 86400,
|
2015-06-29 17:44:44 -07:00
|
|
|
PADDING_RATIO = 0.10, // Padding percentage for top & bottom
|
2015-01-29 09:48:53 -08:00
|
|
|
INITIAL_SIZE = 675; // 1/128 of MAX_POINTS
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2015-04-17 14:00:00 -07:00
|
|
|
* @param {TelemetryHandle} handle the handle to telemetry access
|
2015-01-29 09:48:53 -08:00
|
|
|
* @param {string} domain the key to use when looking up domain values
|
|
|
|
* @param {string} range the key to use when looking up range values
|
2015-06-20 10:28:49 -07:00
|
|
|
* @param {number} maxDuration maximum plot duration to display
|
|
|
|
* @param {number} maxPoints maximum number of points to display
|
2015-01-29 09:48:53 -08:00
|
|
|
*/
|
2015-06-20 10:28:49 -07:00
|
|
|
function PlotUpdater(handle, domain, range, fixedDuration, maxPoints) {
|
2015-04-17 14:00:00 -07:00
|
|
|
var ids = [],
|
|
|
|
lines = {},
|
|
|
|
dimensions = [0, 0],
|
|
|
|
origin = [0, 0],
|
|
|
|
domainExtrema,
|
|
|
|
rangeExtrema,
|
2015-06-24 12:54:49 -07:00
|
|
|
buffers = {},
|
2015-04-17 14:00:00 -07:00
|
|
|
bufferArray = [],
|
|
|
|
domainOffset;
|
2015-01-30 12:26:42 -08:00
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Look up a domain object's id (for mapping, below)
|
|
|
|
function getId(domainObject) {
|
|
|
|
return domainObject.getId();
|
|
|
|
}
|
2015-01-29 09:48:53 -08:00
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Check if this set of ids matches the current set of ids
|
|
|
|
// (used to detect if line preparation can be skipped)
|
|
|
|
function idsMatch(nextIds) {
|
2015-06-24 12:54:49 -07:00
|
|
|
return ids.length === nextIds.length &&
|
|
|
|
nextIds.every(function (id, index) {
|
|
|
|
return ids[index] === id;
|
|
|
|
});
|
2015-01-29 09:48:53 -08:00
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Prepare plot lines for this group of telemetry objects
|
|
|
|
function prepareLines(telemetryObjects) {
|
|
|
|
var nextIds = telemetryObjects.map(getId),
|
|
|
|
next = {};
|
|
|
|
|
|
|
|
// Detect if we already have everything we need prepared
|
2015-06-24 12:54:49 -07:00
|
|
|
if (idsMatch(nextIds)) {
|
2015-04-17 14:00:00 -07:00
|
|
|
// Nothing to prepare, move on
|
|
|
|
return;
|
2015-01-29 09:48:53 -08:00
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Built up a set of ids. Note that we can only
|
|
|
|
// create plot lines after our domain offset has
|
|
|
|
// been determined.
|
|
|
|
if (domainOffset !== undefined) {
|
2015-04-17 14:53:21 -07:00
|
|
|
// Update list of ids in use
|
|
|
|
ids = nextIds;
|
|
|
|
|
|
|
|
// Create buffers for these objects
|
2015-04-17 14:00:00 -07:00
|
|
|
bufferArray = ids.map(function (id) {
|
2015-06-24 12:54:49 -07:00
|
|
|
buffers[id] = buffers[id] || new PlotLineBuffer(
|
|
|
|
domainOffset,
|
|
|
|
INITIAL_SIZE,
|
|
|
|
maxPoints
|
|
|
|
);
|
|
|
|
next[id] = lines[id] || new PlotLine(buffers[id]);
|
|
|
|
return buffers[id];
|
2015-04-17 14:00:00 -07:00
|
|
|
});
|
2015-04-16 16:48:03 -07:00
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// If there are no more lines, clear the domain offset
|
2015-04-17 15:05:56 -07:00
|
|
|
if (Object.keys(next).length < 1) {
|
2015-04-17 14:00:00 -07:00
|
|
|
domainOffset = undefined;
|
2015-04-16 16:48:03 -07:00
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Update to the current set of lines
|
|
|
|
lines = next;
|
2015-04-16 16:48:03 -07:00
|
|
|
}
|
|
|
|
|
2015-06-20 10:28:49 -07:00
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Initialize the domain offset, based on these observed values
|
|
|
|
function initializeDomainOffset(values) {
|
|
|
|
domainOffset =
|
|
|
|
((domainOffset === undefined) && (values.length > 0)) ?
|
|
|
|
(values.reduce(function (a, b) {
|
|
|
|
return (a || 0) + (b || 0);
|
|
|
|
}, 0) / values.length) :
|
|
|
|
domainOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used in the reduce step of updateExtrema
|
|
|
|
function reduceExtrema(a, b) {
|
|
|
|
return [ Math.min(a[0], b[0]), Math.max(a[1], b[1]) ];
|
2015-01-29 09:48:53 -08:00
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// 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];
|
|
|
|
}
|
|
|
|
|
2015-06-29 17:44:44 -07:00
|
|
|
// Expand range slightly so points near edges are visible
|
|
|
|
function expandRange() {
|
|
|
|
var padding = PADDING_RATIO * dimensions[1],
|
|
|
|
top;
|
|
|
|
padding = Math.max(padding, 1.0);
|
|
|
|
top = Math.ceil(origin[1] + dimensions[1] + padding / 2);
|
|
|
|
origin[1] = Math.floor(origin[1] - padding / 2);
|
|
|
|
dimensions[1] = top - origin[1];
|
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Update dimensions and origin based on extrema of plots
|
2015-06-23 14:40:32 -07:00
|
|
|
function updateBounds() {
|
2015-04-17 14:53:21 -07:00
|
|
|
if (bufferArray.length > 0) {
|
|
|
|
domainExtrema = bufferArray.map(function (lineBuffer) {
|
|
|
|
return lineBuffer.getDomainExtrema();
|
|
|
|
}).reduce(reduceExtrema);
|
|
|
|
|
|
|
|
rangeExtrema = bufferArray.map(function (lineBuffer) {
|
|
|
|
return lineBuffer.getRangeExtrema();
|
|
|
|
}).reduce(reduceExtrema);
|
|
|
|
|
2015-06-20 10:39:35 -07:00
|
|
|
// Calculate best-fit dimensions
|
2015-06-29 17:44:44 -07:00
|
|
|
dimensions =
|
|
|
|
[dimensionsOf(domainExtrema), dimensionsOf(rangeExtrema)];
|
2015-04-17 14:53:21 -07:00
|
|
|
origin = [originOf(domainExtrema), originOf(rangeExtrema)];
|
2015-06-20 10:28:49 -07:00
|
|
|
|
2015-06-29 17:44:44 -07:00
|
|
|
// Enforce some minimum visible area
|
|
|
|
expandRange();
|
|
|
|
|
2015-06-20 10:39:35 -07:00
|
|
|
// ...then enforce a fixed duration if needed
|
2015-06-20 10:28:49 -07:00
|
|
|
if (fixedDuration !== undefined) {
|
2015-06-20 10:39:35 -07:00
|
|
|
origin[0] = origin[0] + dimensions[0] - fixedDuration;
|
|
|
|
dimensions[0] = fixedDuration;
|
2015-06-20 10:28:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enforce maximum duration on all plot lines; not that
|
|
|
|
// domain extrema must be up-to-date for this to behave correctly.
|
|
|
|
function enforceDuration() {
|
|
|
|
var cutoff;
|
|
|
|
|
|
|
|
function enforceDurationForBuffer(plotLineBuffer) {
|
|
|
|
var index = plotLineBuffer.findInsertionIndex(cutoff);
|
|
|
|
if (index > 0) {
|
2015-06-20 10:39:35 -07:00
|
|
|
// Leave one point untrimmed, such that line will
|
|
|
|
// continue off left edge of visible plot area.
|
|
|
|
plotLineBuffer.trim(index - 1);
|
2015-06-20 10:28:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fixedDuration !== undefined &&
|
|
|
|
domainExtrema !== undefined &&
|
|
|
|
(domainExtrema[1] - domainExtrema[0] > fixedDuration)) {
|
|
|
|
cutoff = domainExtrema[1] - fixedDuration;
|
|
|
|
bufferArray.forEach(enforceDurationForBuffer);
|
2015-06-23 14:40:32 -07:00
|
|
|
updateBounds(); // Extrema may have changed now
|
2015-04-17 14:53:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
);
|
|
|
|
}
|
2015-04-16 16:41:09 -07:00
|
|
|
}
|
2015-06-20 10:28:49 -07:00
|
|
|
|
2015-01-29 12:10:00 -08:00
|
|
|
// Handle new telemetry data
|
2015-01-29 09:48:53 -08:00
|
|
|
function update() {
|
2015-04-17 14:00:00 -07:00
|
|
|
var objects = handle.getTelemetryObjects();
|
|
|
|
|
|
|
|
// Initialize domain offset if necessary
|
|
|
|
if (domainOffset === undefined) {
|
|
|
|
initializeDomainOffset(objects.map(function (obj) {
|
|
|
|
return handle.getDomainValue(obj, domain);
|
2015-04-17 14:53:21 -07:00
|
|
|
}).filter(function (value) {
|
|
|
|
return typeof value === 'number';
|
2015-04-17 14:00:00 -07:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure lines are available
|
|
|
|
prepareLines(objects);
|
|
|
|
|
|
|
|
// Add new data
|
2015-04-17 14:53:21 -07:00
|
|
|
objects.forEach(addPointFor);
|
2015-04-17 14:00:00 -07:00
|
|
|
|
2015-06-20 10:28:49 -07:00
|
|
|
// Then, update extrema
|
|
|
|
updateBounds();
|
2015-01-29 09:48:53 -08:00
|
|
|
}
|
|
|
|
|
2015-04-17 14:00:00 -07:00
|
|
|
// Add historical data for this domain object
|
|
|
|
function setHistorical(domainObject, series) {
|
2015-04-17 14:53:21 -07:00
|
|
|
var count = series ? series.getPointCount() : 0,
|
2015-04-17 14:00:00 -07:00
|
|
|
line;
|
|
|
|
|
|
|
|
// Nothing to do if it's an empty series
|
|
|
|
if (count < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize domain offset if necessary
|
2015-04-17 14:53:21 -07:00
|
|
|
if (domainOffset === undefined) {
|
2015-04-17 14:00:00 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-06-20 10:28:49 -07:00
|
|
|
// Update extrema
|
|
|
|
updateBounds();
|
2015-01-29 09:48:53 -08:00
|
|
|
}
|
|
|
|
|
2015-02-03 10:35:03 -08:00
|
|
|
// Use a default MAX_POINTS if none is provided
|
2015-06-20 10:28:49 -07:00
|
|
|
maxPoints = maxPoints !== undefined ? maxPoints : MAX_POINTS;
|
2015-02-03 10:35:03 -08:00
|
|
|
|
2015-01-29 12:10:00 -08:00
|
|
|
// Initially prepare state for these objects.
|
|
|
|
// Note that this may be an empty array at this time,
|
|
|
|
// so we also need to check during update cycles.
|
2015-04-17 16:42:15 -07:00
|
|
|
update();
|
2015-01-29 09:48:53 -08:00
|
|
|
|
|
|
|
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 () {
|
2015-04-17 14:00:00 -07:00
|
|
|
return dimensions;
|
2015-01-29 09:48:53 -08:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* 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 () {
|
2015-01-29 11:19:22 -08:00
|
|
|
// Pad range if necessary
|
2015-04-17 14:00:00 -07:00
|
|
|
return origin;
|
2015-01-29 09:48:53 -08:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2015-04-17 14:00:00 -07:00
|
|
|
getLineBuffers: function () {
|
2015-01-29 11:19:22 -08:00
|
|
|
return bufferArray;
|
|
|
|
},
|
2015-01-29 09:48:53 -08:00
|
|
|
/**
|
|
|
|
* Update with latest data.
|
|
|
|
*/
|
2015-04-16 16:41:09 -07:00
|
|
|
update: update,
|
|
|
|
/**
|
|
|
|
* Fill in historical data.
|
|
|
|
*/
|
2015-04-17 14:09:39 -07:00
|
|
|
addHistorical: setHistorical
|
2015-01-29 09:48:53 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return PlotUpdater;
|
|
|
|
|
|
|
|
}
|
2015-06-20 10:28:49 -07:00
|
|
|
);
|