diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md
index c4d52c9501..4e8684fc32 100644
--- a/docs/src/guide/index.md
+++ b/docs/src/guide/index.md
@@ -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
diff --git a/platform/features/plot/README.md b/platform/features/plot/README.md
new file mode 100644
index 0000000000..a4a6537fe1
--- /dev/null
+++ b/platform/features/plot/README.md
@@ -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.
+
diff --git a/platform/features/timeline/bundle.js b/platform/features/timeline/bundle.js
index 42b3c948f9..6ec0e2006b 100644
--- a/platform/features/timeline/bundle.js
+++ b/platform/features/timeline/bundle.js
@@ -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": [
diff --git a/platform/features/timeline/res/templates/resource-graphs.html b/platform/features/timeline/res/templates/resource-graphs.html
index 31a139ea16..51d2f6a053 100644
--- a/platform/features/timeline/res/templates/resource-graphs.html
+++ b/platform/features/timeline/res/templates/resource-graphs.html
@@ -22,7 +22,7 @@
-
\ No newline at end of file
+
diff --git a/platform/features/timeline/src/chart/Canvas2DChart.js b/platform/features/timeline/src/chart/Canvas2DChart.js
new file mode 100644
index 0000000000..e4a8c7fe43
--- /dev/null
+++ b/platform/features/timeline/src/chart/Canvas2DChart.js
@@ -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;
+ }
+);
diff --git a/platform/features/timeline/src/chart/GLChart.js b/platform/features/timeline/src/chart/GLChart.js
new file mode 100644
index 0000000000..0ca7776171
--- /dev/null
+++ b/platform/features/timeline/src/chart/GLChart.js
@@ -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;
+ }
+);
diff --git a/platform/features/timeline/src/chart/MCTTimelineChart.js b/platform/features/timeline/src/chart/MCTTimelineChart.js
new file mode 100644
index 0000000000..67bd0b4a6d
--- /dev/null
+++ b/platform/features/timeline/src/chart/MCTTimelineChart.js
@@ -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 = "";
+
+ /**
+ * 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;
+ }
+);
+
diff --git a/platform/features/timeline/src/controllers/graph/TimelineGraph.js b/platform/features/timeline/src/controllers/graph/TimelineGraph.js
index 17c36d55ac..62ffa68986 100644
--- a/platform/features/timeline/src/controllers/graph/TimelineGraph.js
+++ b/platform/features/timeline/src/controllers/graph/TimelineGraph.js
@@ -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]];
},
diff --git a/platform/features/timeline/src/controllers/graph/TimelineGraphRenderer.js b/platform/features/timeline/src/controllers/graph/TimelineGraphRenderer.js
index fc4d60c34c..f826352c6f 100644
--- a/platform/features/timeline/src/controllers/graph/TimelineGraphRenderer.js
+++ b/platform/features/timeline/src/controllers/graph/TimelineGraphRenderer.js
@@ -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
*/
diff --git a/platform/features/timeline/test/chart/Canvas2DChartSpec.js b/platform/features/timeline/test/chart/Canvas2DChartSpec.js
new file mode 100644
index 0000000000..aef0e07131
--- /dev/null
+++ b/platform/features/timeline/test/chart/Canvas2DChartSpec.js
@@ -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();
+ });
+
+ });
+ }
+);
diff --git a/platform/features/timeline/test/chart/GLChartSpec.js b/platform/features/timeline/test/chart/GLChartSpec.js
new file mode 100644
index 0000000000..f3cbd5b763
--- /dev/null
+++ b/platform/features/timeline/test/chart/GLChartSpec.js
@@ -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);
+ });
+ });
+ }
+);
diff --git a/platform/features/timeline/test/chart/MCTTimelineChartSpec.js b/platform/features/timeline/test/chart/MCTTimelineChartSpec.js
new file mode 100644
index 0000000000..f3e950d1f5
--- /dev/null
+++ b/platform/features/timeline/test/chart/MCTTimelineChartSpec.js
@@ -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);
+ });
+
+ });
+ }
+);