diff --git a/src/plugins/plot/src/chart/MCTChartController.js b/src/plugins/plot/src/chart/MCTChartController.js index 03e4b69a06..2fc73a8b59 100644 --- a/src/plugins/plot/src/chart/MCTChartController.js +++ b/src/plugins/plot/src/chart/MCTChartController.js @@ -197,9 +197,29 @@ function ( this.canvas = canvas; this.overlay = overlay; this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay); + if (this.drawAPI) { + this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this); + } return !!this.drawAPI; }; + MCTChartController.prototype.fallbackToCanvas = function () { + this.stopListening(this.drawAPI); + DrawLoader.releaseDrawAPI(this.drawAPI); + // Have to throw away the old canvas elements and replace with new + // canvas elements in order to get new drawing contexts. + var div = document.createElement('div'); + div.innerHTML = this.TEMPLATE; + var mainCanvas = div.querySelectorAll("canvas")[1]; + var overlayCanvas = div.querySelectorAll("canvas")[0]; + this.canvas.parentNode.replaceChild(mainCanvas, this.canvas); + this.canvas = mainCanvas; + this.overlay.parentNode.replaceChild(overlayCanvas, this.overlay); + this.overlay = overlayCanvas; + this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay); + this.$scope.$emit('plot:reinitializeCanvas'); + }; + MCTChartController.prototype.removeChartElement = function (series) { var elements = this.seriesElements.get(series); diff --git a/src/plugins/plot/src/chart/MCTChartDirective.js b/src/plugins/plot/src/chart/MCTChartDirective.js index 5662465878..3f3574dc42 100644 --- a/src/plugins/plot/src/chart/MCTChartDirective.js +++ b/src/plugins/plot/src/chart/MCTChartDirective.js @@ -43,6 +43,7 @@ define([ restrict: "E", template: TEMPLATE, link: function ($scope, $element, attrs, ctrl) { + ctrl.TEMPLATE = TEMPLATE; var mainCanvas = $element.find("canvas")[1]; var overlayCanvas = $element.find("canvas")[0]; diff --git a/src/plugins/plot/src/draw/Draw2D.js b/src/plugins/plot/src/draw/Draw2D.js index a333529062..e5141345b3 100644 --- a/src/plugins/plot/src/draw/Draw2D.js +++ b/src/plugins/plot/src/draw/Draw2D.js @@ -22,9 +22,13 @@ define([ - + 'lodash', + 'EventEmitter', + '../lib/eventHelpers' ], function ( - + _, + EventEmitter, + eventHelpers ) { /** @@ -47,6 +51,9 @@ define([ } } + _.extend(Draw2D.prototype, EventEmitter.prototype); + eventHelpers.extend(Draw2D.prototype); + // Convert from logical to physical x coordinates Draw2D.prototype.x = function (v) { return ((v - this.origin[0]) / this.dimensions[0]) * this.width; diff --git a/src/plugins/plot/src/draw/DrawLoader.js b/src/plugins/plot/src/draw/DrawLoader.js index c4eb4e1fe6..bb1c3f61a1 100644 --- a/src/plugins/plot/src/draw/DrawLoader.js +++ b/src/plugins/plot/src/draw/DrawLoader.js @@ -35,7 +35,7 @@ define( ALLOCATIONS: [] }, { - MAX_INSTANCES: Number.MAX_INFINITY, + MAX_INSTANCES: Number.POSITIVE_INFINITY, API: Draw2D, ALLOCATIONS: [] } @@ -83,12 +83,24 @@ define( return api; }, + /** + * Returns a fallback draw api. + */ + getFallbackDrawAPI: function (canvas, overlay) { + var api = new CHARTS[1].API(canvas, overlay); + CHARTS[1].ALLOCATIONS.push(api); + return api; + }, + releaseDrawAPI: function (api) { CHARTS.forEach(function (CHART_TYPE) { if (api instanceof CHART_TYPE.API) { CHART_TYPE.ALLOCATIONS.splice(CHART_TYPE.ALLOCATIONS.indexOf(api), 1); } }); + if (api.destroy) { + api.destroy(); + } } }; } diff --git a/src/plugins/plot/src/draw/DrawWebGL.js b/src/plugins/plot/src/draw/DrawWebGL.js index 1e44a60a6a..2e145b0665 100644 --- a/src/plugins/plot/src/draw/DrawWebGL.js +++ b/src/plugins/plot/src/draw/DrawWebGL.js @@ -22,9 +22,13 @@ define([ - + 'lodash', + 'EventEmitter', + '../lib/eventHelpers' ], function ( - + _, + EventEmitter, + eventHelpers ) { // WebGL shader sources (for drawing plain colors) @@ -69,6 +73,21 @@ define([ throw new Error("WebGL unavailable."); } + this.initContext(); + + this.listenTo(this.canvas, "webglcontextlost", this.onContextLost, this); + } + + _.extend(DrawWebGL.prototype, EventEmitter.prototype); + eventHelpers.extend(DrawWebGL.prototype); + + DrawWebGL.prototype.onContextLost = function (event) { + this.emit('error'); + this.isContextLost = true; + this.destroy(); + }; + + DrawWebGL.prototype.initContext = function () { // Initialize shaders this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER); this.gl.shaderSource(this.vertexShader, VERTEX_SHADER); @@ -103,7 +122,12 @@ define([ // Enable blending, for smoothness this.gl.enable(this.gl.BLEND); this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); - } + + }; + + DrawWebGL.prototype.destroy = function () { + this.stopListening(); + }; // Convert from logical to physical x coordinates DrawWebGL.prototype.x = function (v) { @@ -117,6 +141,9 @@ define([ }; DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) { + if (this.isContextLost) { + return; + } this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer); this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW); this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0); @@ -125,6 +152,9 @@ define([ }; DrawWebGL.prototype.clear = function () { + if (this.isContextLost) { + return; + } this.height = this.canvas.height = this.canvas.offsetHeight; this.width = this.canvas.width = this.canvas.offsetWidth; this.overlay.height = this.overlay.offsetHeight; @@ -151,6 +181,9 @@ define([ DrawWebGL.prototype.setDimensions = function (dimensions, origin) { this.dimensions = dimensions; this.origin = origin; + if (this.isContextLost) { + return; + } if (dimensions && dimensions.length > 0 && origin && origin.length > 0) { this.gl.uniform2fv(this.uDimensions, dimensions); @@ -169,6 +202,9 @@ define([ * @param {number} points the number of points to draw */ DrawWebGL.prototype.drawLine = function (buf, color, points) { + if (this.isContextLost) { + return; + } this.doDraw(this.gl.LINE_STRIP, buf, color, points); }; @@ -177,6 +213,9 @@ define([ * */ DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) { + if (this.isContextLost) { + return; + } this.gl.uniform1f(this.uPointSize, pointSize); this.doDraw(this.gl.POINTS, buf, color, points); }; @@ -191,6 +230,9 @@ define([ * is in the range of 0.0-1.0 */ DrawWebGL.prototype.drawSquare = function (min, max, color) { + if (this.isContextLost) { + return; + } this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array( min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]]) ), color, 4); diff --git a/src/plugins/plot/src/plot/MCTPlotController.js b/src/plugins/plot/src/plot/MCTPlotController.js index 27268c62f2..3ce1b91cca 100644 --- a/src/plugins/plot/src/plot/MCTPlotController.js +++ b/src/plugins/plot/src/plot/MCTPlotController.js @@ -59,6 +59,19 @@ define([ eventHelpers.extend(MCTPlotController.prototype); + MCTPlotController.prototype.initCanvas = function () { + if (this.$canvas) { + this.stopListening(this.$canvas); + } + this.$canvas = this.$element.find('canvas'); + + this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); + this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); + this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); + + this.watchForMarquee(); + } + MCTPlotController.prototype.initialize = function () { this.$canvas = this.$element.find('canvas'); @@ -82,6 +95,7 @@ define([ this.listenTo(this.$scope, '$destroy', this.destroy, this); this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this); this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this); + this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this); this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this); this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);