mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 01:42:31 +00:00
[Code Style] Continue refactor of Plot bundle
Continue refactoring Plot bundle to use prototypes, WTD-1482
This commit is contained in:
parent
18bc7d3637
commit
aefad6fdd3
@ -83,7 +83,7 @@ define(
|
||||
* that some pan-zoom state is always available.)
|
||||
*/
|
||||
PlotPanZoomStack.prototype.popPanZoom = function popPanZoom() {
|
||||
if (stack.length > 1) {
|
||||
if (this.stack.length > 1) {
|
||||
this.stack.pop();
|
||||
}
|
||||
};
|
||||
|
@ -38,15 +38,13 @@ define(
|
||||
* group
|
||||
*/
|
||||
function PlotPanZoomStackGroup(count) {
|
||||
var stacks = [],
|
||||
decoratedStacks = [],
|
||||
i;
|
||||
var self = this;
|
||||
|
||||
// Push a pan-zoom state; the index argument identifies
|
||||
// which stack originated the request (all other stacks
|
||||
// will ignore the range part of the change.)
|
||||
function pushPanZoom(origin, dimensions, index) {
|
||||
stacks.forEach(function (stack, i) {
|
||||
self.stacks.forEach(function (stack, i) {
|
||||
if (i === index) {
|
||||
// Do a normal push for the specified stack
|
||||
stack.pushPanZoom(origin, dimensions);
|
||||
@ -61,26 +59,6 @@ define(
|
||||
});
|
||||
}
|
||||
|
||||
// Pop one pan-zoom state from all stacks
|
||||
function popPanZoom() {
|
||||
stacks.forEach(function (stack) {
|
||||
stack.popPanZoom();
|
||||
});
|
||||
}
|
||||
|
||||
// Set the base pan-zoom state for all stacks
|
||||
function setBasePanZoom(origin, dimensions) {
|
||||
stacks.forEach(function (stack) {
|
||||
stack.setBasePanZoom(origin, dimensions);
|
||||
});
|
||||
}
|
||||
|
||||
// Clear the pan-zoom state of all stacks
|
||||
function clearPanZoom() {
|
||||
stacks.forEach(function (stack) {
|
||||
stack.clearPanZoom();
|
||||
});
|
||||
}
|
||||
|
||||
// Decorate a pan-zoom stack; returns an object with
|
||||
// the same interface, but whose stack-mutation methods
|
||||
@ -92,88 +70,101 @@ define(
|
||||
result.pushPanZoom = function (origin, dimensions) {
|
||||
pushPanZoom(origin, dimensions, index);
|
||||
};
|
||||
result.setBasePanZoom = setBasePanZoom;
|
||||
result.popPanZoom = popPanZoom;
|
||||
result.clearPanZoom = clearPanZoom;
|
||||
result.setBasePanZoom = function () {
|
||||
self.setBasePanZoom.apply(self, arguments);
|
||||
};
|
||||
result.popPanZoom = function () {
|
||||
self.popPanZoom.apply(self, arguments);
|
||||
};
|
||||
result.clearPanZoom = function () {
|
||||
self.clearPanZoom.apply(self, arguments);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create the stacks in this group ...
|
||||
while (stacks.length < count) {
|
||||
stacks.push(new PlotPanZoomStack([], []));
|
||||
this.stacks = [];
|
||||
while (this.stacks.length < count) {
|
||||
this.stacks.push(new PlotPanZoomStack([], []));
|
||||
}
|
||||
// ... and their decorated-to-synchronize versions.
|
||||
decoratedStacks = stacks.map(decorateStack);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Pop a pan-zoom state from all stacks in the group.
|
||||
* If called when there is only one pan-zoom state on each
|
||||
* stack, this acts as a no-op (that is, the lowest
|
||||
* pan-zoom state on the stack cannot be popped, to ensure
|
||||
* that some pan-zoom state is always available.)
|
||||
* @memberof platform/features/plot.PlotPanZoomStackGroup#
|
||||
*/
|
||||
popPanZoom: popPanZoom,
|
||||
|
||||
/**
|
||||
* Set the base pan-zoom state for all stacks in this group.
|
||||
* This changes the state at the bottom of each stack.
|
||||
* This allows the "unzoomed" state of plots to be updated
|
||||
* (e.g. as new data comes in) without
|
||||
* interfering with the user's chosen pan/zoom states.
|
||||
* @param {number[]} origin the base origin
|
||||
* @param {number[]} dimensions the base dimensions
|
||||
* @memberof platform/features/plot.PlotPanZoomStackGroup#
|
||||
*/
|
||||
setBasePanZoom: setBasePanZoom,
|
||||
|
||||
/**
|
||||
* Clear all pan-zoom stacks in this group down to
|
||||
* their bottom element; in effect, pop all elements
|
||||
* but the last, e.g. to remove any temporary user
|
||||
* modifications to pan-zoom state.
|
||||
* @memberof platform/features/plot.PlotPanZoomStackGroup#
|
||||
*/
|
||||
clearPanZoom: clearPanZoom,
|
||||
/**
|
||||
* Get the current stack depth; that is, the number
|
||||
* of items on each stack in the group.
|
||||
* A depth of one means that no
|
||||
* panning or zooming relative to the base value has
|
||||
* been applied.
|
||||
* @returns {number} the depth of the stacks in this group
|
||||
* @memberof platform/features/plot.PlotPanZoomStackGroup#
|
||||
*/
|
||||
getDepth: function () {
|
||||
// All stacks are kept in sync, so look up depth
|
||||
// from the first one.
|
||||
return stacks.length > 0 ?
|
||||
stacks[0].getDepth() : 0;
|
||||
},
|
||||
/**
|
||||
* Get a specific pan-zoom stack in this group.
|
||||
* Stacks are specified by index; this index must be less
|
||||
* than the count provided at construction time, and must
|
||||
* not be less than zero.
|
||||
* The stack returned by this function will be synchronized
|
||||
* to other stacks in this group; that is, mutating that
|
||||
* stack directly will result in other stacks in this group
|
||||
* undergoing similar updates to ensure that domain bounds
|
||||
* remain the same.
|
||||
* @param {number} index the index of the stack to get
|
||||
* @returns {PlotPanZoomStack} the pan-zoom stack in the
|
||||
* group identified by that index
|
||||
* @memberof platform/features/plot.PlotPanZoomStackGroup#
|
||||
*/
|
||||
getPanZoomStack: function (index) {
|
||||
return decoratedStacks[index];
|
||||
}
|
||||
};
|
||||
|
||||
this.decoratedStacks = this.stacks.map(decorateStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a pan-zoom state from all stacks in the group.
|
||||
* If called when there is only one pan-zoom state on each
|
||||
* stack, this acts as a no-op (that is, the lowest
|
||||
* pan-zoom state on the stack cannot be popped, to ensure
|
||||
* that some pan-zoom state is always available.)
|
||||
*/
|
||||
PlotPanZoomStackGroup.prototype.popPanZoom = function () {
|
||||
this.stacks.forEach(function (stack) {
|
||||
stack.popPanZoom();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the base pan-zoom state for all stacks in this group.
|
||||
* This changes the state at the bottom of each stack.
|
||||
* This allows the "unzoomed" state of plots to be updated
|
||||
* (e.g. as new data comes in) without
|
||||
* interfering with the user's chosen pan/zoom states.
|
||||
* @param {number[]} origin the base origin
|
||||
* @param {number[]} dimensions the base dimensions
|
||||
*/
|
||||
PlotPanZoomStackGroup.prototype.setBasePanZoom = function (origin, dimensions) {
|
||||
this.stacks.forEach(function (stack) {
|
||||
stack.setBasePanZoom(origin, dimensions);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear all pan-zoom stacks in this group down to
|
||||
* their bottom element; in effect, pop all elements
|
||||
* but the last, e.g. to remove any temporary user
|
||||
* modifications to pan-zoom state.
|
||||
*/
|
||||
PlotPanZoomStackGroup.prototype.clearPanZoom = function () {
|
||||
this.stacks.forEach(function (stack) {
|
||||
stack.clearPanZoom();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current stack depth; that is, the number
|
||||
* of items on each stack in the group.
|
||||
* A depth of one means that no
|
||||
* panning or zooming relative to the base value has
|
||||
* been applied.
|
||||
* @returns {number} the depth of the stacks in this group
|
||||
*/
|
||||
PlotPanZoomStackGroup.prototype.getDepth = function () {
|
||||
// All stacks are kept in sync, so look up depth
|
||||
// from the first one.
|
||||
return this.stacks.length > 0 ?
|
||||
this.stacks[0].getDepth() : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a specific pan-zoom stack in this group.
|
||||
* Stacks are specified by index; this index must be less
|
||||
* than the count provided at construction time, and must
|
||||
* not be less than zero.
|
||||
* The stack returned by this function will be synchronized
|
||||
* to other stacks in this group; that is, mutating that
|
||||
* stack directly will result in other stacks in this group
|
||||
* undergoing similar updates to ensure that domain bounds
|
||||
* remain the same.
|
||||
* @param {number} index the index of the stack to get
|
||||
* @returns {PlotPanZoomStack} the pan-zoom stack in the
|
||||
* group identified by that index
|
||||
*/
|
||||
PlotPanZoomStackGroup.prototype.getPanZoomStack = function (index) {
|
||||
return this.decoratedStacks[index];
|
||||
};
|
||||
|
||||
return PlotPanZoomStackGroup;
|
||||
}
|
||||
);
|
||||
|
@ -48,8 +48,7 @@ define(
|
||||
function PlotPosition(x, y, width, height, panZoomStack) {
|
||||
var panZoom = panZoomStack.getPanZoom(),
|
||||
origin = panZoom.origin,
|
||||
dimensions = panZoom.dimensions,
|
||||
position;
|
||||
dimensions = panZoom.dimensions;
|
||||
|
||||
function convert(v, i) {
|
||||
return v * dimensions[i] + origin[i];
|
||||
@ -57,45 +56,42 @@ define(
|
||||
|
||||
if (!dimensions || !origin) {
|
||||
// We need both dimensions and origin to compute a position
|
||||
position = [];
|
||||
this.position = [];
|
||||
} else {
|
||||
// Convert from pixel to domain-range space.
|
||||
// Note that range is reversed from the y-axis in pixel space
|
||||
//(positive range points up, positive pixel-y points down)
|
||||
position = [ x / width, (height - y) / height ].map(convert);
|
||||
this.position =
|
||||
[ x / width, (height - y) / height ].map(convert);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get the domain value corresponding to this pixel position.
|
||||
* @returns {number} the domain value
|
||||
* @memberof platform/features/plot.PlotPosition#
|
||||
*/
|
||||
getDomain: function () {
|
||||
return position[0];
|
||||
},
|
||||
/**
|
||||
* Get the range value corresponding to this pixel position.
|
||||
* @returns {number} the range value
|
||||
* @memberof platform/features/plot.PlotPosition#
|
||||
*/
|
||||
getRange: function () {
|
||||
return position[1];
|
||||
},
|
||||
/**
|
||||
* Get the domain and values corresponding to this
|
||||
* pixel position.
|
||||
* @returns {number[]} an array containing the domain and
|
||||
* the range value, in that order
|
||||
* @memberof platform/features/plot.PlotPosition#
|
||||
*/
|
||||
getPosition: function () {
|
||||
return position;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain value corresponding to this pixel position.
|
||||
* @returns {number} the domain value
|
||||
*/
|
||||
PlotPosition.prototype.getDomain = function () {
|
||||
return this.position[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the range value corresponding to this pixel position.
|
||||
* @returns {number} the range value
|
||||
*/
|
||||
PlotPosition.prototype.getRange =function () {
|
||||
return this.position[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the domain and values corresponding to this
|
||||
* pixel position.
|
||||
* @returns {number[]} an array containing the domain and
|
||||
* the range value, in that order
|
||||
*/
|
||||
PlotPosition.prototype.getPosition = function () {
|
||||
return this.position;
|
||||
};
|
||||
|
||||
return PlotPosition;
|
||||
}
|
||||
);
|
||||
|
@ -49,8 +49,7 @@ define(
|
||||
min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
x,
|
||||
y,
|
||||
domainOffset = Number.POSITIVE_INFINITY,
|
||||
buffers;
|
||||
domainOffset = Number.POSITIVE_INFINITY;
|
||||
|
||||
// Remove any undefined data sets
|
||||
datas = (datas || []).filter(identity);
|
||||
@ -85,65 +84,69 @@ define(
|
||||
}
|
||||
|
||||
// Convert to Float32Array
|
||||
buffers = vertices.map(function (v) { return new Float32Array(v); });
|
||||
this.buffers = vertices.map(function (v) {
|
||||
return new Float32Array(v);
|
||||
});
|
||||
|
||||
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
|
||||
* @memberof platform/features/plot.PlotPreparer#
|
||||
*/
|
||||
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
|
||||
* @memberof platform/features/plot.PlotPreparer#
|
||||
*/
|
||||
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
|
||||
* @memberof platform/features/plot.PlotPreparer#
|
||||
*/
|
||||
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
|
||||
* @memberof platform/features/plot.PlotPreparer#
|
||||
*/
|
||||
getBuffers: function () {
|
||||
return buffers;
|
||||
}
|
||||
};
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.domainOffset = domainOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
PlotPreparer.prototype.getDimensions = function () {
|
||||
var max = this.max, min = this.min;
|
||||
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
|
||||
*/
|
||||
PlotPreparer.prototype.getOrigin = function () {
|
||||
return this.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
|
||||
*/
|
||||
PlotPreparer.prototype.getDomainOffset = function () {
|
||||
return this.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
|
||||
*/
|
||||
PlotPreparer.prototype.getBuffers = function () {
|
||||
return this.buffers;
|
||||
};
|
||||
|
||||
return PlotPreparer;
|
||||
|
||||
}
|
||||
|
@ -30,41 +30,53 @@ define(
|
||||
* insertion into a plot line.
|
||||
* @constructor
|
||||
* @memberof platform/features/plot
|
||||
* @implements {TelemetrySeries}
|
||||
*/
|
||||
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
|
||||
)
|
||||
] : [];
|
||||
}
|
||||
};
|
||||
this.series = series;
|
||||
this.domain = domain;
|
||||
this.range = range;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
PlotSeriesWindow.prototype.getPointCount = function () {
|
||||
return this.end - this.start;
|
||||
};
|
||||
|
||||
PlotSeriesWindow.prototype.getDomainValue = function (index) {
|
||||
return this.series.getDomainValue(index + this.start, this.domain);
|
||||
};
|
||||
|
||||
PlotSeriesWindow.prototype.getRangeValue = function (index) {
|
||||
return this.series.getRangeValue(index + this.start, this.range);
|
||||
};
|
||||
|
||||
/**
|
||||
* Split this series into two series of equal (or nearly-equal) size.
|
||||
* @returns {PlotSeriesWindow[]} two series
|
||||
*/
|
||||
PlotSeriesWindow.prototype.split = function () {
|
||||
var mid = Math.floor((this.end + this.start) / 2);
|
||||
return ((this.end - this.start) > 1) ?
|
||||
[
|
||||
new PlotSeriesWindow(
|
||||
this.series,
|
||||
this.domain,
|
||||
this.range,
|
||||
this.start,
|
||||
mid
|
||||
),
|
||||
new PlotSeriesWindow(
|
||||
this.series,
|
||||
this.domain,
|
||||
this.range,
|
||||
mid,
|
||||
this.end
|
||||
)
|
||||
] : [];
|
||||
};
|
||||
|
||||
return PlotSeriesWindow;
|
||||
}
|
||||
);
|
||||
|
@ -39,60 +39,56 @@ define(
|
||||
* domain and range values.
|
||||
*/
|
||||
function PlotTickGenerator(panZoomStack, formatter) {
|
||||
this.panZoomStack = panZoomStack;
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
// Generate ticks; interpolate from start up to
|
||||
// start + span in count steps, using the provided
|
||||
// formatter to represent each value.
|
||||
function generateTicks(start, span, count, format) {
|
||||
var step = span / (count - 1),
|
||||
result = [],
|
||||
i;
|
||||
// Generate ticks; interpolate from start up to
|
||||
// start + span in count steps, using the provided
|
||||
// formatter to represent each value.
|
||||
PlotTickGenerator.prototype.generateTicks = function (start, span, count, format) {
|
||||
var step = span / (count - 1),
|
||||
result = [],
|
||||
i;
|
||||
|
||||
for (i = 0; i < count; i += 1) {
|
||||
result.push({
|
||||
label: format(i * step + start)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
for (i = 0; i < count; i += 1) {
|
||||
result.push({
|
||||
label: format(i * step + start)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Generate tick marks for the domain axis.
|
||||
* @param {number} count the number of ticks
|
||||
* @returns {string[]} labels for those ticks
|
||||
* @memberof platform/features/plot.PlotTickGenerator#
|
||||
*/
|
||||
generateDomainTicks: function (count) {
|
||||
var panZoom = panZoomStack.getPanZoom();
|
||||
return generateTicks(
|
||||
panZoom.origin[0],
|
||||
panZoom.dimensions[0],
|
||||
count,
|
||||
formatter.formatDomainValue
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Generate tick marks for the domain axis.
|
||||
* @param {number} count the number of ticks
|
||||
* @returns {string[]} labels for those ticks
|
||||
*/
|
||||
PlotTickGenerator.prototype.generateDomainTicks = function (count) {
|
||||
var panZoom = this.panZoomStack.getPanZoom();
|
||||
return this.generateTicks(
|
||||
panZoom.origin[0],
|
||||
panZoom.dimensions[0],
|
||||
count,
|
||||
this.formatter.formatDomainValue
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate tick marks for the range axis.
|
||||
* @param {number} count the number of ticks
|
||||
* @returns {string[]} labels for those ticks
|
||||
* @memberof platform/features/plot.PlotTickGenerator#
|
||||
*/
|
||||
generateRangeTicks: function (count) {
|
||||
var panZoom = panZoomStack.getPanZoom();
|
||||
return generateTicks(
|
||||
panZoom.origin[1],
|
||||
panZoom.dimensions[1],
|
||||
count,
|
||||
formatter.formatRangeValue
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* Generate tick marks for the range axis.
|
||||
* @param {number} count the number of ticks
|
||||
* @returns {string[]} labels for those ticks
|
||||
*/
|
||||
PlotTickGenerator.prototype.generateRangeTicks = function (count) {
|
||||
var panZoom = this.panZoomStack.getPanZoom();
|
||||
return this.generateTicks(
|
||||
panZoom.origin[1],
|
||||
panZoom.dimensions[1],
|
||||
count,
|
||||
this.formatter.formatRangeValue
|
||||
);
|
||||
};
|
||||
|
||||
return PlotTickGenerator;
|
||||
}
|
||||
|
@ -21,10 +21,6 @@
|
||||
*****************************************************************************/
|
||||
/*global define,Float32Array*/
|
||||
|
||||
/**
|
||||
* Prepares data to be rendered in a GL Plot. Handles
|
||||
* the conversion from data API to displayable buffers.
|
||||
*/
|
||||
define(
|
||||
['./PlotLine', './PlotLineBuffer'],
|
||||
function (PlotLine, PlotLineBuffer) {
|
||||
@ -44,302 +40,282 @@ define(
|
||||
* @param {TelemetryHandle} handle the handle to telemetry access
|
||||
* @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 {number} maxDuration maximum plot duration to display
|
||||
* @param {number} fixedDuration maximum plot duration to display
|
||||
* @param {number} maxPoints maximum number of points to display
|
||||
*/
|
||||
function PlotUpdater(handle, domain, range, fixedDuration, maxPoints) {
|
||||
var ids = [],
|
||||
lines = {},
|
||||
dimensions = [0, 0],
|
||||
origin = [0, 0],
|
||||
domainExtrema,
|
||||
rangeExtrema,
|
||||
buffers = {},
|
||||
bufferArray = [],
|
||||
domainOffset;
|
||||
this.handle = handle;
|
||||
this.domain = domain;
|
||||
this.range = range;
|
||||
this.fixedDuration = fixedDuration;
|
||||
this.maxPoints = maxPoints;
|
||||
|
||||
// Look up a domain object's id (for mapping, below)
|
||||
function getId(domainObject) {
|
||||
return domainObject.getId();
|
||||
}
|
||||
|
||||
// Check if this set of ids matches the current set of ids
|
||||
// (used to detect if line preparation can be skipped)
|
||||
function idsMatch(nextIds) {
|
||||
return ids.length === nextIds.length &&
|
||||
nextIds.every(function (id, index) {
|
||||
return ids[index] === id;
|
||||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
if (idsMatch(nextIds)) {
|
||||
// Nothing to prepare, move on
|
||||
return;
|
||||
}
|
||||
|
||||
// Built up a set of ids. Note that we can only
|
||||
// create plot lines after our domain offset has
|
||||
// been determined.
|
||||
if (domainOffset !== undefined) {
|
||||
// Update list of ids in use
|
||||
ids = nextIds;
|
||||
|
||||
// Create buffers for these objects
|
||||
bufferArray = ids.map(function (id) {
|
||||
buffers[id] = buffers[id] || new PlotLineBuffer(
|
||||
domainOffset,
|
||||
INITIAL_SIZE,
|
||||
maxPoints
|
||||
);
|
||||
next[id] = lines[id] || new PlotLine(buffers[id]);
|
||||
return buffers[id];
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// 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]) ];
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
// Update dimensions and origin based on extrema of plots
|
||||
function updateBounds() {
|
||||
if (bufferArray.length > 0) {
|
||||
domainExtrema = bufferArray.map(function (lineBuffer) {
|
||||
return lineBuffer.getDomainExtrema();
|
||||
}).reduce(reduceExtrema);
|
||||
|
||||
rangeExtrema = bufferArray.map(function (lineBuffer) {
|
||||
return lineBuffer.getRangeExtrema();
|
||||
}).reduce(reduceExtrema);
|
||||
|
||||
// Calculate best-fit dimensions
|
||||
dimensions =
|
||||
[dimensionsOf(domainExtrema), dimensionsOf(rangeExtrema)];
|
||||
origin = [originOf(domainExtrema), originOf(rangeExtrema)];
|
||||
|
||||
// Enforce some minimum visible area
|
||||
expandRange();
|
||||
|
||||
// ...then enforce a fixed duration if needed
|
||||
if (fixedDuration !== undefined) {
|
||||
origin[0] = origin[0] + dimensions[0] - fixedDuration;
|
||||
dimensions[0] = fixedDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Leave one point untrimmed, such that line will
|
||||
// continue off left edge of visible plot area.
|
||||
plotLineBuffer.trim(index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (fixedDuration !== undefined &&
|
||||
domainExtrema !== undefined &&
|
||||
(domainExtrema[1] - domainExtrema[0] > fixedDuration)) {
|
||||
cutoff = domainExtrema[1] - fixedDuration;
|
||||
bufferArray.forEach(enforceDurationForBuffer);
|
||||
updateBounds(); // Extrema may have changed now
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
function update() {
|
||||
var objects = handle.getTelemetryObjects();
|
||||
|
||||
// Initialize domain offset if necessary
|
||||
if (domainOffset === undefined) {
|
||||
initializeDomainOffset(objects.map(function (obj) {
|
||||
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);
|
||||
|
||||
// Then, update extrema
|
||||
updateBounds();
|
||||
}
|
||||
|
||||
// Add historical data for this domain object
|
||||
function setHistorical(domainObject, series) {
|
||||
var count = series ? series.getPointCount() : 0,
|
||||
line;
|
||||
|
||||
// Nothing to do if it's an empty series
|
||||
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);
|
||||
}
|
||||
|
||||
// Update extrema
|
||||
updateBounds();
|
||||
}
|
||||
this.ids = [];
|
||||
this.lines = {};
|
||||
this.buffers = {};
|
||||
this.bufferArray = [];
|
||||
|
||||
// Use a default MAX_POINTS if none is provided
|
||||
maxPoints = maxPoints !== undefined ? maxPoints : MAX_POINTS;
|
||||
this.maxPoints = maxPoints !== undefined ? maxPoints : MAX_POINTS;
|
||||
this.dimensions = [0, 0];
|
||||
this.origin = [0, 0];
|
||||
|
||||
// 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.
|
||||
update();
|
||||
|
||||
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
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
getDimensions: function () {
|
||||
return dimensions;
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
getOrigin: function () {
|
||||
// Pad range if necessary
|
||||
return origin;
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
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
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
getLineBuffers: function () {
|
||||
return bufferArray;
|
||||
},
|
||||
/**
|
||||
* Update with latest data.
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
update: update,
|
||||
/**
|
||||
* Fill in historical data.
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
addHistorical: setHistorical
|
||||
};
|
||||
this.update();
|
||||
}
|
||||
|
||||
// Look up a domain object's id (for mapping, below)
|
||||
function getId(domainObject) {
|
||||
return domainObject.getId();
|
||||
}
|
||||
|
||||
// Used in the reduce step of updateExtrema
|
||||
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];
|
||||
}
|
||||
|
||||
// Check if this set of ids matches the current set of ids
|
||||
// (used to detect if line preparation can be skipped)
|
||||
PlotUpdater.prototype.idsMatch = function (nextIds) {
|
||||
var ids = this.ids;
|
||||
return ids.length === nextIds.length &&
|
||||
nextIds.every(function (id, index) {
|
||||
return ids[index] === id;
|
||||
});
|
||||
};
|
||||
|
||||
// Prepare plot lines for this group of telemetry objects
|
||||
PlotUpdater.prototype.prepareLines = function (telemetryObjects) {
|
||||
var nextIds = telemetryObjects.map(getId),
|
||||
next = {},
|
||||
self = this;
|
||||
|
||||
// Detect if we already have everything we need prepared
|
||||
if (this.idsMatch(nextIds)) {
|
||||
// Nothing to prepare, move on
|
||||
return;
|
||||
}
|
||||
|
||||
// Built up a set of ids. Note that we can only
|
||||
// create plot lines after our domain offset has
|
||||
// been determined.
|
||||
if (this.domainOffset !== undefined) {
|
||||
// Update list of ids in use
|
||||
this.ids = nextIds;
|
||||
|
||||
// Create buffers for these objects
|
||||
this.bufferArray = this.ids.map(function (id) {
|
||||
self.buffers[id] = self.buffers[id] || new PlotLineBuffer(
|
||||
self.domainOffset,
|
||||
INITIAL_SIZE,
|
||||
self.maxPoints
|
||||
);
|
||||
next[id] =
|
||||
self.lines[id] || new PlotLine(self.buffers[id]);
|
||||
return self.buffers[id];
|
||||
});
|
||||
}
|
||||
|
||||
// If there are no more lines, clear the domain offset
|
||||
if (Object.keys(next).length < 1) {
|
||||
this.domainOffset = undefined;
|
||||
}
|
||||
|
||||
// Update to the current set of lines
|
||||
this.lines = next;
|
||||
};
|
||||
|
||||
// Initialize the domain offset, based on these observed values
|
||||
PlotUpdater.prototype.initializeDomainOffset = function (values) {
|
||||
this.domainOffset =
|
||||
((this.domainOffset === undefined) && (values.length > 0)) ?
|
||||
(values.reduce(function (a, b) {
|
||||
return (a || 0) + (b || 0);
|
||||
}, 0) / values.length) :
|
||||
this.domainOffset;
|
||||
};
|
||||
|
||||
// Expand range slightly so points near edges are visible
|
||||
PlotUpdater.prototype.expandRange = function () {
|
||||
var padding = PADDING_RATIO * this.dimensions[1],
|
||||
top;
|
||||
padding = Math.max(padding, 1.0);
|
||||
top = Math.ceil(this.origin[1] + this.dimensions[1] + padding / 2);
|
||||
this.origin[1] = Math.floor(this.origin[1] - padding / 2);
|
||||
this.dimensions[1] = top - this.origin[1];
|
||||
};
|
||||
|
||||
// Update dimensions and origin based on extrema of plots
|
||||
PlotUpdater.prototype.updateBounds = function () {
|
||||
var bufferArray = this.bufferArray;
|
||||
if (bufferArray.length > 0) {
|
||||
this.domainExtrema = bufferArray.map(function (lineBuffer) {
|
||||
return lineBuffer.getDomainExtrema();
|
||||
}).reduce(reduceExtrema);
|
||||
|
||||
this.rangeExtrema = bufferArray.map(function (lineBuffer) {
|
||||
return lineBuffer.getRangeExtrema();
|
||||
}).reduce(reduceExtrema);
|
||||
|
||||
// Calculate best-fit dimensions
|
||||
this.dimensions = [ this.domainExtrema, this.rangeExtrema ]
|
||||
.map(dimensionsOf);
|
||||
this.origin = [ this.domainExtrema, this.rangeExtrema ]
|
||||
.map(originOf);
|
||||
|
||||
// Enforce some minimum visible area
|
||||
this.expandRange();
|
||||
|
||||
// ...then enforce a fixed duration if needed
|
||||
if (this.fixedDuration !== undefined) {
|
||||
this.origin[0] = this.origin[0] + this.dimensions[0] -
|
||||
this.fixedDuration;
|
||||
this.dimensions[0] = this.fixedDuration;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add latest data for this domain object
|
||||
PlotUpdater.prototype.addPointFor = function (domainObject) {
|
||||
var line = this.lines[domainObject.getId()];
|
||||
if (line) {
|
||||
line.addPoint(
|
||||
this.handle.getDomainValue(domainObject, this.domain),
|
||||
this.handle.getRangeValue(domainObject, this.range)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update with latest data.
|
||||
*/
|
||||
PlotUpdater.prototype.update = function update() {
|
||||
var objects = this.handle.getTelemetryObjects(),
|
||||
self = this;
|
||||
|
||||
// Initialize domain offset if necessary
|
||||
if (this.domainOffset === undefined) {
|
||||
this.initializeDomainOffset(objects.map(function (obj) {
|
||||
return self.handle.getDomainValue(obj, self.domain);
|
||||
}).filter(function (value) {
|
||||
return typeof value === 'number';
|
||||
}));
|
||||
}
|
||||
|
||||
// Make sure lines are available
|
||||
this.prepareLines(objects);
|
||||
|
||||
// Add new data
|
||||
objects.forEach(function (domainObject, index) {
|
||||
self.addPointFor(domainObject, index);
|
||||
});
|
||||
|
||||
// Then, update extrema
|
||||
this.updateBounds();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
PlotUpdater.prototype.getDimensions = function () {
|
||||
return this.dimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
PlotUpdater.prototype.getOrigin = function () {
|
||||
return this.origin;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
PlotUpdater.prototype.getDomainOffset = function () {
|
||||
return this.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
|
||||
* @memberof platform/features/plot.PlotUpdater#
|
||||
*/
|
||||
PlotUpdater.prototype.getLineBuffers = function () {
|
||||
return this.bufferArray;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill in historical data.
|
||||
*/
|
||||
PlotUpdater.prototype.addHistorical = function (domainObject, series) {
|
||||
var count = series ? series.getPointCount() : 0,
|
||||
line;
|
||||
|
||||
// Nothing to do if it's an empty series
|
||||
if (count < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize domain offset if necessary
|
||||
if (this.domainOffset === undefined) {
|
||||
this.initializeDomainOffset([
|
||||
series.getDomainValue(0, this.domain),
|
||||
series.getDomainValue(count - 1, this.domain)
|
||||
]);
|
||||
}
|
||||
|
||||
// Make sure lines are available
|
||||
this.prepareLines(this.handle.getTelemetryObjects());
|
||||
|
||||
// Look up the line for this domain object
|
||||
line = this.lines[domainObject.getId()];
|
||||
|
||||
// ...and put the data into it.
|
||||
if (line) {
|
||||
line.addSeries(series, this.domain, this.range);
|
||||
}
|
||||
|
||||
// Update extrema
|
||||
this.updateBounds();
|
||||
};
|
||||
|
||||
return PlotUpdater;
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user