Merge pull request #1406 from nasa/separate-timeline-and-plot

[Reorg] Make timeline-specific chart directive
This commit is contained in:
Andrew Henry 2017-01-26 11:34:05 -08:00 committed by GitHub
commit 4f24c46e9b
12 changed files with 1033 additions and 40 deletions

View File

@ -1339,41 +1339,6 @@ are supported:
Open MCT defines several Angular directives that are intended for use both
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
The `mct-container` is similar to the `mct-include` directive insofar as it allows

View 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.

View File

@ -38,6 +38,7 @@ define([
"./src/directives/MCTSwimlaneDrop",
"./src/directives/MCTSwimlaneDrag",
"./src/services/ObjectLoader",
"./src/chart/MCTTimelineChart",
"text!./res/templates/values.html",
"text!./res/templates/timeline.html",
"text!./res/templates/activity-gantt.html",
@ -67,6 +68,7 @@ define([
MCTSwimlaneDrop,
MCTSwimlaneDrag,
ObjectLoader,
MCTTimelineChart,
valuesTemplate,
timelineTemplate,
activityGanttTemplate,
@ -556,6 +558,14 @@ define([
"depends": [
"dndService"
]
},
{
"key": "mctTimelineChart",
"implementation": MCTTimelineChart,
"depends": [
"$interval",
"$log"
]
}
],
"services": [

View File

@ -22,7 +22,7 @@
<span ng-controller="TimelineGraphController as graphController">
<div class="t-graph l-graph" ng-repeat="graph in parameters.graphs">
<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 class="t-graph-labels l-graph-labels">
<mct-include key="'timeline-resource-graph-labels'"
@ -31,4 +31,4 @@
</mct-include>
</div>
</div>
</span>
</span>

View 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;
}
);

View 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;
}
);

View 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;
}
);

View File

@ -167,7 +167,7 @@ define(
*/
setBounds: function (offset, duration) {
// 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.dimensions = [duration, drawingObject.dimensions[1]];
},

View File

@ -26,7 +26,7 @@ define(
/**
* 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
*/
function TimelineGraphRenderer() {
@ -54,7 +54,7 @@ define(
* Convert an HTML color (in #-prefixed 6-digit hexadecimal)
* 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
* `mct-chart` (which uses WebGL.)
* `mct-timeline-chart` (which uses WebGL.)
* @param {string} the color
* @returns {number[]} the same color, in floating-point format
*/

View 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();
});
});
}
);

View 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);
});
});
}
);

View 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);
});
});
}
);