mirror of
https://github.com/nasa/openmct.git
synced 2025-06-19 23:53:49 +00:00
[Plot] Bring in scripts from sandbox branch
Bring in scripts for plotting from the sandbox branch to begin transitioning/integrating plot view. WTD-533.
This commit is contained in:
27
platform/features/plot/bundle.json
Normal file
27
platform/features/plot/bundle.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "Plot view for telemetry",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"name": "Plot",
|
||||||
|
"templateUrl": "templates/plot.html",
|
||||||
|
"needs": [ "telemetry" ],
|
||||||
|
"delegation": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"directives": [
|
||||||
|
{
|
||||||
|
"key": "mctChart",
|
||||||
|
"implementation": "MCTChart.js",
|
||||||
|
"depends": [ "$interval" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "PlotController",
|
||||||
|
"implementation": "PlotController.js",
|
||||||
|
"depends": [ "$scope" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
6
platform/features/plot/lib/moment.min.js
vendored
Normal file
6
platform/features/plot/lib/moment.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
121
platform/features/plot/res/templates/plot.html
Normal file
121
platform/features/plot/res/templates/plot.html
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<div class="gl-plot"
|
||||||
|
ng-controller="TelemetryController as telemetry">
|
||||||
|
<span ng-controller="PlotController as plot"
|
||||||
|
ng-mouseleave="representation.showControls = false">
|
||||||
|
|
||||||
|
<div class="gl-plot-legend">
|
||||||
|
<span class='plot-legend-item'
|
||||||
|
ng-repeat="telemetryObject in telemetry.getTelemetryObjects() track by $index">
|
||||||
|
<span class='plot-color-swatch' ng-style="{ 'background-color': plot.getColor($index) }"></span>
|
||||||
|
<span class='title-label'>{{telemetryObject.getModel().name}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="gl-plot-coords"
|
||||||
|
ng-if="representation.showControls">
|
||||||
|
{{plot.getHoverCoordinates().join(', ')}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-axis-area gl-plot-y">
|
||||||
|
|
||||||
|
<div class="gl-plot-label gl-plot-y-label"
|
||||||
|
ng-show="!representation.showControls">
|
||||||
|
{{axes[1].active.name}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-repeat="tick in axes[1].ticks"
|
||||||
|
class="gl-plot-tick gl-plot-y-tick-label"
|
||||||
|
ng-style="{ bottom: (100 * $index / (axes[1].ticks.length - 1)) + '%' }">
|
||||||
|
{{tick.label}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-y-options gl-plot-local-controls"
|
||||||
|
ng-show="representation.showControls"
|
||||||
|
ng-if="axes[1].options.length > 0">
|
||||||
|
<div class='form-control shell select'>
|
||||||
|
<select class="form-control input shell select"
|
||||||
|
ng-model="axes[1].active"
|
||||||
|
ng-options="option.name for option in axes[1].options">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-display-area"
|
||||||
|
ng-mouseenter="representation.showControls = true">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="gl-plot-hash hash-v"
|
||||||
|
ng-repeat="tick in axes[0].ticks"
|
||||||
|
ng-style="{ left: (100 * $index / (axes[0].ticks.length - 1)) + '%', height: '100%' }"
|
||||||
|
ng-show="$index > 0 && $index < (axes[0].ticks.length - 1)">
|
||||||
|
</div>
|
||||||
|
<div class="gl-plot-hash hash-h"
|
||||||
|
ng-repeat="tick in axes[1].ticks"
|
||||||
|
ng-style="{ bottom: (100 * $index / (axes[1].ticks.length - 1)) + '%', width: '100%' }"
|
||||||
|
ng-show="$index > 0 && $index < (axes[1].ticks.length - 1)">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mct-chart draw="draw"
|
||||||
|
ng-mousemove="plot.hover($event)"
|
||||||
|
ng-mousedown="plot.startMarquee($event)"
|
||||||
|
ng-mouseup="plot.endMarquee($event)">
|
||||||
|
</mct-chart>
|
||||||
|
|
||||||
|
<!-- TODO: Move into correct position; make part of group; infer from set of actions -->
|
||||||
|
<div class="gl-plot-local-controls"
|
||||||
|
style="position: absolute; top: 8px; right: 8px;">
|
||||||
|
|
||||||
|
<a href=""
|
||||||
|
class="t-btn l-btn s-btn s-icon-btn s-very-subtle"
|
||||||
|
ng-click="plot.stepBackPanZoom()"
|
||||||
|
ng-show="plot.isZoomed()"
|
||||||
|
title="Restore previous pan/zoom">
|
||||||
|
<span class="ui-symbol icon">B</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href=""
|
||||||
|
class="t-btn l-btn s-btn s-icon-btn s-very-subtle"
|
||||||
|
ng-click="plot.unzoom()"
|
||||||
|
ng-show="plot.isZoomed()"
|
||||||
|
title="Reset pan/zoom">
|
||||||
|
<span class="ui-symbol icon">R</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="t-wait-spinner loading" ng-show="telemetry.isRequestPending()">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-axis-area gl-plot-x">
|
||||||
|
|
||||||
|
<div ng-repeat="tick in axes[0].ticks"
|
||||||
|
class="gl-plot-tick gl-plot-x-tick-label"
|
||||||
|
ng-show="$index > 0 && $index < (axes[0].ticks.length - 1)"
|
||||||
|
ng-style="{ left: (100 * $index / (axes[0].ticks.length - 1)) + '%' }">
|
||||||
|
{{tick.label}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gl-plot-label gl-plot-x-label">
|
||||||
|
{{axes[0].active.name}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="gl-plot-x-options gl-plot-local-controls"
|
||||||
|
ng-show="representation.showControls"
|
||||||
|
ng-if="axes[0].options.length > 0">
|
||||||
|
<div class='form-control shell select'>
|
||||||
|
<select class="form-control input shell select"
|
||||||
|
ng-model="axes[0].active"
|
||||||
|
ng-options="option.name for option in axes[0].options">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
99
platform/features/plot/src/GLPlot.js
Normal file
99
platform/features/plot/src/GLPlot.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*global define,Promise,Float32Array*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining GLPlot. Created by vwoeltje on 11/12/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var FRAGMENT_SHADER = [
|
||||||
|
"precision mediump float;",
|
||||||
|
"uniform vec4 uColor;",
|
||||||
|
"void main(void) {",
|
||||||
|
"gl_FragColor = uColor;",
|
||||||
|
"}"
|
||||||
|
].join('\n'),
|
||||||
|
VERTEX_SHADER = [
|
||||||
|
"attribute vec2 aVertexPosition;",
|
||||||
|
"uniform vec2 uDimensions;",
|
||||||
|
"uniform vec2 uOrigin;",
|
||||||
|
"void main(void) {",
|
||||||
|
"gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
|
||||||
|
"}"
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
function GLPlot(canvas) {
|
||||||
|
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"),
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
|
program,
|
||||||
|
aVertexPosition,
|
||||||
|
uColor,
|
||||||
|
uDimensions,
|
||||||
|
uOrigin,
|
||||||
|
buffer;
|
||||||
|
|
||||||
|
if (!gl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize shaders
|
||||||
|
vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
||||||
|
gl.shaderSource(vertexShader, VERTEX_SHADER);
|
||||||
|
gl.compileShader(vertexShader);
|
||||||
|
fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
||||||
|
gl.shaderSource(fragmentShader, FRAGMENT_SHADER);
|
||||||
|
gl.compileShader(fragmentShader);
|
||||||
|
|
||||||
|
program = gl.createProgram();
|
||||||
|
gl.attachShader(program, vertexShader);
|
||||||
|
gl.attachShader(program, fragmentShader);
|
||||||
|
gl.linkProgram(program);
|
||||||
|
gl.useProgram(program);
|
||||||
|
|
||||||
|
aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
|
||||||
|
gl.enableVertexAttribArray(aVertexPosition);
|
||||||
|
|
||||||
|
uColor = gl.getUniformLocation(program, "uColor");
|
||||||
|
uDimensions = gl.getUniformLocation(program, "uDimensions");
|
||||||
|
uOrigin = gl.getUniformLocation(program, "uOrigin");
|
||||||
|
|
||||||
|
buffer = gl.createBuffer();
|
||||||
|
|
||||||
|
gl.lineWidth(2.0);
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
function doDraw(drawType, buf, color, points) {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
|
||||||
|
gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.uniform4fv(uColor, color);
|
||||||
|
gl.drawArrays(drawType, 0, points);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
clear: function () {
|
||||||
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
|
||||||
|
},
|
||||||
|
setDimensions: function (dimensions, origin) {
|
||||||
|
gl.uniform2fv(uDimensions, dimensions);
|
||||||
|
gl.uniform2fv(uOrigin, origin);
|
||||||
|
},
|
||||||
|
drawLine: function (buf, color, points) {
|
||||||
|
doDraw(gl.LINE_STRIP, buf, color, points);
|
||||||
|
},
|
||||||
|
drawSquare: function (min, max, color) {
|
||||||
|
doDraw(gl.TRIANGLE_FAN, new Float32Array(
|
||||||
|
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
|
||||||
|
), color, 4);
|
||||||
|
},
|
||||||
|
gl: gl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return GLPlot;
|
||||||
|
}
|
||||||
|
);
|
92
platform/features/plot/src/GLPlotPreparer.js
Normal file
92
platform/features/plot/src/GLPlotPreparer.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*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';
|
||||||
|
|
||||||
|
function identity(x) { return x; }
|
||||||
|
|
||||||
|
function GLPlotPreparer(datas, domain, range) {
|
||||||
|
var index,
|
||||||
|
vertices = [],
|
||||||
|
max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||||
|
min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
xLabels = {},
|
||||||
|
yLabels = {},
|
||||||
|
yUnits = {},
|
||||||
|
domainOffset = Number.POSITIVE_INFINITY,
|
||||||
|
buffers;
|
||||||
|
|
||||||
|
datas = datas || [];
|
||||||
|
|
||||||
|
datas.filter(identity).forEach(function (data) {
|
||||||
|
domainOffset = Math.min(data.getDomainValue(0, domain), domainOffset);
|
||||||
|
});
|
||||||
|
|
||||||
|
datas.forEach(function (data, i) {
|
||||||
|
if (!data) {
|
||||||
|
return; // skip null data
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.push([]);
|
||||||
|
for (index = 0; index < data.getPointCount(); index = index + 1) {
|
||||||
|
x = data.getDomainValue(index, domain) - domainOffset;
|
||||||
|
y = data.getRangeValue(index, range);
|
||||||
|
vertices[i].push(x);
|
||||||
|
vertices[i].push(y);
|
||||||
|
min[0] = Math.min(min[0], x);
|
||||||
|
min[1] = Math.min(min[1], y);
|
||||||
|
max[0] = Math.max(max[0], x);
|
||||||
|
max[1] = Math.max(max[1], y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.getDomainLabel) {
|
||||||
|
xLabels[data.getDomainLabel(domain)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.getRangeLabel) {
|
||||||
|
yLabels[data.getRangeLabel(range)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.getRangeUnits) {
|
||||||
|
yUnits[data.getRangeUnits(range)] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (max[1] === min[1]) {
|
||||||
|
max[1] = max[1] + 1.0;
|
||||||
|
min[1] = min[1] - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
xLabels = Object.keys(xLabels).sort();
|
||||||
|
yLabels = Object.keys(yLabels).sort();
|
||||||
|
yUnits = Object.keys(yUnits).sort();
|
||||||
|
|
||||||
|
buffers = vertices.map(function (v) { return new Float32Array(v); });
|
||||||
|
|
||||||
|
return {
|
||||||
|
getDimensions: function () {
|
||||||
|
return [max[0] - min[0], max[1] - min[1]];
|
||||||
|
},
|
||||||
|
getOrigin: function () {
|
||||||
|
return min;
|
||||||
|
},
|
||||||
|
getDomainOffset: function () {
|
||||||
|
return domainOffset;
|
||||||
|
},
|
||||||
|
getBuffers: function () {
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return GLPlotPreparer;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
78
platform/features/plot/src/MCTChart.js
Normal file
78
platform/features/plot/src/MCTChart.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining MCTChart. Created by vwoeltje on 11/12/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["./GLPlot"],
|
||||||
|
function (GLPlot) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MCTChart($interval) {
|
||||||
|
|
||||||
|
function linkChart(scope, element) {
|
||||||
|
var canvas = element.find("canvas")[0],
|
||||||
|
chart = new GLPlot(canvas);
|
||||||
|
|
||||||
|
function doDraw() {
|
||||||
|
var draw = scope.draw;
|
||||||
|
|
||||||
|
canvas.width = canvas.offsetWidth;
|
||||||
|
canvas.height = canvas.offsetHeight;
|
||||||
|
chart.clear();
|
||||||
|
|
||||||
|
if (!draw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.setDimensions(
|
||||||
|
draw.dimensions || [1, 1],
|
||||||
|
draw.origin || [0, 0]
|
||||||
|
);
|
||||||
|
|
||||||
|
(draw.lines || []).forEach(function (line) {
|
||||||
|
chart.drawLine(
|
||||||
|
line.buffer,
|
||||||
|
line.color,
|
||||||
|
line.points
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
(draw.boxes || []).forEach(function (box) {
|
||||||
|
chart.drawSquare(
|
||||||
|
box.start,
|
||||||
|
box.end,
|
||||||
|
box.color
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawIfResized() {
|
||||||
|
if (canvas.width !== canvas.offsetWidth ||
|
||||||
|
canvas.height !== canvas.offsetHeight) {
|
||||||
|
doDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$interval(drawIfResized, 1000);
|
||||||
|
scope.$watchCollection("draw", doDraw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: "E",
|
||||||
|
template: TEMPLATE,
|
||||||
|
link: linkChart,
|
||||||
|
scope: { draw: "=" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTChart;
|
||||||
|
}
|
||||||
|
);
|
270
platform/features/plot/src/PlotController.js
Normal file
270
platform/features/plot/src/PlotController.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
/*global define,moment,Promise*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining PlotController. Created by vwoeltje on 11/12/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["./GLPlotPreparer", "./PlotPalette", "../lib/moment.min.js"],
|
||||||
|
function (GLPlotPreparer, PlotPalette) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var AXIS_DEFAULTS = [
|
||||||
|
{ "name": "Time" },
|
||||||
|
{ "name": "Value" }
|
||||||
|
],
|
||||||
|
DOMAIN_TICKS = 5,
|
||||||
|
RANGE_TICKS = 7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function PlotController($scope) {
|
||||||
|
var mousePosition,
|
||||||
|
marqueeStart,
|
||||||
|
panZoomStack = [{
|
||||||
|
dimensions: [],
|
||||||
|
origin: []
|
||||||
|
}],
|
||||||
|
domainOffset;
|
||||||
|
|
||||||
|
function formatDomainValue(v) {
|
||||||
|
return moment.utc(v).format("YYYY-DDD HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRangeValue(v) {
|
||||||
|
return v.toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility, for map/forEach loops. Index 0 is domain,
|
||||||
|
// index 1 is range.
|
||||||
|
function formatValue(v, i) {
|
||||||
|
return (i ? formatRangeValue : formatDomainValue)(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function pixelToDomainRange(x, y, width, height, domainOffset) {
|
||||||
|
var panZoom = panZoomStack[panZoomStack.length - 1],
|
||||||
|
offset = [ domainOffset || 0, 0],
|
||||||
|
origin = panZoom.origin,
|
||||||
|
dimensions = panZoom.dimensions;
|
||||||
|
|
||||||
|
if (!dimensions || !origin) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ x / width, (height - y) / height ].map(function (v, i) {
|
||||||
|
return v * dimensions[i] + origin[i] + offset[i];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousePositionToDomainRange(mousePosition, domainOffset) {
|
||||||
|
return pixelToDomainRange(
|
||||||
|
mousePosition.x,
|
||||||
|
mousePosition.y,
|
||||||
|
mousePosition.width,
|
||||||
|
mousePosition.height,
|
||||||
|
domainOffset
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTicks(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMarqueeBox() {
|
||||||
|
$scope.draw.boxes = marqueeStart ?
|
||||||
|
[{
|
||||||
|
start: mousePositionToDomainRange(marqueeStart),
|
||||||
|
end: mousePositionToDomainRange(mousePosition),
|
||||||
|
color: [1, 1, 1, 0.5 ]
|
||||||
|
}] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDrawingBounds() {
|
||||||
|
var panZoom = panZoomStack[panZoomStack.length - 1];
|
||||||
|
|
||||||
|
$scope.draw.dimensions = panZoom.dimensions;
|
||||||
|
$scope.draw.origin = panZoom.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function plotTelemetry() {
|
||||||
|
var telemetry, prepared, data;
|
||||||
|
|
||||||
|
telemetry = $scope.telemetry;
|
||||||
|
|
||||||
|
if (!telemetry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = telemetry.getResponse();
|
||||||
|
|
||||||
|
prepared = new GLPlotPreparer(
|
||||||
|
data,
|
||||||
|
($scope.axes[0].active || {}).key,
|
||||||
|
($scope.axes[1].active || {}).key
|
||||||
|
);
|
||||||
|
|
||||||
|
$scope.axes[0].ticks = generateTicks(
|
||||||
|
prepared.getOrigin()[0] + prepared.getDomainOffset(),
|
||||||
|
prepared.getDimensions()[0],
|
||||||
|
DOMAIN_TICKS,
|
||||||
|
formatDomainValue
|
||||||
|
);
|
||||||
|
$scope.axes[1].ticks = generateTicks(
|
||||||
|
prepared.getOrigin()[1],
|
||||||
|
prepared.getDimensions()[1],
|
||||||
|
RANGE_TICKS,
|
||||||
|
formatRangeValue
|
||||||
|
);
|
||||||
|
|
||||||
|
panZoomStack[0] = {
|
||||||
|
origin: prepared.getOrigin(),
|
||||||
|
dimensions: prepared.getDimensions()
|
||||||
|
};
|
||||||
|
|
||||||
|
domainOffset = prepared.getDomainOffset();
|
||||||
|
|
||||||
|
$scope.draw.lines = prepared.getBuffers().map(function (buf, i) {
|
||||||
|
return {
|
||||||
|
buffer: buf,
|
||||||
|
color: PlotPalette.getFloatColor(i),
|
||||||
|
points: buf.length / 2
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
updateDrawingBounds();
|
||||||
|
updateMarqueeBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupAxes(metadatas) {
|
||||||
|
var domainKeys = {},
|
||||||
|
rangeKeys = {},
|
||||||
|
domains = [],
|
||||||
|
ranges = [];
|
||||||
|
|
||||||
|
function buildOptionsForMetadata(m) {
|
||||||
|
(m.domains || []).forEach(function (domain) {
|
||||||
|
if (!domainKeys[domain.key]) {
|
||||||
|
domainKeys[domain.key] = true;
|
||||||
|
domains.push(domain);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(m.ranges || []).forEach(function (range) {
|
||||||
|
if (!rangeKeys[range.key]) {
|
||||||
|
rangeKeys[range.key] = true;
|
||||||
|
ranges.push(range);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(metadatas || []).
|
||||||
|
forEach(buildOptionsForMetadata);
|
||||||
|
|
||||||
|
[domains, ranges].forEach(function (options, i) {
|
||||||
|
var active = $scope.axes[i].active;
|
||||||
|
$scope.axes[i].options = options;
|
||||||
|
if (!active || !active.key) {
|
||||||
|
$scope.axes[i].active =
|
||||||
|
options[0] || AXIS_DEFAULTS[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMousePosition($event) {
|
||||||
|
var target = $event.target,
|
||||||
|
bounds = target.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: $event.clientX - bounds.left,
|
||||||
|
y: $event.clientY - bounds.top,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function marqueeZoom(start, end) {
|
||||||
|
var a = mousePositionToDomainRange(start),
|
||||||
|
b = mousePositionToDomainRange(end),
|
||||||
|
origin = [
|
||||||
|
Math.min(a[0], b[0]),
|
||||||
|
Math.min(a[1], b[1])
|
||||||
|
],
|
||||||
|
dimensions = [
|
||||||
|
Math.max(a[0], b[0]) - origin[0],
|
||||||
|
Math.max(a[1], b[1]) - origin[1]
|
||||||
|
];
|
||||||
|
|
||||||
|
panZoomStack.push({
|
||||||
|
origin: origin,
|
||||||
|
dimensions: dimensions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.axes = [ {}, {} ];
|
||||||
|
$scope.$watch("telemetry.getMetadata()", setupAxes);
|
||||||
|
$scope.$on("telemetryUpdate", plotTelemetry);
|
||||||
|
$scope.draw = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getColor: function (index) {
|
||||||
|
return PlotPalette.getStringColor(index);
|
||||||
|
},
|
||||||
|
getHoverCoordinates: function () {
|
||||||
|
return mousePosition ?
|
||||||
|
mousePositionToDomainRange(
|
||||||
|
mousePosition,
|
||||||
|
domainOffset
|
||||||
|
).map(formatValue) : [];
|
||||||
|
},
|
||||||
|
hover: function ($event) {
|
||||||
|
mousePosition = toMousePosition($event);
|
||||||
|
if (marqueeStart) {
|
||||||
|
updateMarqueeBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startMarquee: function ($event) {
|
||||||
|
mousePosition = marqueeStart = toMousePosition($event);
|
||||||
|
updateMarqueeBox();
|
||||||
|
},
|
||||||
|
endMarquee: function ($event) {
|
||||||
|
mousePosition = toMousePosition($event);
|
||||||
|
if (marqueeStart) {
|
||||||
|
marqueeZoom(marqueeStart, mousePosition);
|
||||||
|
marqueeStart = undefined;
|
||||||
|
updateMarqueeBox();
|
||||||
|
updateDrawingBounds();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isZoomed: function () {
|
||||||
|
return panZoomStack.length > 1;
|
||||||
|
},
|
||||||
|
stepBackPanZoom: function () {
|
||||||
|
if (panZoomStack.length > 1) {
|
||||||
|
panZoomStack.pop();
|
||||||
|
updateDrawingBounds();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unzoom: function () {
|
||||||
|
panZoomStack = [panZoomStack[0]];
|
||||||
|
updateDrawingBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlotController;
|
||||||
|
}
|
||||||
|
);
|
65
platform/features/plot/src/PlotPalette.js
Normal file
65
platform/features/plot/src/PlotPalette.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plot palette. Defines colors for various plot lines.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var integerPalette = [
|
||||||
|
[ 0x20, 0xB2, 0xAA ],
|
||||||
|
[ 0x9A, 0xCD, 0x32 ],
|
||||||
|
[ 0xFF, 0x8C, 0x00 ],
|
||||||
|
[ 0xD2, 0xB4, 0x8C ],
|
||||||
|
[ 0x40, 0xE0, 0xD0 ],
|
||||||
|
[ 0x41, 0x69, 0xFF ],
|
||||||
|
[ 0xFF, 0xD7, 0x00 ],
|
||||||
|
[ 0x6A, 0x5A, 0xCD ],
|
||||||
|
[ 0xEE, 0x82, 0xEE ],
|
||||||
|
[ 0xCC, 0x99, 0x66 ],
|
||||||
|
[ 0x99, 0xCC, 0xCC ],
|
||||||
|
[ 0x66, 0xCC, 0x33 ],
|
||||||
|
[ 0xFF, 0xCC, 0x00 ],
|
||||||
|
[ 0xFF, 0x66, 0x33 ],
|
||||||
|
[ 0xCC, 0x66, 0xFF ],
|
||||||
|
[ 0xFF, 0x00, 0x66 ],
|
||||||
|
[ 0xFF, 0xFF, 0x00 ],
|
||||||
|
[ 0x80, 0x00, 0x80 ],
|
||||||
|
[ 0x00, 0x86, 0x8B ],
|
||||||
|
[ 0x00, 0x8A, 0x00 ],
|
||||||
|
[ 0xFF, 0x00, 0x00 ],
|
||||||
|
[ 0x00, 0x00, 0xFF ],
|
||||||
|
[ 0xF5, 0xDE, 0xB3 ],
|
||||||
|
[ 0xBC, 0x8F, 0x8F ],
|
||||||
|
[ 0x46, 0x82, 0xB4 ],
|
||||||
|
[ 0xFF, 0xAF, 0xAF ],
|
||||||
|
[ 0x43, 0xCD, 0x80 ],
|
||||||
|
[ 0xCD, 0xC1, 0xC5 ],
|
||||||
|
[ 0xA0, 0x52, 0x2D ],
|
||||||
|
[ 0x64, 0x95, 0xED ]
|
||||||
|
], stringPalette = integerPalette.map(function (arr) {
|
||||||
|
// Convert to # notation for use in styles
|
||||||
|
return '#' + arr.map(function (c) {
|
||||||
|
return (c < 16 ? '0' : '') + c.toString(16);
|
||||||
|
}).join('');
|
||||||
|
}), floatPalette = integerPalette.map(function (arr) {
|
||||||
|
return arr.map(function (c) {
|
||||||
|
return c / 255.0;
|
||||||
|
}).concat([1]); // RGBA
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getIntegerColor: function (i) {
|
||||||
|
return integerPalette[Math.floor(i) % integerPalette.length];
|
||||||
|
},
|
||||||
|
getFloatColor: function (i) {
|
||||||
|
return floatPalette[Math.floor(i) % floatPalette.length];
|
||||||
|
},
|
||||||
|
getStringColor: function (i) {
|
||||||
|
return stringPalette[Math.floor(i) % stringPalette.length];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
Reference in New Issue
Block a user