mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 09:52:04 +00:00
Merge pull request #1406 from nasa/separate-timeline-and-plot
[Reorg] Make timeline-specific chart directive
This commit is contained in:
commit
4f24c46e9b
@ -1339,41 +1339,6 @@ are supported:
|
|||||||
Open MCT defines several Angular directives that are intended for use both
|
Open MCT defines several Angular directives that are intended for use both
|
||||||
internally within the platform, and by plugins.
|
internally within the platform, and by plugins.
|
||||||
|
|
||||||
## Chart
|
|
||||||
|
|
||||||
The `mct-chart` directive is used to support drawing of simple charts. It is
|
|
||||||
present to support the Plot view, and its functionality is limited to the
|
|
||||||
functionality that is relevant for that view.
|
|
||||||
|
|
||||||
This directive is used at the element level and takes one attribute, `draw`
|
|
||||||
which is an Angular expression which will should evaluate to a drawing object.
|
|
||||||
This drawing object should contain the following properties:
|
|
||||||
|
|
||||||
* `dimensions`: The size, in logical coordinates, of the chart area. A
|
|
||||||
two-element array or numbers.
|
|
||||||
* `origin`: The position, in logical coordinates, of the lower-left corner of
|
|
||||||
the chart area. A two-element array or numbers.
|
|
||||||
* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is
|
|
||||||
expressed as an object containing:
|
|
||||||
* `buffer`: A Float32Array containing points in the line, in logical
|
|
||||||
coordinates, in sequential x,y pairs.
|
|
||||||
* `color`: The color of the line, as a four-element RGBA array, where
|
|
||||||
each element is a number in the range of 0.0-1.0.
|
|
||||||
* `points`: The number of points in the line.
|
|
||||||
* `boxes`: An array of rectangles to draw in the chart area. Each is an object
|
|
||||||
containing:
|
|
||||||
* `start`: The first corner of the rectangle, as a two-element array of
|
|
||||||
numbers, in logical coordinates.
|
|
||||||
* `end`: The opposite corner of the rectangle, as a two-element array of
|
|
||||||
numbers, in logical coordinates. color : The color of the line, as a
|
|
||||||
four-element RGBA array, where each element is a number in the range of
|
|
||||||
0.0-1.0.
|
|
||||||
|
|
||||||
While `mct-chart` is intended to support plots specifically, it does perform
|
|
||||||
some useful management of canvas objects (e.g. choosing between WebGL and Canvas
|
|
||||||
2D APIs for drawing based on browser support) so its usage is recommended when
|
|
||||||
its supported drawing primitives are sufficient for other charting tasks.
|
|
||||||
|
|
||||||
## Container
|
## Container
|
||||||
|
|
||||||
The `mct-container` is similar to the `mct-include` directive insofar as it allows
|
The `mct-container` is similar to the `mct-include` directive insofar as it allows
|
||||||
|
37
platform/features/plot/README.md
Normal file
37
platform/features/plot/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Plot README
|
||||||
|
|
||||||
|
## Chart
|
||||||
|
|
||||||
|
The `mct-chart` directive is used to support drawing of simple charts. It is
|
||||||
|
present to support the Plot view, and its functionality is limited to the
|
||||||
|
functionality that is relevant for that view.
|
||||||
|
|
||||||
|
This directive is used at the element level and takes one attribute, `draw`
|
||||||
|
which is an Angular expression which will should evaluate to a drawing object.
|
||||||
|
This drawing object should contain the following properties:
|
||||||
|
|
||||||
|
* `dimensions`: The size, in logical coordinates, of the chart area. A
|
||||||
|
two-element array or numbers.
|
||||||
|
* `origin`: The position, in logical coordinates, of the lower-left corner of
|
||||||
|
the chart area. A two-element array or numbers.
|
||||||
|
* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is
|
||||||
|
expressed as an object containing:
|
||||||
|
* `buffer`: A Float32Array containing points in the line, in logical
|
||||||
|
coordinates, in sequential x,y pairs.
|
||||||
|
* `color`: The color of the line, as a four-element RGBA array, where
|
||||||
|
each element is a number in the range of 0.0-1.0.
|
||||||
|
* `points`: The number of points in the line.
|
||||||
|
* `boxes`: An array of rectangles to draw in the chart area. Each is an object
|
||||||
|
containing:
|
||||||
|
* `start`: The first corner of the rectangle, as a two-element array of
|
||||||
|
numbers, in logical coordinates.
|
||||||
|
* `end`: The opposite corner of the rectangle, as a two-element array of
|
||||||
|
numbers, in logical coordinates. color : The color of the line, as a
|
||||||
|
four-element RGBA array, where each element is a number in the range of
|
||||||
|
0.0-1.0.
|
||||||
|
|
||||||
|
While `mct-chart` is intended to support plots specifically, it does perform
|
||||||
|
some useful management of canvas objects (e.g. choosing between WebGL and Canvas
|
||||||
|
2D APIs for drawing based on browser support) so its usage is recommended when
|
||||||
|
its supported drawing primitives are sufficient for other charting tasks.
|
||||||
|
|
@ -38,6 +38,7 @@ define([
|
|||||||
"./src/directives/MCTSwimlaneDrop",
|
"./src/directives/MCTSwimlaneDrop",
|
||||||
"./src/directives/MCTSwimlaneDrag",
|
"./src/directives/MCTSwimlaneDrag",
|
||||||
"./src/services/ObjectLoader",
|
"./src/services/ObjectLoader",
|
||||||
|
"./src/chart/MCTTimelineChart",
|
||||||
"text!./res/templates/values.html",
|
"text!./res/templates/values.html",
|
||||||
"text!./res/templates/timeline.html",
|
"text!./res/templates/timeline.html",
|
||||||
"text!./res/templates/activity-gantt.html",
|
"text!./res/templates/activity-gantt.html",
|
||||||
@ -67,6 +68,7 @@ define([
|
|||||||
MCTSwimlaneDrop,
|
MCTSwimlaneDrop,
|
||||||
MCTSwimlaneDrag,
|
MCTSwimlaneDrag,
|
||||||
ObjectLoader,
|
ObjectLoader,
|
||||||
|
MCTTimelineChart,
|
||||||
valuesTemplate,
|
valuesTemplate,
|
||||||
timelineTemplate,
|
timelineTemplate,
|
||||||
activityGanttTemplate,
|
activityGanttTemplate,
|
||||||
@ -556,6 +558,14 @@ define([
|
|||||||
"depends": [
|
"depends": [
|
||||||
"dndService"
|
"dndService"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "mctTimelineChart",
|
||||||
|
"implementation": MCTTimelineChart,
|
||||||
|
"depends": [
|
||||||
|
"$interval",
|
||||||
|
"$log"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"services": [
|
"services": [
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<span ng-controller="TimelineGraphController as graphController">
|
<span ng-controller="TimelineGraphController as graphController">
|
||||||
<div class="t-graph l-graph" ng-repeat="graph in parameters.graphs">
|
<div class="t-graph l-graph" ng-repeat="graph in parameters.graphs">
|
||||||
<div class="l-graph-area l-canvas-holder">
|
<div class="l-graph-area l-canvas-holder">
|
||||||
<mct-chart draw="graph.drawingObject"></mct-chart>
|
<mct-timeline-chart draw="graph.drawingObject"></mct-timeline-chart>
|
||||||
</div>
|
</div>
|
||||||
<div class="t-graph-labels l-graph-labels">
|
<div class="t-graph-labels l-graph-labels">
|
||||||
<mct-include key="'timeline-resource-graph-labels'"
|
<mct-include key="'timeline-resource-graph-labels'"
|
||||||
@ -31,4 +31,4 @@
|
|||||||
</mct-include>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
117
platform/features/timeline/src/chart/Canvas2DChart.js
Normal file
117
platform/features/timeline/src/chart/Canvas2DChart.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new chart which uses Canvas's 2D API for rendering.
|
||||||
|
*
|
||||||
|
* @memberof platform/features/plot
|
||||||
|
* @constructor
|
||||||
|
* @implements {platform/features/plot.Chart}
|
||||||
|
* @param {CanvasElement} canvas the canvas object to render upon
|
||||||
|
* @throws {Error} an error is thrown if Canvas's 2D API is unavailable.
|
||||||
|
*/
|
||||||
|
function Canvas2DChart(canvas) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.c2d = canvas.getContext('2d');
|
||||||
|
this.width = canvas.width;
|
||||||
|
this.height = canvas.height;
|
||||||
|
this.dimensions = [this.width, this.height];
|
||||||
|
this.origin = [0, 0];
|
||||||
|
|
||||||
|
if (!this.c2d) {
|
||||||
|
throw new Error("Canvas 2d API unavailable.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from logical to physical x coordinates
|
||||||
|
Canvas2DChart.prototype.x = function (v) {
|
||||||
|
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert from logical to physical y coordinates
|
||||||
|
Canvas2DChart.prototype.y = function (v) {
|
||||||
|
return this.height -
|
||||||
|
((v - this.origin[1]) / this.dimensions[1]) * this.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the color to be used for drawing operations
|
||||||
|
Canvas2DChart.prototype.setColor = function (color) {
|
||||||
|
var mappedColor = color.map(function (c, i) {
|
||||||
|
return i < 3 ? Math.floor(c * 255) : (c);
|
||||||
|
}).join(',');
|
||||||
|
this.c2d.strokeStyle = "rgba(" + mappedColor + ")";
|
||||||
|
this.c2d.fillStyle = "rgba(" + mappedColor + ")";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Canvas2DChart.prototype.clear = function () {
|
||||||
|
var canvas = this.canvas;
|
||||||
|
this.width = canvas.width;
|
||||||
|
this.height = canvas.height;
|
||||||
|
this.c2d.clearRect(0, 0, this.width, this.height);
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas2DChart.prototype.setDimensions = function (newDimensions, newOrigin) {
|
||||||
|
this.dimensions = newDimensions;
|
||||||
|
this.origin = newOrigin;
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas2DChart.prototype.drawLine = function (buf, color, points) {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
this.setColor(color);
|
||||||
|
|
||||||
|
// Configure context to draw two-pixel-thick lines
|
||||||
|
this.c2d.lineWidth = 2;
|
||||||
|
|
||||||
|
// Start a new path...
|
||||||
|
if (buf.length > 1) {
|
||||||
|
this.c2d.beginPath();
|
||||||
|
this.c2d.moveTo(this.x(buf[0]), this.y(buf[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and add points to it...
|
||||||
|
for (i = 2; i < points * 2; i = i + 2) {
|
||||||
|
this.c2d.lineTo(this.x(buf[i]), this.y(buf[i + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...before finally drawing it.
|
||||||
|
this.c2d.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
Canvas2DChart.prototype.drawSquare = function (min, max, color) {
|
||||||
|
var x1 = this.x(min[0]),
|
||||||
|
y1 = this.y(min[1]),
|
||||||
|
w = this.x(max[0]) - x1,
|
||||||
|
h = this.y(max[1]) - y1;
|
||||||
|
|
||||||
|
this.setColor(color);
|
||||||
|
this.c2d.fillRect(x1, y1, w, h);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Canvas2DChart;
|
||||||
|
}
|
||||||
|
);
|
160
platform/features/timeline/src/chart/GLChart.js
Normal file
160
platform/features/timeline/src/chart/GLChart.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining GLPlot. Created by vwoeltje on 11/12/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
// WebGL shader sources (for drawing plain colors)
|
||||||
|
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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new chart which uses WebGL for rendering.
|
||||||
|
*
|
||||||
|
* @memberof platform/features/plot
|
||||||
|
* @constructor
|
||||||
|
* @implements {platform/features/plot.Chart}
|
||||||
|
* @param {CanvasElement} canvas the canvas object to render upon
|
||||||
|
* @throws {Error} an error is thrown if WebGL is unavailable.
|
||||||
|
*/
|
||||||
|
function GLChart(canvas) {
|
||||||
|
var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) ||
|
||||||
|
canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }),
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader,
|
||||||
|
program,
|
||||||
|
aVertexPosition,
|
||||||
|
uColor,
|
||||||
|
uDimensions,
|
||||||
|
uOrigin;
|
||||||
|
|
||||||
|
// Ensure a context was actually available before proceeding
|
||||||
|
if (!gl) {
|
||||||
|
throw new Error("WebGL unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Assemble vertex/fragment shaders into programs
|
||||||
|
program = gl.createProgram();
|
||||||
|
gl.attachShader(program, vertexShader);
|
||||||
|
gl.attachShader(program, fragmentShader);
|
||||||
|
gl.linkProgram(program);
|
||||||
|
gl.useProgram(program);
|
||||||
|
|
||||||
|
// Get locations for attribs/uniforms from the
|
||||||
|
// shader programs (to pass values into shaders at draw-time)
|
||||||
|
aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
|
||||||
|
uColor = gl.getUniformLocation(program, "uColor");
|
||||||
|
uDimensions = gl.getUniformLocation(program, "uDimensions");
|
||||||
|
uOrigin = gl.getUniformLocation(program, "uOrigin");
|
||||||
|
gl.enableVertexAttribArray(aVertexPosition);
|
||||||
|
|
||||||
|
// Create a buffer to holds points which will be drawn
|
||||||
|
this.buffer = gl.createBuffer();
|
||||||
|
|
||||||
|
// Use a line width of 2.0 for legibility
|
||||||
|
gl.lineWidth(2.0);
|
||||||
|
|
||||||
|
// Enable blending, for smoothness
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
this.gl = gl;
|
||||||
|
this.aVertexPosition = aVertexPosition;
|
||||||
|
this.uColor = uColor;
|
||||||
|
this.uDimensions = uDimensions;
|
||||||
|
this.uOrigin = uOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to handle drawing of a buffer;
|
||||||
|
// drawType will determine whether this is a box, line, etc.
|
||||||
|
GLChart.prototype.doDraw = function (drawType, buf, color, points) {
|
||||||
|
var gl = this.gl;
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, buf, gl.DYNAMIC_DRAW);
|
||||||
|
gl.vertexAttribPointer(this.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.uniform4fv(this.uColor, color);
|
||||||
|
gl.drawArrays(drawType, 0, points);
|
||||||
|
};
|
||||||
|
|
||||||
|
GLChart.prototype.clear = function () {
|
||||||
|
var gl = this.gl;
|
||||||
|
|
||||||
|
// Set the viewport size; note that we use the width/height
|
||||||
|
// that our WebGL context reports, which may be lower
|
||||||
|
// resolution than the canvas we requested.
|
||||||
|
gl.viewport(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
gl.drawingBufferWidth,
|
||||||
|
gl.drawingBufferHeight
|
||||||
|
);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
GLChart.prototype.setDimensions = function (dimensions, origin) {
|
||||||
|
var gl = this.gl;
|
||||||
|
if (dimensions && dimensions.length > 0 &&
|
||||||
|
origin && origin.length > 0) {
|
||||||
|
gl.uniform2fv(this.uDimensions, dimensions);
|
||||||
|
gl.uniform2fv(this.uOrigin, origin);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GLChart.prototype.drawLine = function (buf, color, points) {
|
||||||
|
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
|
||||||
|
};
|
||||||
|
|
||||||
|
GLChart.prototype.drawSquare = function (min, max, color) {
|
||||||
|
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
|
||||||
|
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
|
||||||
|
), color, 4);
|
||||||
|
};
|
||||||
|
|
||||||
|
return GLChart;
|
||||||
|
}
|
||||||
|
);
|
250
platform/features/timeline/src/chart/MCTTimelineChart.js
Normal file
250
platform/features/timeline/src/chart/MCTTimelineChart.js
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining MCTTimelineChart. Created by vwoeltje on 11/12/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["./GLChart", "./Canvas2DChart"],
|
||||||
|
function (GLChart, Canvas2DChart) {
|
||||||
|
|
||||||
|
var TEMPLATE = "<canvas style='position: absolute; background: none; width: 100%; height: 100%;'></canvas>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mct-timeline-chart directive provides a canvas element which can be
|
||||||
|
* drawn upon, to support Plot view and similar visualizations.
|
||||||
|
*
|
||||||
|
* This directive takes one attribute, "draw", which is an Angular
|
||||||
|
* expression which will be two-way bound to a drawing object. This
|
||||||
|
* drawing object should contain:
|
||||||
|
*
|
||||||
|
* * `dimensions`: An object describing the logical bounds of the
|
||||||
|
* drawable area, containing two fields:
|
||||||
|
* * `origin`: The position, in logical coordinates, of the
|
||||||
|
* lower-left corner of the chart area. A two-element array.
|
||||||
|
* * `dimensions`: A two-element array containing the width
|
||||||
|
* and height of the chart area, in logical coordinates.
|
||||||
|
* * `lines`: An array of lines to be drawn, where each line is
|
||||||
|
* expressed as an object containing:
|
||||||
|
* * `buffer`: A Float32Array containing points in the line,
|
||||||
|
* in logical coordinate, in sequential x/y pairs.
|
||||||
|
* * `color`: The color of the line, as a four-element RGBA
|
||||||
|
* array, where each element is in the range of 0.0-1.0
|
||||||
|
* * `points`: The number of points in the line.
|
||||||
|
* * `boxes`: An array of rectangles to draw in the chart area
|
||||||
|
* (used for marquee zoom). Each is an object containing:
|
||||||
|
* * `start`: The first corner of the rectangle (as a two-element
|
||||||
|
* array, logical coordinates)
|
||||||
|
* * `end`: The opposite corner of the rectangle (again, as a
|
||||||
|
* two-element array)
|
||||||
|
* * `color`: The color of the box, as a four-element RGBA
|
||||||
|
* array, where each element is in the range of 0.0-1.0
|
||||||
|
*
|
||||||
|
* @memberof platform/features/plot
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MCTTimelineChart($interval, $log) {
|
||||||
|
// Get an underlying chart implementation
|
||||||
|
function getChart(Charts, canvas) {
|
||||||
|
// Try the first available option...
|
||||||
|
var Chart = Charts[0];
|
||||||
|
|
||||||
|
// This function recursively try-catches all options;
|
||||||
|
// if these all fail, issue a warning.
|
||||||
|
if (!Chart) {
|
||||||
|
$log.warn("Cannot initialize mct-timeline-chart.");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try first option; if it fails, try remaining options
|
||||||
|
try {
|
||||||
|
return new Chart(canvas);
|
||||||
|
} catch (e) {
|
||||||
|
$log.warn([
|
||||||
|
"Could not instantiate chart",
|
||||||
|
Chart.name,
|
||||||
|
";",
|
||||||
|
e.message
|
||||||
|
].join(" "));
|
||||||
|
|
||||||
|
return getChart(Charts.slice(1), canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkChart(scope, element) {
|
||||||
|
var canvas = element.find("canvas")[0],
|
||||||
|
activeInterval,
|
||||||
|
chart;
|
||||||
|
|
||||||
|
// Handle drawing, based on contents of the "draw" object
|
||||||
|
// in scope
|
||||||
|
function doDraw(draw) {
|
||||||
|
// Ensure canvas context has same resolution
|
||||||
|
// as canvas element
|
||||||
|
canvas.width = canvas.offsetWidth;
|
||||||
|
canvas.height = canvas.offsetHeight;
|
||||||
|
|
||||||
|
// Clear previous contents
|
||||||
|
chart.clear();
|
||||||
|
|
||||||
|
// Nothing to draw if no draw object defined
|
||||||
|
if (!draw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set logical boundaries for the chart
|
||||||
|
chart.setDimensions(
|
||||||
|
draw.dimensions || [1, 1],
|
||||||
|
draw.origin || [0, 0]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw line segments
|
||||||
|
(draw.lines || []).forEach(function (line) {
|
||||||
|
chart.drawLine(
|
||||||
|
line.buffer,
|
||||||
|
line.color,
|
||||||
|
line.points
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw boxes (e.g. marquee zoom rect)
|
||||||
|
(draw.boxes || []).forEach(function (box) {
|
||||||
|
chart.drawSquare(
|
||||||
|
box.start,
|
||||||
|
box.end,
|
||||||
|
box.color
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue a drawing call, if-and-only-if canvas size
|
||||||
|
// has changed. This will be called on a timer, since
|
||||||
|
// there is no event to depend on.
|
||||||
|
function drawIfResized() {
|
||||||
|
if (canvas.width !== canvas.offsetWidth ||
|
||||||
|
canvas.height !== canvas.offsetHeight) {
|
||||||
|
doDraw(scope.draw);
|
||||||
|
scope.$apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop watching for changes to size (scope destroyed)
|
||||||
|
function releaseInterval() {
|
||||||
|
if (activeInterval) {
|
||||||
|
$interval.cancel(activeInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch from WebGL to plain 2D if context is lost
|
||||||
|
function fallbackFromWebGL() {
|
||||||
|
element.html(TEMPLATE);
|
||||||
|
canvas = element.find("canvas")[0];
|
||||||
|
chart = getChart([Canvas2DChart], canvas);
|
||||||
|
if (chart) {
|
||||||
|
doDraw(scope.draw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to initialize a chart.
|
||||||
|
chart = getChart([GLChart, Canvas2DChart], canvas);
|
||||||
|
|
||||||
|
// If that failed, there's nothing more we can do here.
|
||||||
|
// (A warning will already have been issued)
|
||||||
|
if (!chart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebGL is a bit of a special case; it may work, then fail
|
||||||
|
// later for various reasons, so we need to listen for this
|
||||||
|
// and fall back to plain canvas drawing when it occurs.
|
||||||
|
canvas.addEventListener("webglcontextlost", fallbackFromWebGL);
|
||||||
|
|
||||||
|
// Check for resize, on a timer
|
||||||
|
activeInterval = $interval(drawIfResized, 1000, 0, false);
|
||||||
|
|
||||||
|
// Watch "draw" for external changes to the set of
|
||||||
|
// things to be drawn.
|
||||||
|
scope.$watchCollection("draw", doDraw);
|
||||||
|
|
||||||
|
// Stop checking for resize when scope is destroyed
|
||||||
|
scope.$on("$destroy", releaseInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Apply directive only to elements
|
||||||
|
restrict: "E",
|
||||||
|
|
||||||
|
// Template to use (a canvas element)
|
||||||
|
template: TEMPLATE,
|
||||||
|
|
||||||
|
// Link function; set up scope
|
||||||
|
link: linkChart,
|
||||||
|
|
||||||
|
// Initial, isolate scope for the directive
|
||||||
|
scope: { draw: "=" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface platform/features/plot.Chart
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the chart.
|
||||||
|
* @method platform/features/plot.Chart#clear
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Set the logical boundaries of the chart.
|
||||||
|
* @param {number[]} dimensions the horizontal and
|
||||||
|
* vertical dimensions of the chart
|
||||||
|
* @param {number[]} origin the horizontal/vertical
|
||||||
|
* origin of the chart
|
||||||
|
* @memberof platform/features/plot.Chart#setDimensions
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Draw the supplied buffer as a line strip (a sequence
|
||||||
|
* of line segments), in the chosen color.
|
||||||
|
* @param {Float32Array} buf the line strip to draw,
|
||||||
|
* in alternating x/y positions
|
||||||
|
* @param {number[]} color the color to use when drawing
|
||||||
|
* the line, as an RGBA color where each element
|
||||||
|
* is in the range of 0.0-1.0
|
||||||
|
* @param {number} points the number of points to draw
|
||||||
|
* @memberof platform/features/plot.Chart#drawLine
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Draw a rectangle extending from one corner to another,
|
||||||
|
* in the chosen color.
|
||||||
|
* @param {number[]} min the first corner of the rectangle
|
||||||
|
* @param {number[]} max the opposite corner
|
||||||
|
* @param {number[]} color the color to use when drawing
|
||||||
|
* the rectangle, as an RGBA color where each element
|
||||||
|
* is in the range of 0.0-1.0
|
||||||
|
* @memberof platform/features/plot.Chart#drawSquare
|
||||||
|
*/
|
||||||
|
|
||||||
|
return MCTTimelineChart;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -167,7 +167,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
setBounds: function (offset, duration) {
|
setBounds: function (offset, duration) {
|
||||||
// We don't update in-place, because we need the change
|
// We don't update in-place, because we need the change
|
||||||
// to trigger a watch in mct-chart.
|
// to trigger a watch in mct-timeline-chart.
|
||||||
drawingObject.origin = [offset, drawingObject.origin[1]];
|
drawingObject.origin = [offset, drawingObject.origin[1]];
|
||||||
drawingObject.dimensions = [duration, drawingObject.dimensions[1]];
|
drawingObject.dimensions = [duration, drawingObject.dimensions[1]];
|
||||||
},
|
},
|
||||||
|
@ -26,7 +26,7 @@ define(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for preparing data for display by
|
* Responsible for preparing data for display by
|
||||||
* `mct-chart` in a timeline's resource graph.
|
* `mct-timeline-chart` in a timeline's resource graph.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function TimelineGraphRenderer() {
|
function TimelineGraphRenderer() {
|
||||||
@ -54,7 +54,7 @@ define(
|
|||||||
* Convert an HTML color (in #-prefixed 6-digit hexadecimal)
|
* Convert an HTML color (in #-prefixed 6-digit hexadecimal)
|
||||||
* to an array of floating point values in a range of 0.0-1.0.
|
* to an array of floating point values in a range of 0.0-1.0.
|
||||||
* An alpha element is included to facilitate display in an
|
* An alpha element is included to facilitate display in an
|
||||||
* `mct-chart` (which uses WebGL.)
|
* `mct-timeline-chart` (which uses WebGL.)
|
||||||
* @param {string} the color
|
* @param {string} the color
|
||||||
* @returns {number[]} the same color, in floating-point format
|
* @returns {number[]} the same color, in floating-point format
|
||||||
*/
|
*/
|
||||||
|
95
platform/features/timeline/test/chart/Canvas2DChartSpec.js
Normal file
95
platform/features/timeline/test/chart/Canvas2DChartSpec.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../src/chart/Canvas2DChart"],
|
||||||
|
function (Canvas2DChart) {
|
||||||
|
|
||||||
|
describe("A canvas 2d chart", function () {
|
||||||
|
var mockCanvas,
|
||||||
|
mock2d,
|
||||||
|
chart;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockCanvas = jasmine.createSpyObj("canvas", ["getContext"]);
|
||||||
|
mock2d = jasmine.createSpyObj(
|
||||||
|
"2d",
|
||||||
|
[
|
||||||
|
"clearRect",
|
||||||
|
"beginPath",
|
||||||
|
"moveTo",
|
||||||
|
"lineTo",
|
||||||
|
"stroke",
|
||||||
|
"fillRect"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockCanvas.getContext.andReturn(mock2d);
|
||||||
|
|
||||||
|
chart = new Canvas2DChart(mockCanvas);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note that tests below are less specific than they
|
||||||
|
// could be, esp. w.r.t. arguments to drawing calls;
|
||||||
|
// this is a fallback option so is a lower test priority.
|
||||||
|
|
||||||
|
it("allows the canvas to be cleared", function () {
|
||||||
|
chart.clear();
|
||||||
|
expect(mock2d.clearRect).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not construct if 2D is unavailable", function () {
|
||||||
|
mockCanvas.getContext.andReturn(undefined);
|
||||||
|
expect(function () {
|
||||||
|
return new Canvas2DChart(mockCanvas);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows dimensions to be set", function () {
|
||||||
|
// No return value, just verify API is present
|
||||||
|
chart.setDimensions([120, 120], [0, 10]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows lines to be drawn", function () {
|
||||||
|
var testBuffer = [0, 1, 3, 8],
|
||||||
|
testColor = [0.25, 0.33, 0.66, 1.0],
|
||||||
|
testPoints = 2;
|
||||||
|
chart.drawLine(testBuffer, testColor, testPoints);
|
||||||
|
expect(mock2d.beginPath).toHaveBeenCalled();
|
||||||
|
expect(mock2d.lineTo.calls.length).toEqual(1);
|
||||||
|
expect(mock2d.stroke).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows squares to be drawn", function () {
|
||||||
|
var testMin = [0, 1],
|
||||||
|
testMax = [10, 10],
|
||||||
|
testColor = [0.25, 0.33, 0.66, 1.0];
|
||||||
|
|
||||||
|
chart.drawSquare(testMin, testMax, testColor);
|
||||||
|
expect(mock2d.fillRect).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
143
platform/features/timeline/test/chart/GLChartSpec.js
Normal file
143
platform/features/timeline/test/chart/GLChartSpec.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../src/chart/GLChart"],
|
||||||
|
function (GLChart) {
|
||||||
|
|
||||||
|
describe("A WebGL chart", function () {
|
||||||
|
var mockCanvas,
|
||||||
|
mockGL,
|
||||||
|
glChart;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockCanvas = jasmine.createSpyObj("canvas", ["getContext"]);
|
||||||
|
mockGL = jasmine.createSpyObj(
|
||||||
|
"gl",
|
||||||
|
[
|
||||||
|
"createShader",
|
||||||
|
"compileShader",
|
||||||
|
"shaderSource",
|
||||||
|
"attachShader",
|
||||||
|
"createProgram",
|
||||||
|
"linkProgram",
|
||||||
|
"useProgram",
|
||||||
|
"enableVertexAttribArray",
|
||||||
|
"getAttribLocation",
|
||||||
|
"getUniformLocation",
|
||||||
|
"createBuffer",
|
||||||
|
"lineWidth",
|
||||||
|
"enable",
|
||||||
|
"blendFunc",
|
||||||
|
"viewport",
|
||||||
|
"clear",
|
||||||
|
"uniform2fv",
|
||||||
|
"uniform4fv",
|
||||||
|
"bufferData",
|
||||||
|
"bindBuffer",
|
||||||
|
"vertexAttribPointer",
|
||||||
|
"drawArrays"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockGL.ARRAY_BUFFER = "ARRAY_BUFFER";
|
||||||
|
mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW";
|
||||||
|
mockGL.TRIANGLE_FAN = "TRIANGLE_FAN";
|
||||||
|
mockGL.LINE_STRIP = "LINE_STRIP";
|
||||||
|
|
||||||
|
// Echo back names for uniform locations, so we can
|
||||||
|
// test which of these are set for certain operations.
|
||||||
|
mockGL.getUniformLocation.andCallFake(function (a, name) {
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockCanvas.getContext.andReturn(mockGL);
|
||||||
|
|
||||||
|
glChart = new GLChart(mockCanvas);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the canvas to be cleared", function () {
|
||||||
|
glChart.clear();
|
||||||
|
expect(mockGL.clear).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not construct if WebGL is unavailable", function () {
|
||||||
|
mockCanvas.getContext.andReturn(undefined);
|
||||||
|
expect(function () {
|
||||||
|
return new GLChart(mockCanvas);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows dimensions to be set", function () {
|
||||||
|
glChart.setDimensions([120, 120], [0, 10]);
|
||||||
|
expect(mockGL.uniform2fv)
|
||||||
|
.toHaveBeenCalledWith("uDimensions", [120, 120]);
|
||||||
|
expect(mockGL.uniform2fv)
|
||||||
|
.toHaveBeenCalledWith("uOrigin", [0, 10]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows lines to be drawn", function () {
|
||||||
|
var testBuffer = [0, 1, 3, 8],
|
||||||
|
testColor = [0.25, 0.33, 0.66, 1.0],
|
||||||
|
testPoints = 2;
|
||||||
|
glChart.drawLine(testBuffer, testColor, testPoints);
|
||||||
|
expect(mockGL.bufferData).toHaveBeenCalledWith(
|
||||||
|
mockGL.ARRAY_BUFFER,
|
||||||
|
testBuffer,
|
||||||
|
mockGL.DYNAMIC_DRAW
|
||||||
|
);
|
||||||
|
expect(mockGL.uniform4fv)
|
||||||
|
.toHaveBeenCalledWith("uColor", testColor);
|
||||||
|
expect(mockGL.drawArrays)
|
||||||
|
.toHaveBeenCalledWith("LINE_STRIP", 0, testPoints);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows squares to be drawn", function () {
|
||||||
|
var testMin = [0, 1],
|
||||||
|
testMax = [10, 10],
|
||||||
|
testColor = [0.25, 0.33, 0.66, 1.0];
|
||||||
|
|
||||||
|
glChart.drawSquare(testMin, testMax, testColor);
|
||||||
|
|
||||||
|
expect(mockGL.uniform4fv)
|
||||||
|
.toHaveBeenCalledWith("uColor", testColor);
|
||||||
|
expect(mockGL.drawArrays)
|
||||||
|
.toHaveBeenCalledWith("TRIANGLE_FAN", 0, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses buffer sizes reported by WebGL", function () {
|
||||||
|
// Make sure that GLChart uses the GL buffer size, which may
|
||||||
|
// differ from what canvas requested. WTD-852
|
||||||
|
mockCanvas.width = 300;
|
||||||
|
mockCanvas.height = 150;
|
||||||
|
mockGL.drawingBufferWidth = 200;
|
||||||
|
mockGL.drawingBufferHeight = 175;
|
||||||
|
|
||||||
|
glChart.clear();
|
||||||
|
|
||||||
|
expect(mockGL.viewport).toHaveBeenCalledWith(0, 0, 200, 175);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
216
platform/features/timeline/test/chart/MCTTimelineChartSpec.js
Normal file
216
platform/features/timeline/test/chart/MCTTimelineChartSpec.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT 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 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCTTimelineChart. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../src/chart/MCTTimelineChart"],
|
||||||
|
function (MCTTimelineChart) {
|
||||||
|
|
||||||
|
describe("The mct-timeline-chart directive", function () {
|
||||||
|
var mockInterval,
|
||||||
|
mockLog,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
mockCanvas,
|
||||||
|
mockGL,
|
||||||
|
mockC2d,
|
||||||
|
mockPromise,
|
||||||
|
mctChart;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockInterval =
|
||||||
|
jasmine.createSpy("$interval");
|
||||||
|
mockLog =
|
||||||
|
jasmine.createSpyObj("$log", ["warn", "info", "debug"]);
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
"$scope",
|
||||||
|
["$watchCollection", "$on", "$apply"]
|
||||||
|
);
|
||||||
|
mockElement =
|
||||||
|
jasmine.createSpyObj("element", ["find", "html"]);
|
||||||
|
mockInterval.cancel = jasmine.createSpy("cancelInterval");
|
||||||
|
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
||||||
|
|
||||||
|
|
||||||
|
// mct-timeline-chart uses GLChart, so it needs WebGL API
|
||||||
|
mockCanvas =
|
||||||
|
jasmine.createSpyObj("canvas", ["getContext", "addEventListener"]);
|
||||||
|
mockGL = jasmine.createSpyObj(
|
||||||
|
"gl",
|
||||||
|
[
|
||||||
|
"createShader",
|
||||||
|
"compileShader",
|
||||||
|
"shaderSource",
|
||||||
|
"attachShader",
|
||||||
|
"createProgram",
|
||||||
|
"linkProgram",
|
||||||
|
"useProgram",
|
||||||
|
"enableVertexAttribArray",
|
||||||
|
"getAttribLocation",
|
||||||
|
"getUniformLocation",
|
||||||
|
"createBuffer",
|
||||||
|
"lineWidth",
|
||||||
|
"enable",
|
||||||
|
"blendFunc",
|
||||||
|
"viewport",
|
||||||
|
"clear",
|
||||||
|
"uniform2fv",
|
||||||
|
"uniform4fv",
|
||||||
|
"bufferData",
|
||||||
|
"bindBuffer",
|
||||||
|
"vertexAttribPointer",
|
||||||
|
"drawArrays"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockC2d = jasmine.createSpyObj('c2d', ['clearRect']);
|
||||||
|
mockGL.ARRAY_BUFFER = "ARRAY_BUFFER";
|
||||||
|
mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW";
|
||||||
|
mockGL.TRIANGLE_FAN = "TRIANGLE_FAN";
|
||||||
|
mockGL.LINE_STRIP = "LINE_STRIP";
|
||||||
|
|
||||||
|
// Echo back names for uniform locations, so we can
|
||||||
|
// test which of these are set for certain operations.
|
||||||
|
mockGL.getUniformLocation.andCallFake(function (a, name) {
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockElement.find.andReturn([mockCanvas]);
|
||||||
|
mockCanvas.getContext.andCallFake(function (type) {
|
||||||
|
return { webgl: mockGL, '2d': mockC2d }[type];
|
||||||
|
});
|
||||||
|
mockInterval.andReturn(mockPromise);
|
||||||
|
|
||||||
|
mctChart = new MCTTimelineChart(mockInterval, mockLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is applicable at the element level", function () {
|
||||||
|
expect(mctChart.restrict).toEqual("E");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("places a 'draw' attribute in-scope", function () {
|
||||||
|
// Should ask Angular for the draw attribute
|
||||||
|
expect(mctChart.scope.draw).toEqual("=");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches for changes in the drawn object", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
expect(mockScope.$watchCollection)
|
||||||
|
.toHaveBeenCalledWith("draw", jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("issues one draw call per line", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
mockScope.$watchCollection.mostRecentCall.args[1]({
|
||||||
|
lines: [{}, {}, {}]
|
||||||
|
});
|
||||||
|
expect(mockGL.drawArrays.calls.length).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("issues one draw call per box", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
mockScope.$watchCollection.mostRecentCall.args[1]({
|
||||||
|
boxes: [
|
||||||
|
{ start: [0, 0], end: [1, 1] },
|
||||||
|
{ start: [0, 0], end: [1, 1] },
|
||||||
|
{ start: [0, 0], end: [1, 1] },
|
||||||
|
{ start: [0, 0], end: [1, 1] }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
expect(mockGL.drawArrays.calls.length).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not fail if no draw object is in scope", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
expect(mockScope.$watchCollection.mostRecentCall.args[1])
|
||||||
|
.not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("draws on canvas resize", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
|
||||||
|
// Should track canvas size in an interval
|
||||||
|
expect(mockInterval).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(Function),
|
||||||
|
jasmine.any(Number),
|
||||||
|
0,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify pre-condition
|
||||||
|
expect(mockGL.clear).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
mockCanvas.width = 100;
|
||||||
|
mockCanvas.offsetWidth = 150;
|
||||||
|
mockCanvas.height = 200;
|
||||||
|
mockCanvas.offsetHeight = 200;
|
||||||
|
mockInterval.mostRecentCall.args[0]();
|
||||||
|
|
||||||
|
// Use clear as an indication that drawing has occurred
|
||||||
|
expect(mockGL.clear).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("warns if no WebGL context is available", function () {
|
||||||
|
mockCanvas.getContext.andReturn(undefined);
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
expect(mockLog.warn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to Canvas 2d API if WebGL context is lost", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
expect(mockCanvas.addEventListener)
|
||||||
|
.toHaveBeenCalledWith("webglcontextlost", jasmine.any(Function));
|
||||||
|
expect(mockCanvas.getContext).not.toHaveBeenCalledWith('2d');
|
||||||
|
mockCanvas.addEventListener.mostRecentCall.args[1]();
|
||||||
|
expect(mockCanvas.getContext).toHaveBeenCalledWith('2d');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs nothing in nominal situations (WebGL available)", function () {
|
||||||
|
// Complement the previous test
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
expect(mockLog.warn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Avoid resource leaks
|
||||||
|
it("stops polling for size changes on destroy", function () {
|
||||||
|
mctChart.link(mockScope, mockElement);
|
||||||
|
|
||||||
|
// Should be listening for a destroy event
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||||
|
"$destroy",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Precondition - interval still active
|
||||||
|
expect(mockInterval.cancel).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Broadcast a $destroy
|
||||||
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// Should have stopped the interval
|
||||||
|
expect(mockInterval.cancel).toHaveBeenCalledWith(mockPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user