-
- {{tick.label | reverse}}
-
-
- {{axes[0].active.name}}
-
-
diff --git a/platform/features/plot/src/GLChart.js b/platform/features/plot/src/GLChart.js
index 56ca29bb3e..0ca7776171 100644
--- a/platform/features/plot/src/GLChart.js
+++ b/platform/features/plot/src/GLChart.js
@@ -54,7 +54,8 @@ define(
* @throws {Error} an error is thrown if WebGL is unavailable.
*/
function GLChart(canvas) {
- var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"),
+ var gl = canvas.getContext("webgl", { preserveDrawingBuffer: true }) ||
+ canvas.getContext("experimental-webgl", { preserveDrawingBuffer: true }),
vertexShader,
fragmentShader,
program,
diff --git a/platform/features/plot/src/PlotController.js b/platform/features/plot/src/PlotController.js
index 88d82ea31b..3b23bce34a 100644
--- a/platform/features/plot/src/PlotController.js
+++ b/platform/features/plot/src/PlotController.js
@@ -63,6 +63,8 @@ define(
*/
function PlotController(
$scope,
+ $element,
+ exportImageService,
telemetryFormatter,
telemetryHandler,
throttle,
@@ -246,6 +248,8 @@ define(
});
self.pending = true;
+ self.$element = $element;
+ self.exportImageService = exportImageService;
// Initialize axes; will get repopulated when telemetry
// metadata becomes available.
@@ -364,6 +368,39 @@ define(
return this.pending;
};
+ /**
+ * Export the plot to PDF
+ */
+ PlotController.prototype.exportPDF = function () {
+ var self = this;
+ self.hideExportButtons = true;
+ self.exportImageService.exportPDF(self.$element[0], "plot.pdf").finally(function () {
+ self.hideExportButtons = false;
+ });
+ };
+
+ /**
+ * Export the plot to PNG
+ */
+ PlotController.prototype.exportPNG = function () {
+ var self = this;
+ self.hideExportButtons = true;
+ self.exportImageService.exportPNG(self.$element[0], "plot.png").finally(function () {
+ self.hideExportButtons = false;
+ });
+ };
+
+ /**
+ * Export the plot to JPG
+ */
+ PlotController.prototype.exportJPG = function () {
+ var self = this;
+ self.hideExportButtons = true;
+ self.exportImageService.exportJPG(self.$element[0], "plot.jpg").finally(function () {
+ self.hideExportButtons = false;
+ });
+ };
+
return PlotController;
}
);
diff --git a/platform/features/plot/src/services/ExportImageService.js b/platform/features/plot/src/services/ExportImageService.js
new file mode 100644
index 0000000000..d49adc15ec
--- /dev/null
+++ b/platform/features/plot/src/services/ExportImageService.js
@@ -0,0 +1,176 @@
+/*****************************************************************************
+ * 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 ExportImageService. Created by hudsonfoo on 09/02/16
+ */
+define(
+ [
+ "html2canvas",
+ "jsPDF",
+ "saveAs"
+ ],
+ function (
+ html2canvas,
+ jsPDF,
+ saveAs
+ ) {
+ var self = this;
+
+ /**
+ * The export image service will export any HTML node to
+ * PDF, JPG, or PNG.
+ * @param {object} $q
+ * @param {object} $timeout
+ * @param {object} $log
+ * @param {constant} EXPORT_IMAGE_TIMEOUT time in milliseconds before a timeout error is returned
+ * @constructor
+ */
+ function ExportImageService($q, $timeout, $log, EXPORT_IMAGE_TIMEOUT, injHtml2Canvas, injJsPDF, injSaveAs, injFileReader) {
+ self.$q = $q;
+ self.$timeout = $timeout;
+ self.$log = $log;
+ self.EXPORT_IMAGE_TIMEOUT = EXPORT_IMAGE_TIMEOUT;
+ self.html2canvas = injHtml2Canvas || html2canvas;
+ self.jsPDF = injJsPDF || jsPDF;
+ self.saveAs = injSaveAs || saveAs;
+ self.reader = injFileReader || new FileReader();
+ }
+
+ /**
+ * Renders an HTML element into a base64 encoded image
+ * as a BLOB, PNG, or JPG.
+ * @param {node} element that will be converted to an image
+ * @param {string} type of image to convert the element to
+ * @returns {promise}
+ */
+ function renderElement(element, type) {
+ var defer = self.$q.defer(),
+ validTypes = ["png", "jpg", "jpeg"],
+ renderTimeout;
+
+ if (validTypes.indexOf(type) === -1) {
+ self.$log.error("Invalid type requested. Try: (" + validTypes.join(",") + ")");
+ return;
+ }
+
+ renderTimeout = self.$timeout(function () {
+ defer.reject("html2canvas timed out");
+ self.$log.warn("html2canvas timed out");
+ }, self.EXPORT_IMAGE_TIMEOUT);
+
+ try {
+ self.html2canvas(element, {
+ onrendered: function (canvas) {
+ switch (type.toLowerCase()) {
+ case "png":
+ canvas.toBlob(defer.resolve, "image/png");
+ break;
+
+ default:
+ case "jpg":
+ case "jpeg":
+ canvas.toBlob(defer.resolve, "image/jpeg");
+ break;
+ }
+ }
+ });
+ } catch (e) {
+ defer.reject(e);
+ self.$log.warn("html2canvas failed with error: " + e);
+ }
+
+ defer.promise.finally(renderTimeout.cancel);
+
+ return defer.promise;
+ }
+
+ /**
+ * canvas.toBlob() not supported in IE < 10, Opera, and Safari. This polyfill
+ * implements the method in browsers that would not otherwise support it.
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
+ */
+ function polyfillToBlob() {
+ if (!HTMLCanvasElement.prototype.toBlob) {
+ Object.defineProperty(HTMLCanvasElement.prototype, "toBlob", {
+ value: function (callback, type, quality) {
+
+ var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
+ len = binStr.length,
+ arr = new Uint8Array(len);
+
+ for (var i = 0; i < len; i++) {
+ arr[i] = binStr.charCodeAt(i);
+ }
+
+ callback(new Blob([arr], {type: type || "image/png"}));
+ }
+ });
+ }
+ }
+
+ /**
+ * Takes a screenshot of a DOM node and exports to PDF.
+ * @param {node} element to be exported
+ * @param {string} filename the exported image
+ * @returns {promise}
+ */
+ ExportImageService.prototype.exportPDF = function (element, filename) {
+ return renderElement(element, "jpeg").then(function (img) {
+ self.reader.readAsDataURL(img);
+ self.reader.onloadend = function () {
+ var pdf = new self.jsPDF("l", "px", [element.offsetHeight, element.offsetWidth]);
+ pdf.addImage(self.reader.result, "JPEG", 0, 0, element.offsetWidth, element.offsetHeight);
+ pdf.save(filename);
+ };
+ });
+ };
+
+ /**
+ * Takes a screenshot of a DOM node and exports to JPG.
+ * @param {node} element to be exported
+ * @param {string} filename the exported image
+ * @returns {promise}
+ */
+ ExportImageService.prototype.exportJPG = function (element, filename) {
+ return renderElement(element, "jpeg").then(function (img) {
+ self.saveAs(img, filename);
+ });
+ };
+
+ /**
+ * Takes a screenshot of a DOM node and exports to PNG.
+ * @param {node} element to be exported
+ * @param {string} filename the exported image
+ * @returns {promise}
+ */
+ ExportImageService.prototype.exportPNG = function (element, filename) {
+ return renderElement(element, "png").then(function (img) {
+ self.saveAs(img, filename);
+ });
+ };
+
+ polyfillToBlob();
+
+ return ExportImageService;
+ }
+);
diff --git a/platform/features/plot/test/PlotControllerSpec.js b/platform/features/plot/test/PlotControllerSpec.js
index a3de710bdf..73a01d716a 100644
--- a/platform/features/plot/test/PlotControllerSpec.js
+++ b/platform/features/plot/test/PlotControllerSpec.js
@@ -1,3 +1,5 @@
+/*global angular*/
+
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@@ -29,6 +31,8 @@ define(
describe("The plot controller", function () {
var mockScope,
+ mockElement,
+ mockExportImageService,
mockFormatter,
mockHandler,
mockThrottle,
@@ -65,6 +69,11 @@ define(
"$scope",
["$watch", "$on", "$emit"]
);
+ mockElement = angular.element('
');
+ mockExportImageService = jasmine.createSpyObj(
+ "ExportImageService",
+ ["exportJPG", "exportPNG", "exportPDF"]
+ );
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
@@ -107,6 +116,8 @@ define(
controller = new PlotController(
mockScope,
+ mockElement,
+ mockExportImageService,
mockFormatter,
mockHandler,
mockThrottle
diff --git a/platform/features/plot/test/services/ExportImageServiceSpec.js b/platform/features/plot/test/services/ExportImageServiceSpec.js
new file mode 100644
index 0000000000..111a8d3432
--- /dev/null
+++ b/platform/features/plot/test/services/ExportImageServiceSpec.js
@@ -0,0 +1,141 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/**
+ * ExportImageServiceSpec. Created by hudsonfoo on 09/03/16.
+ */
+define(
+ ["../../src/services/ExportImageService"],
+ function (ExportImageService) {
+ var mockQ,
+ mockDeferred,
+ mockPromise,
+ mockTimeout,
+ mockLog,
+ mockHtml2Canvas,
+ mockCanvas,
+ mockJsPDF,
+ mockJsPDFSave,
+ mockSaveAs,
+ mockFileReader,
+ mockExportTimeoutConstant,
+ testElement,
+ exportImageService;
+
+ describe("ExportImageService", function () {
+ beforeEach(function () {
+ mockDeferred = jasmine.createSpyObj(
+ "deferred",
+ ["reject", "resolve"]
+ );
+ mockPromise = jasmine.createSpyObj(
+ "promise",
+ ["then", "finally"]
+ );
+ mockPromise.then = function (callback) {
+ callback();
+ };
+ mockQ = {
+ "defer": function () {
+ return {
+ "resolve": mockDeferred.resolve,
+ "reject": mockDeferred.reject,
+ "promise": mockPromise
+ };
+ }
+ };
+ mockTimeout = function (fn, time) {
+ return {
+ "cancel": function () {}
+ };
+ };
+ mockLog = jasmine.createSpyObj(
+ "$log",
+ ["warn"]
+ );
+ mockHtml2Canvas = jasmine.createSpy("html2canvas").andCallFake(function (element, opts) {
+ opts.onrendered(mockCanvas);
+ });
+ mockCanvas = jasmine.createSpyObj(
+ "canvas",
+ ["toBlob"]
+ );
+ mockJsPDFSave = jasmine.createSpy("jsPDFSave");
+ mockJsPDF = function () {
+ return {
+ "addImage": function () {},
+ "save": mockJsPDFSave
+ };
+ };
+ mockSaveAs = jasmine.createSpy("saveAs");
+ mockFileReader = jasmine.createSpyObj(
+ "FileReader",
+ ["readAsDataURL", "onloadend"]
+ );
+ mockExportTimeoutConstant = 0;
+ testElement = {};
+
+ exportImageService = new ExportImageService(
+ mockQ,
+ mockTimeout,
+ mockLog,
+ mockExportTimeoutConstant,
+ mockHtml2Canvas,
+ mockJsPDF,
+ mockSaveAs,
+ mockFileReader
+ );
+ });
+
+ it("runs html2canvas and tries to save a pdf", function () {
+ exportImageService.exportPDF(testElement, "plot.pdf");
+ mockFileReader.onloadend();
+
+ expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
+ expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg");
+ expect(mockDeferred.reject).not.toHaveBeenCalled();
+ expect(mockJsPDFSave).toHaveBeenCalled();
+ expect(mockPromise.finally).toHaveBeenCalled();
+ });
+
+ it("runs html2canvas and tries to save a png", function () {
+ exportImageService.exportPNG(testElement, "plot.png");
+
+ expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
+ expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/png");
+ expect(mockDeferred.reject).not.toHaveBeenCalled();
+ expect(mockSaveAs).toHaveBeenCalled();
+ expect(mockPromise.finally).toHaveBeenCalled();
+ });
+
+ it("runs html2canvas and tries to save a jpg", function () {
+ exportImageService.exportJPG(testElement, "plot.png");
+
+ expect(mockHtml2Canvas).toHaveBeenCalledWith(testElement, { onrendered: jasmine.any(Function) });
+ expect(mockCanvas.toBlob).toHaveBeenCalledWith(mockDeferred.resolve, "image/jpeg");
+ expect(mockDeferred.reject).not.toHaveBeenCalled();
+ expect(mockSaveAs).toHaveBeenCalled();
+ expect(mockPromise.finally).toHaveBeenCalled();
+ });
+ });
+ }
+);
diff --git a/platform/features/table/res/templates/historical-table.html b/platform/features/table/res/templates/historical-table.html
index e2cd91b0db..d306601daf 100644
--- a/platform/features/table/res/templates/historical-table.html
+++ b/platform/features/table/res/templates/historical-table.html
@@ -4,6 +4,6 @@
rows="rows"
enableFilter="true"
enableSort="true"
- class="tabular-holder t-exportable">
+ class="tabular-holder has-control-bar">
\ No newline at end of file
diff --git a/platform/features/table/res/templates/mct-table.html b/platform/features/table/res/templates/mct-table.html
index 6d96b17afd..ac06f53e28 100644
--- a/platform/features/table/res/templates/mct-table.html
+++ b/platform/features/table/res/templates/mct-table.html
@@ -1,8 +1,10 @@
-