From 6ee0f25c09cd4ded75dd7924cdfd5afd5704f813 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 5 Jun 2018 15:24:11 -0400 Subject: [PATCH 1/6] fix gl3d plot show-no-WebGL message - by making sure it scene creation returns early when WebGL context creation fails. --- src/lib/show_no_webgl_msg.js | 3 ++- src/plots/gl3d/scene.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js index 34545151b1a..8d91cfebed9 100644 --- a/src/lib/show_no_webgl_msg.js +++ b/src/lib/show_no_webgl_msg.js @@ -31,7 +31,8 @@ module.exports = function showWebGlMsg(scene) { }; var div = document.createElement('div'); - div.textContent = 'Webgl is not supported by your browser - visit https://get.webgl.org for more info'; + div.className = 'no-webgl'; + div.textContent = 'WebGL is not supported by your browser - visit https://get.webgl.org for more info'; div.style.cursor = 'pointer'; div.style.fontSize = '24px'; div.style.color = Color.defaults[0]; diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index af02713a09c..f85c2077dcc 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -204,7 +204,7 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) { * The destroy method - which will remove the container from the DOM * is overridden with a function that removes the container only. */ - showNoWebGlMsg(scene); + return showNoWebGlMsg(scene); } var relayoutCallback = function(scene) { From 17fc17c166f3734ef16c270e9b25de7e9e9423d7 Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 5 Jun 2018 15:24:44 -0400 Subject: [PATCH 2/6] test no-webgl environments using --disabel-webgl CLI flag --- test/jasmine/bundle_tests/no_webgl_test.js | 41 ++++++++++++++++++++++ test/jasmine/karma.conf.js | 3 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/jasmine/bundle_tests/no_webgl_test.js diff --git a/test/jasmine/bundle_tests/no_webgl_test.js b/test/jasmine/bundle_tests/no_webgl_test.js new file mode 100644 index 00000000000..49987e6ee80 --- /dev/null +++ b/test/jasmine/bundle_tests/no_webgl_test.js @@ -0,0 +1,41 @@ +var Plotly = require('@lib'); + +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var failTest = require('../assets/fail_test'); + +describe('Plotly w/o WebGL support:', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + function checkNoWebGLMsg(visible) { + var glDiv = gd.querySelector('div.gl-container'); + var msg = glDiv.querySelector('div.no-webgl'); + if(visible) { + expect(msg.innerHTML.substr(0, 22)).toBe('WebGL is not supported'); + } else { + expect(msg).toBe(null); + } + } + + it('gl3d subplots', function(done) { + Plotly.react(gd, require('@mocks/gl3d_autocolorscale.json')) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) + .then(function() { + checkNoWebGLMsg(false); + }) + .catch(failTest) + .then(done); + }); +}); diff --git a/test/jasmine/karma.conf.js b/test/jasmine/karma.conf.js index a427ea4a2ef..468eb11b498 100644 --- a/test/jasmine/karma.conf.js +++ b/test/jasmine/karma.conf.js @@ -188,7 +188,8 @@ func.defaultConfig = { flags: [ '--touch-events', '--window-size=' + argv.width + ',' + argv.height, - isCI ? '--ignore-gpu-blacklist' : '' + isCI ? '--ignore-gpu-blacklist' : '', + (isBundleTest && basename(testFileGlob) === 'no_webgl') ? '--disable-webgl' : '' ] }, _Firefox: { From a2a215ee5ae85a1a8176a570d4e43ca9d6ab3b0e Mon Sep 17 00:00:00 2001 From: etienne Date: Tue, 5 Jun 2018 16:56:21 -0400 Subject: [PATCH 3/6] fixes #2666 - show no-WebGL msg for all gl trace types - gl2d was broken (fixed here) - all regl-based trace types (scattergl, scatterpolargl, splom and parcoords) were missing. --- src/lib/prepare_regl.js | 32 +++++++--- src/lib/show_no_webgl_msg.js | 6 +- src/plots/gl2d/scene2d.js | 7 ++- src/plots/plots.js | 1 + src/traces/parcoords/plot.js | 3 +- src/traces/scattergl/index.js | 10 +++- src/traces/splom/base_plot.js | 8 ++- test/jasmine/bundle_tests/no_webgl_test.js | 68 +++++++++++++++++++++- 8 files changed, 118 insertions(+), 17 deletions(-) diff --git a/src/lib/prepare_regl.js b/src/lib/prepare_regl.js index 228d134208a..13931e4eaa4 100644 --- a/src/lib/prepare_regl.js +++ b/src/lib/prepare_regl.js @@ -8,6 +8,8 @@ 'use strict'; +var showNoWebGlMsg = require('./show_no_webgl_msg'); + // Note that this module should be ONLY required into // files corresponding to regl trace modules // so that bundles with non-regl only don't include @@ -21,23 +23,35 @@ var createRegl = require('regl'); * * @param {DOM node or object} gd : graph div object * @param {array} extensions : list of extension to pass to createRegl + * + * @return {boolean} true if all createRegl calls succeeded, false otherwise */ module.exports = function prepareRegl(gd, extensions) { var fullLayout = gd._fullLayout; + var success = true; fullLayout._glcanvas.each(function(d) { if(d.regl) return; // only parcoords needs pick layer if(d.pick && !fullLayout._has('parcoords')) return; - d.regl = createRegl({ - canvas: this, - attributes: { - antialias: !d.pick, - preserveDrawingBuffer: true - }, - pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio, - extensions: extensions || [] - }); + try { + d.regl = createRegl({ + canvas: this, + attributes: { + antialias: !d.pick, + preserveDrawingBuffer: true + }, + pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio, + extensions: extensions || [] + }); + } catch(e) { + success = false; + } }); + + if(!success) { + showNoWebGlMsg({container: fullLayout._glcontainer.node()}); + } + return success; }; diff --git a/src/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js index 8d91cfebed9..0e2a43949f1 100644 --- a/src/lib/show_no_webgl_msg.js +++ b/src/lib/show_no_webgl_msg.js @@ -21,7 +21,7 @@ var noop = function() {}; * Expects 'scene' to have property 'container' * */ -module.exports = function showWebGlMsg(scene) { +module.exports = function showNoWebGlMsg(scene) { for(var prop in scene) { if(typeof scene[prop] === 'function') scene[prop] = noop; } @@ -36,6 +36,10 @@ module.exports = function showWebGlMsg(scene) { div.style.cursor = 'pointer'; div.style.fontSize = '24px'; div.style.color = Color.defaults[0]; + div.style.position = 'absolute'; + div.style.left = '20px'; + div.style.top = '50%'; + div.style.width = div.style.height = '100%'; scene.container.appendChild(div); scene.container.style.background = '#FFFFFF'; diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 1ab225ded52..9bc5db60cf2 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -45,6 +45,7 @@ function Scene2D(options, fullLayout) { this.updateRefs(fullLayout); this.makeFramework(); + if(this.stopped) return; // update options this.glplotOptions = createOptions(this); @@ -121,7 +122,11 @@ proto.makeFramework = function() { premultipliedAlpha: true }); - if(!gl) showNoWebGlMsg(this); + if(!gl) { + showNoWebGlMsg(this); + this.stopped = true; + return; + } this.canvas = liveCanvas; this.gl = gl; diff --git a/src/plots/plots.js b/src/plots/plots.js index c49f5cb3ae7..497a506c97a 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -682,6 +682,7 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou if(hadGl && !hasGl) { if(oldFullLayout._glcontainer !== undefined) { oldFullLayout._glcontainer.selectAll('.gl-canvas').remove(); + oldFullLayout._glcontainer.selectAll('.no-webgl').remove(); oldFullLayout._glcanvas = null; } } diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index f8a0a485376..aef8c9581c4 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -17,7 +17,8 @@ module.exports = function plot(gd, cdparcoords) { var root = fullLayout._paperdiv; var container = fullLayout._glcontainer; - prepareRegl(gd); + var success = prepareRegl(gd); + if(!success) return; var gdDimensions = {}; var gdDimensionsOriginalOrder = {}; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 17b2096e37c..44e813bd327 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -326,7 +326,15 @@ function plot(gd, subplot, cdata) { var width = fullLayout.width; var height = fullLayout.height; - prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']); + var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']); + if(!success) { + scene.error2d = false; + scene.line2d = false; + scene.scatter2d = false; + scene.fill2d = false; + return; + } + var regl = fullLayout._glcanvas.data()[0].regl; // that is needed for fills diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js index 10b062e2c38..80bef941fb1 100644 --- a/src/traces/splom/base_plot.js +++ b/src/traces/splom/base_plot.js @@ -24,7 +24,8 @@ function plot(gd) { var _module = Registry.getModule(SPLOM); var splomCalcData = getModuleCalcData(gd.calcdata, _module)[0]; - prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']); + var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']); + if(!success) return; if(fullLayout._hasOnlyLargeSploms) { drawGrid(gd); @@ -209,7 +210,10 @@ function clean(newFullData, newFullLayout, oldFullData, oldFullLayout, oldCalcda var trace = cd0.trace; var scene = cd0.t._scene; - if(trace.type === 'splom' && scene && scene.matrix) { + if( + trace.type === 'splom' && + scene && scene.matrix && scene.matrix.destroy + ) { scene.matrix.destroy(); cd0.t._scene = null; } diff --git a/test/jasmine/bundle_tests/no_webgl_test.js b/test/jasmine/bundle_tests/no_webgl_test.js index 49987e6ee80..59944ca2c70 100644 --- a/test/jasmine/bundle_tests/no_webgl_test.js +++ b/test/jasmine/bundle_tests/no_webgl_test.js @@ -17,8 +17,7 @@ describe('Plotly w/o WebGL support:', function() { }); function checkNoWebGLMsg(visible) { - var glDiv = gd.querySelector('div.gl-container'); - var msg = glDiv.querySelector('div.no-webgl'); + var msg = gd.querySelector('div.no-webgl'); if(visible) { expect(msg.innerHTML.substr(0, 22)).toBe('WebGL is not supported'); } else { @@ -38,4 +37,69 @@ describe('Plotly w/o WebGL support:', function() { .catch(failTest) .then(done); }); + + it('gl2d subplots', function(done) { + Plotly.react(gd, require('@mocks/gl2d_pointcloud-basic.json')) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) + .then(function() { + checkNoWebGLMsg(false); + }) + .catch(failTest) + .then(done); + }); + + it('scattergl subplots', function(done) { + Plotly.react(gd, require('@mocks/gl2d_12.json')) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) + .then(function() { + checkNoWebGLMsg(false); + }) + .catch(failTest) + .then(done); + }); + + it('scatterpolargl subplots', function(done) { + Plotly.react(gd, require('@mocks/glpolar_scatter.json')) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) + .then(function() { + checkNoWebGLMsg(false); + }) + .catch(failTest) + .then(done); + }); + + it('splom subplots', function(done) { + Plotly.react(gd, require('@mocks/splom_0.json')) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) + .then(function() { + checkNoWebGLMsg(false); + }) + .catch(failTest) + .then(done); + }); + + it('parcoords subplots', function(done) { + Plotly.react(gd, require('@mocks/gl2d_parcoords_2.json')) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) + .then(function() { + checkNoWebGLMsg(false); + }) + .catch(failTest) + .then(done); + }); }); From 1d3cc9109c7f72431633fcb8c6700658bc4f84bb Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 6 Jun 2018 14:52:24 -0400 Subject: [PATCH 4/6] style no webgl msg --- src/lib/show_no_webgl_msg.js | 18 ++++++++++++++---- test/jasmine/bundle_tests/no_webgl_test.js | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib/show_no_webgl_msg.js b/src/lib/show_no_webgl_msg.js index 0e2a43949f1..65349670b76 100644 --- a/src/lib/show_no_webgl_msg.js +++ b/src/lib/show_no_webgl_msg.js @@ -32,15 +32,25 @@ module.exports = function showNoWebGlMsg(scene) { var div = document.createElement('div'); div.className = 'no-webgl'; - div.textContent = 'WebGL is not supported by your browser - visit https://get.webgl.org for more info'; div.style.cursor = 'pointer'; div.style.fontSize = '24px'; div.style.color = Color.defaults[0]; div.style.position = 'absolute'; - div.style.left = '20px'; - div.style.top = '50%'; + div.style.left = div.style.top = '0px'; div.style.width = div.style.height = '100%'; - + div.style['background-color'] = Color.lightLine; + div.style['z-index'] = 30; + + var p = document.createElement('p'); + p.textContent = 'WebGL is not supported by your browser - visit https://get.webgl.org for more info'; + p.style.position = 'relative'; + p.style.top = '50%'; + p.style.left = '50%'; + p.style.height = '30%'; + p.style.width = '50%'; + p.style.margin = '-15% 0 0 -25%'; + + div.appendChild(p); scene.container.appendChild(div); scene.container.style.background = '#FFFFFF'; scene.container.onclick = function() { diff --git a/test/jasmine/bundle_tests/no_webgl_test.js b/test/jasmine/bundle_tests/no_webgl_test.js index 59944ca2c70..ef06a20b031 100644 --- a/test/jasmine/bundle_tests/no_webgl_test.js +++ b/test/jasmine/bundle_tests/no_webgl_test.js @@ -17,7 +17,7 @@ describe('Plotly w/o WebGL support:', function() { }); function checkNoWebGLMsg(visible) { - var msg = gd.querySelector('div.no-webgl'); + var msg = gd.querySelector('div.no-webgl > p'); if(visible) { expect(msg.innerHTML.substr(0, 22)).toBe('WebGL is not supported'); } else { From eb594d1bf96d0640b5fe7d5b75aac67f3e2418a0 Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 6 Jun 2018 15:07:58 -0400 Subject: [PATCH 5/6] :palm_tree: *-regl2d init / reinit when no-webgl --- src/traces/scattergl/index.js | 17 +++++++++-------- test/jasmine/bundle_tests/no_webgl_test.js | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 44e813bd327..7018a34c389 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -166,7 +166,7 @@ function sceneUpdate(gd, subplot) { var scene = subplot._scene; var fullLayout = gd._fullLayout; - var reset = { + var resetOpts = { // number of traces in subplot, since scene:subplot → 1:1 count: 0, // whether scene requires init hook in plot call (dirty plot call) @@ -181,7 +181,7 @@ function sceneUpdate(gd, subplot) { errorYOptions: [] }; - var first = { + var initOpts = { selectBatch: null, unselectBatch: null, // regl- component stubs, initialized in dirty plot call @@ -193,7 +193,11 @@ function sceneUpdate(gd, subplot) { }; if(!subplot._scene) { - scene = subplot._scene = Lib.extendFlat({}, reset, first); + scene = subplot._scene = {}; + + scene.init = function init() { + Lib.extendFlat(scene, initOpts, resetOpts); + }; // apply new option to all regl components (used on drag) scene.update = function update(opt) { @@ -306,7 +310,7 @@ function sceneUpdate(gd, subplot) { // In case if we have scene from the last calc - reset data if(!scene.dirty) { - Lib.extendFlat(scene, reset); + Lib.extendFlat(scene, resetOpts); } return scene; @@ -328,10 +332,7 @@ function plot(gd, subplot, cdata) { var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']); if(!success) { - scene.error2d = false; - scene.line2d = false; - scene.scatter2d = false; - scene.fill2d = false; + scene.init(); return; } diff --git a/test/jasmine/bundle_tests/no_webgl_test.js b/test/jasmine/bundle_tests/no_webgl_test.js index ef06a20b031..29489ba7958 100644 --- a/test/jasmine/bundle_tests/no_webgl_test.js +++ b/test/jasmine/bundle_tests/no_webgl_test.js @@ -57,6 +57,23 @@ describe('Plotly w/o WebGL support:', function() { checkNoWebGLMsg(true); return Plotly.react(gd, require('@mocks/10.json')); }) + .then(function() { + checkNoWebGLMsg(false); + + // one with all regl2d modules + return Plotly.react(gd, [{ + type: 'scattergl', + mode: 'lines+markers', + fill: 'tozerox', + y: [1, 2, 1], + error_x: { value: 10 }, + error_y: { value: 10 } + }]); + }) + .then(function() { + checkNoWebGLMsg(true); + return Plotly.react(gd, require('@mocks/10.json')); + }) .then(function() { checkNoWebGLMsg(false); }) From fef3980373b3ec86ee34a9661369b73ed121044b Mon Sep 17 00:00:00 2001 From: etienne Date: Wed, 6 Jun 2018 17:28:47 -0400 Subject: [PATCH 6/6] fixup (init scene) --- src/traces/scattergl/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 7018a34c389..81bda45806f 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -199,6 +199,8 @@ function sceneUpdate(gd, subplot) { Lib.extendFlat(scene, initOpts, resetOpts); }; + scene.init(); + // apply new option to all regl components (used on drag) scene.update = function update(opt) { var opts = new Array(scene.count);