From 6290a838c6bf73de1ebc9650ca4abe86f5377334 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 3 Sep 2017 11:27:04 -0400 Subject: [PATCH 01/19] Chill parcoord attribs out --- src/traces/parcoords/attributes.js | 41 +++++++++++------------------- src/traces/parcoords/parcoords.js | 1 - 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js index 83855a2da16..46729697a6e 100644 --- a/src/traces/parcoords/attributes.js +++ b/src/traces/parcoords/attributes.js @@ -18,7 +18,6 @@ var extendDeep = require('../../lib/extend').extendDeep; var extendFlat = require('../../lib/extend').extendFlat; module.exports = { - domain: { x: { valType: 'info_array', @@ -120,36 +119,26 @@ module.exports = { description: 'The dimensions (variables) of the parallel coordinates chart. 2..60 dimensions are supported.' }, - line: extendFlat({}, - + line: extendFlat( // the default autocolorscale isn't quite usable for parcoords due to context ambiguity around 0 (grey, off-white) - // autocolorscale therefore defaults to false too, to avoid being overridden by the blue-white-red autocolor palette + // autocolorscale therefore defaults to false too, to avoid being overridden by the blue-white-red autocolor palette extendDeep( - {}, colorAttributes('line'), { - colorscale: extendDeep( - {}, - colorAttributes('line').colorscale, - {dflt: colorscales.Viridis} - ), - autocolorscale: extendDeep( - {}, - colorAttributes('line').autocolorscale, - { - dflt: false, - description: [ - 'Has an effect only if line.color` is set to a numerical array.', - 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', - 'or the palette determined by `line.colorscale`.', - 'In case `colorscale` is unspecified or `autocolorscale` is true, the default ', - 'palette will be chosen according to whether numbers in the `color` array are', - 'all positive, all negative or mixed.', - 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' - ].join(' ') - } - ) + colorscale: {dflt: colorscales.Viridis}, + autocolorscale: { + dflt: false, + description: [ + 'Has an effect only if line.color` is set to a numerical array.', + 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', + 'or the palette determined by `line.colorscale`.', + 'In case `colorscale` is unspecified or `autocolorscale` is true, the default ', + 'palette will be chosen according to whether numbers in the `color` array are', + 'all positive, all negative or mixed.', + 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' + ].join(' ') + } } ), diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 455e76f08f9..cfbc9459f1f 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -254,7 +254,6 @@ function styleExtentTexts(selection) { } module.exports = function(root, svg, styledData, layout, callbacks) { - var domainBrushing = false; var linePickActive = true; From 63af430833c87fb604ddd601b481552a291231ac Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 5 Sep 2017 19:26:31 -0400 Subject: [PATCH 02/19] Make parcoords use shared canvases --- src/plot_api/plot_api.js | 38 +++++++++++++++-- src/traces/parcoords/parcoords.js | 68 ++++++++++++------------------- src/traces/parcoords/plot.js | 2 + 3 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index a6a4a62f9f1..15f1e454153 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3027,9 +3027,41 @@ function makePlotFramework(gd) { // TODO: sort out all the ordering so we don't have to // explicitly delete anything fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') - .data([0]); - fullLayout._glcontainer.enter().append('div') - .classed('gl-container', true); + .data([{}]); + + // FIXME: bring this constant to some plotly constants module + // it is taken from parcoords lineLayerModel + fullLayout._glcanvas = fullLayout._glcontainer.enter().append('div') + .classed('gl-container', true) + .selectAll('.gl-canvas') + .data([{ + key: 'contextLayer' + }, { + key: 'focusLayer' + }, { + key: 'pickLayer' + }]); + + // create canvases only in case if there is at least one regl component + // FIXME: probably there is a better d3 way of doing so + for(var i = 0; i < fullLayout._modules.length; i++) { + var module = fullLayout._modules[i]; + if(module.categories && module.categories.indexOf('gl') >= 0) { + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .attr('width', fullLayout.width) + .attr('height', fullLayout.height) + .style('position', 'absolute') + .style('top', 0) + .style('left', 0) + .style('pointer-events', 'none') + .style('overflow', 'visible'); + + break; + } + } fullLayout._paperdiv.selectAll('.main-svg').remove(); diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index cfbc9459f1f..422f983fe6d 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -233,18 +233,6 @@ function viewModel(model) { return viewModel; } -function lineLayerModel(vm) { - return c.layers.map(function(key) { - return { - key: key, - context: key === 'contextLineLayer', - pick: key === 'pickLineLayer', - viewModel: vm, - model: vm.model - }; - }); -} - function styleExtentTexts(selection) { selection .classed('axisExtentText', true) @@ -253,7 +241,7 @@ function styleExtentTexts(selection) { .style('user-select', 'none'); } -module.exports = function(root, svg, styledData, layout, callbacks) { +module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, callbacks) { var domainBrushing = false; var linePickActive = true; @@ -300,37 +288,31 @@ module.exports = function(root, svg, styledData, layout, callbacks) { .map(model.bind(0, layout)) .map(viewModel); - root.selectAll('.parcoords-line-layers').remove(); - - var parcoordsLineLayers = root.selectAll('.parcoords-line-layers') - .data(vm, keyFun); - - parcoordsLineLayers.enter() - .insert('div', '.' + svg.attr('class').split(' ').join(' .')) // not hardcoding .main-svg - .classed('parcoords-line-layers', true) - .style('box-sizing', 'content-box'); + parcoordsLineLayers.each(function(d, i) { + return Lib.extendFlat(d, vm[i]); + }); parcoordsLineLayers .style('transform', function(d) { return 'translate(' + (d.model.translateX - c.overdrag) + 'px,' + d.model.translateY + 'px)'; }); - var parcoordsLineLayer = parcoordsLineLayers.selectAll('.parcoords-lines') - .data(lineLayerModel, keyFun); + var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas') + .each(function(d) { + var key = d.key; + d.context = key === 'contextLayer'; + d.pick = key === 'pickLayer'; + + // FIXME: figure out how to handle multiple instances + d.viewModel = vm[0]; + d.model = vm[0].model; + }); var tweakables = {renderers: [], dimensions: []}; var lastHovered = null; - parcoordsLineLayer.enter() - .append('canvas') - .attr('class', function(d) {return 'parcoords-lines ' + (d.context ? 'context' : d.pick ? 'pick' : 'focus');}) - .style('box-sizing', 'content-box') - .style('float', 'left') - .style('clear', 'both') - .style('left', 0) - .style('overflow', 'visible') - .style('position', function(d, i) {return i > 0 ? 'absolute' : 'absolute';}) + parcoordsLineLayer .filter(function(d) {return d.pick;}) .on('mousemove', function(d) { if(linePickActive && d.lineLayer && callbacks && callbacks.hover) { @@ -512,8 +494,8 @@ module.exports = function(root, svg, styledData, layout, callbacks) { .attr('transform', function(d) {return 'translate(' + d.xScale(d.xIndex) + ', 0)';}); d3.select(this).attr('transform', 'translate(' + d.x + ', 0)'); yAxis.each(function(dd, i, ii) {if(ii === d.parent.key) p.dimensions[i] = dd;}); - p.contextLineLayer && p.contextLineLayer.render(p.panels, false, !someFiltersActive(p)); - p.focusLineLayer.render && p.focusLineLayer.render(p.panels); + p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p)); + p.focusLayer.render && p.focusLayer.render(p.panels); }) .on('dragend', function(d) { var p = d.parent; @@ -528,9 +510,9 @@ module.exports = function(root, svg, styledData, layout, callbacks) { updatePanelLayout(yAxis, p); d3.select(this) .attr('transform', function(d) {return 'translate(' + d.x + ', 0)';}); - p.contextLineLayer && p.contextLineLayer.render(p.panels, false, !someFiltersActive(p)); - p.focusLineLayer && p.focusLineLayer.render(p.panels); - p.pickLineLayer && p.pickLineLayer.render(p.panels, true); + p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p)); + p.focusLayer && p.focusLayer.render(p.panels); + p.pickLayer && p.pickLayer.render(p.panels, true); linePickActive = true; if(callbacks && callbacks.axesMoved) { @@ -742,13 +724,13 @@ module.exports = function(root, svg, styledData, layout, callbacks) { var newExtent = reset ? [0, 1] : extent.slice(); if(newExtent[0] !== filter[0] || newExtent[1] !== filter[1]) { dimensions[dimension.xIndex].filter = newExtent; - p.focusLineLayer && p.focusLineLayer.render(p.panels, true); + p.focusLayer && p.focusLayer.render(p.panels, true); var filtersActive = someFiltersActive(p); if(!contextShown && filtersActive) { - p.contextLineLayer && p.contextLineLayer.render(p.panels, true); + p.contextLayer && p.contextLayer.render(p.panels, true); contextShown = true; } else if(contextShown && !filtersActive) { - p.contextLineLayer && p.contextLineLayer.render(p.panels, true, true); + p.contextLayer && p.contextLayer.render(p.panels, true, true); contextShown = false; } } @@ -769,9 +751,9 @@ module.exports = function(root, svg, styledData, layout, callbacks) { f[1] = Math.min(1, f[1] + 0.05); } d3.select(this).transition().duration(150).call(dimension.brush.extent(f)); - p.focusLineLayer.render(p.panels, true); + p.focusLayer.render(p.panels, true); } - p.pickLineLayer && p.pickLineLayer.render(p.panels, true); + p.pickLayer && p.pickLayer.render(p.panels, true); linePickActive = true; domainBrushing = 'ending'; if(callbacks && callbacks.filterChanged) { diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index 90cc3353846..aa6be64e6d9 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -15,6 +15,7 @@ module.exports = function plot(gd, cdparcoords) { var fullLayout = gd._fullLayout; var svg = fullLayout._paper; var root = fullLayout._paperdiv; + var container = fullLayout._glcontainer; var gdDimensions = {}; var gdDimensionsOriginalOrder = {}; @@ -98,6 +99,7 @@ module.exports = function plot(gd, cdparcoords) { parcoords( root, svg, + container, cdparcoords, { width: size.w, From 943a74370869a2bc348ad62e5c1dc7cd751fdc5a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 5 Sep 2017 20:02:53 -0400 Subject: [PATCH 03/19] Use top paper --- src/traces/parcoords/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index aa6be64e6d9..8abee427202 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -13,7 +13,7 @@ var parcoords = require('./parcoords'); module.exports = function plot(gd, cdparcoords) { var fullLayout = gd._fullLayout; - var svg = fullLayout._paper; + var svg = fullLayout._toppaper; var root = fullLayout._paperdiv; var container = fullLayout._glcontainer; From 7858201f7edaf60931d0b42213d3f4325185f44f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 14:43:39 -0400 Subject: [PATCH 04/19] Simplify line creation method --- src/traces/parcoords/lines.js | 14 +++++++++++++- src/traces/parcoords/parcoords.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 92776dabf0a..4faef839bbe 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -165,7 +165,19 @@ function valid(i, offset, panelCount) { return i + offset <= panelCount; } -module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDimensions, initialPanels, unitToColor, context, pick, scatter) { +module.exports = function(canvasGL, d, scatter) { + var model = d.model, + vm = d.viewModel; + + var lines = model.lines, + canvasWidth = model.canvasWidth, + canvasHeight = model.canvasHeight, + initialDimensions = vm.dimensions, + initialPanels = vm.panels, + unitToColor = model.unitToColor, + context = d.context, + pick = d.pick; + var renderState = { currentRafs: {}, diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 422f983fe6d..ab9c865316b 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -460,7 +460,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca parcoordsLineLayer .each(function(d) { - d.lineLayer = lineLayerMaker(this, d.model.lines, d.model.canvasWidth, d.model.canvasHeight, d.viewModel.dimensions, d.viewModel.panels, d.model.unitToColor, d.context, d.pick, c.scatter); + d.lineLayer = lineLayerMaker(this, d, c.scatter); d.viewModel[d.key] = d.lineLayer; tweakables.renderers.push(function() {d.lineLayer.render(d.viewModel.panels, true);}); d.lineLayer.render(d.viewModel.panels, !d.context); From 4d17a53c54189976e39bae751167b8be76755f41 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 16:05:58 -0400 Subject: [PATCH 05/19] Fix image generation --- src/traces/parcoords/base_plot.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index e5be3285b75..afd81d1fdd3 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -28,8 +28,6 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) var hasParcoords = (newFullLayout._has && newFullLayout._has('parcoords')); if(hadParcoords && !hasParcoords) { - oldFullLayout._paperdiv.selectAll('.parcoords-line-layers').remove(); - oldFullLayout._paperdiv.selectAll('.parcoords-line-layers').remove(); oldFullLayout._paperdiv.selectAll('.parcoords').remove(); oldFullLayout._paperdiv.selectAll('.parcoords').remove(); oldFullLayout._glimages.selectAll('*').remove(); @@ -41,7 +39,7 @@ exports.toSVG = function(gd) { var imageRoot = gd._fullLayout._glimages; var root = d3.select(gd).selectAll('.svg-container'); var canvases = root.filter(function(d, i) {return i === root.size() - 1;}) - .selectAll('.parcoords-lines.context, .parcoords-lines.focus'); + .selectAll('.gl-canvas-context, .gl-canvas-focus'); function canvasToImage(d) { var canvas = this; From 314dfdc7ebaf76a870df57bef17d1d638dfbd50a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 17:11:40 -0400 Subject: [PATCH 06/19] Use viewport to limit painting area instead of css --- src/traces/parcoords/lines.js | 24 +++++++++++++++++------- src/traces/parcoords/parcoords.js | 16 +++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 4faef839bbe..07af4f390c8 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -10,7 +10,7 @@ var createREGL = require('regl'); var glslify = require('glslify'); -var verticalPadding = require('./constants').verticalPadding; +var c = require('./constants'); var vertexShaderSource = glslify('./shaders/vertex.glsl'); var pickVertexShaderSource = glslify('./shaders/pick_vertex.glsl'); var fragmentShaderSource = glslify('./shaders/fragment.glsl'); @@ -56,7 +56,8 @@ function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item item.offset = sectionVertexCount * blockNumber * blockLineCount; item.count = sectionVertexCount * count; if(blockNumber === 0) { - window.cancelAnimationFrame(renderState.currentRafs[rafKey]); // stop drawing possibly stale glyphs before clearing + // stop drawing possibly stale glyphs before clearing + window.cancelAnimationFrame(renderState.currentRafs[rafKey]); delete renderState.currentRafs[rafKey]; clear(regl, item.scissorX, item.scissorY, item.scissorWidth, item.viewBoxSize[1]); } @@ -167,7 +168,8 @@ function valid(i, offset, panelCount) { module.exports = function(canvasGL, d, scatter) { var model = d.model, - vm = d.viewModel; + vm = d.viewModel, + domain = model.domain; var lines = model.lines, canvasWidth = model.canvasWidth, @@ -178,7 +180,6 @@ module.exports = function(canvasGL, d, scatter) { context = d.context, pick = d.pick; - var renderState = { currentRafs: {}, drawCompleted: true, @@ -260,6 +261,15 @@ module.exports = function(canvasGL, d, scatter) { } }, + viewport: () => { + return { + x: model.pad.l - overdrag + model.layoutWidth * domain.x[0], + y: model.pad.b + model.layoutHeight * domain.y[0], + width: canvasWidth, + height: canvasHeight + } + }, + dither: false, vert: pick ? pickVertexShaderSource : vertexShaderSource, @@ -309,7 +319,7 @@ module.exports = function(canvasGL, d, scatter) { function makeItem(i, ii, x, y, panelSizeX, canvasPanelSizeY, crossfilterDimensionIndex, scatter, I, leftmost, rightmost) { var loHi, abcd, d, index; var leftRight = [i, ii]; - var filterEpsilon = verticalPadding / canvasPanelSizeY; + var filterEpsilon = c.verticalPadding / canvasPanelSizeY; var dims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});}); var lims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});}); @@ -353,9 +363,9 @@ module.exports = function(canvasGL, d, scatter) { colorClamp: colorClamp, scatter: scatter || 0, - scissorX: I === leftmost ? 0 : x + overdrag, + scissorX: (I === leftmost ? 0 : x + overdrag) + (model.pad.l - overdrag) + model.layoutWidth * domain.x[0], scissorWidth: (I === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (I === leftmost ? x + overdrag : 0), - scissorY: y, + scissorY: y + model.pad.b + model.layoutHeight * domain.y[0], scissorHeight: canvasPanelSizeY }; } diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index ab9c865316b..33119b9fdb0 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -166,6 +166,9 @@ function model(layout, d, i) { labelFont: labelFont, tickFont: tickFont, rangeFont: rangeFont, + layoutWidth: width, + layoutHeight: layout.height, + domain: domain, translateX: domain.x[0] * width, translateY: layout.height - domain.y[1] * layout.height, pad: pad, @@ -292,11 +295,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca return Lib.extendFlat(d, vm[i]); }); - parcoordsLineLayers - .style('transform', function(d) { - return 'translate(' + (d.model.translateX - c.overdrag) + 'px,' + d.model.translateY + 'px)'; - }); - var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas') .each(function(d) { var key = d.key; @@ -350,14 +348,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca }); parcoordsLineLayer - .style('margin', function(d) { - var p = d.model.pad; - return p.t + 'px ' + p.r + 'px ' + p.b + 'px ' + p.l + 'px'; - }) - .attr('width', function(d) {return d.model.canvasWidth;}) - .attr('height', function(d) {return d.model.canvasHeight;}) - .style('width', function(d) {return (d.model.width + 2 * c.overdrag) + 'px';}) - .style('height', function(d) {return d.model.height + 'px';}) .style('opacity', function(d) {return d.pick ? 0.01 : 1;}); svg.style('background', 'rgba(255, 255, 255, 0)'); From aefdab16d0564267831bb0f97e4eb5637d198ddd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 17:29:24 -0400 Subject: [PATCH 07/19] Eslint, enhance props propagation --- src/traces/parcoords/base_plot.js | 12 ++---------- src/traces/parcoords/lines.js | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index afd81d1fdd3..1321fed4816 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -12,7 +12,6 @@ var d3 = require('d3'); var Plots = require('../../plots/plots'); var parcoordsPlot = require('./plot'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); -var c = require('./constants'); exports.name = 'parcoords'; @@ -41,21 +40,14 @@ exports.toSVG = function(gd) { var canvases = root.filter(function(d, i) {return i === root.size() - 1;}) .selectAll('.gl-canvas-context, .gl-canvas-focus'); - function canvasToImage(d) { + function canvasToImage() { var canvas = this; var imageData = canvas.toDataURL('image/png'); var image = imageRoot.append('svg:image'); - var size = gd._fullLayout._size; - var domain = gd._fullData[d.model.key].domain; image.attr({ xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData, - x: size.l + size.w * domain.x[0] - c.overdrag, - y: size.t + size.h * (1 - domain.y[1]), - width: (domain.x[1] - domain.x[0]) * size.w + 2 * c.overdrag, - height: (domain.y[1] - domain.y[0]) * size.h, - preserveAspectRatio: 'none' + 'xlink:href': imageData }); } diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 07af4f390c8..98495e53ad8 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -261,13 +261,11 @@ module.exports = function(canvasGL, d, scatter) { } }, - viewport: () => { - return { - x: model.pad.l - overdrag + model.layoutWidth * domain.x[0], - y: model.pad.b + model.layoutHeight * domain.y[0], - width: canvasWidth, - height: canvasHeight - } + viewport: { + x: regl.prop('viewportX'), + y: regl.prop('viewportY'), + width: regl.prop('viewportWidth'), + height: regl.prop('viewportHeight') }, dither: false, @@ -363,10 +361,16 @@ module.exports = function(canvasGL, d, scatter) { colorClamp: colorClamp, scatter: scatter || 0, + scissorX: (I === leftmost ? 0 : x + overdrag) + (model.pad.l - overdrag) + model.layoutWidth * domain.x[0], scissorWidth: (I === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (I === leftmost ? x + overdrag : 0), scissorY: y + model.pad.b + model.layoutHeight * domain.y[0], - scissorHeight: canvasPanelSizeY + scissorHeight: canvasPanelSizeY, + + viewportX: model.pad.l - overdrag + model.layoutWidth * domain.x[0], + viewportY: model.pad.b + model.layoutHeight * domain.y[0], + viewportWidth: canvasWidth, + viewportHeight: canvasHeight }; } From 58ff5a3aceaf6beb95fd62e3753d799dcd1bceae Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 17:49:04 -0400 Subject: [PATCH 08/19] Provide image attribs hoping CI will trigger --- src/traces/parcoords/base_plot.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index 1321fed4816..d34583c8be7 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -47,7 +47,12 @@ exports.toSVG = function(gd) { image.attr({ xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData + 'xlink:href': imageData, + preserveAspectRatio: 'none', + x: 0, + y: 0, + width: canvas.width, + height: canvas.height }); } From dd829616358fb714859a402c36f9aa2cf2c2068c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 18:25:17 -0400 Subject: [PATCH 09/19] Update failing baseline --- test/image/baselines/gl2d_parcoords_2.png | Bin 37011 -> 29852 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/gl2d_parcoords_2.png b/test/image/baselines/gl2d_parcoords_2.png index 4eebd736916a6d359cd61783a0606ba230b76460..91d01c98f600993ce93b564bfb91672d85871f18 100644 GIT binary patch literal 29852 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 DdOSk} literal 37011 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 From 891539292343668c22aa5b7fd765b1b6ad769432 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 18:53:01 -0400 Subject: [PATCH 10/19] Make gl-components use pre-created canvases --- src/plot_api/plot_api.js | 2 ++ src/plots/gl2d/scene2d.js | 6 ++---- src/plots/gl3d/scene.js | 6 ++---- src/traces/contourgl/index.js | 2 +- src/traces/heatmapgl/index.js | 2 +- src/traces/mesh3d/index.js | 2 +- src/traces/pointcloud/index.js | 2 +- src/traces/scatter3d/index.js | 2 +- src/traces/scattergl/index.js | 2 +- src/traces/surface/index.js | 2 +- 10 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 15f1e454153..20141cf1cb1 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3056,6 +3056,8 @@ function makePlotFramework(gd) { .style('position', 'absolute') .style('top', 0) .style('left', 0) + .style('width', '100%') + .style('height', '100%') .style('pointer-events', 'none') .style('overflow', 'visible'); diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 57b9e6f9bbf..a19404f4f3f 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -111,7 +111,7 @@ proto.makeFramework = function() { this.gl = STATIC_CONTEXT; } else { - var liveCanvas = document.createElement('canvas'); + var liveCanvas = this.container.querySelector('.gl-canvas-focus'); var gl = getContext({ canvas: liveCanvas, @@ -139,7 +139,7 @@ proto.makeFramework = function() { // disabling user select on the canvas // sanitizes double-clicks interactions // ref: https://github.com/plotly/plotly.js/issues/744 - canvas.className += 'user-select-none'; + canvas.className += ' user-select-none'; // create SVG container for hover text var svgContainer = this.svgContainer = document.createElementNS( @@ -158,7 +158,6 @@ proto.makeFramework = function() { // append canvas, hover svg and mouse div to container var container = this.container; - container.appendChild(canvas); container.appendChild(svgContainer); container.appendChild(mouseContainer); @@ -369,7 +368,6 @@ proto.destroy = function() { this.glplot.dispose(); - if(!this.staticPlot) this.container.removeChild(this.canvas); this.container.removeChild(this.svgContainer); this.container.removeChild(this.mouseContainer); diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 071310a78ec..98f0f329359 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -136,9 +136,9 @@ function render(scene) { function initializeGLPlot(scene, fullLayout, canvas, gl) { var glplotOptions = { - canvas: canvas, gl: gl, container: scene.container, + canvas: scene.container.querySelector('.gl-canvas-focus'), axes: scene.axesOptions, spikes: scene.spikeOptions, pickRadius: 10, @@ -228,8 +228,7 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) { function Scene(options, fullLayout) { // create sub container for plot - var sceneContainer = document.createElement('div'); - var plotContainer = options.container; + var sceneContainer = fullLayout._glcontainer.node(); // keep a ref to the graph div to fire hover+click events this.graphDiv = options.graphDiv; @@ -251,7 +250,6 @@ function Scene(options, fullLayout) { sceneContainer.style.position = 'absolute'; sceneContainer.style.top = sceneContainer.style.left = '0px'; sceneContainer.style.width = sceneContainer.style.height = '100%'; - plotContainer.appendChild(sceneContainer); this.fullLayout = fullLayout; this.id = options.id || 'scene'; diff --git a/src/traces/contourgl/index.js b/src/traces/contourgl/index.js index ac4fca3b72d..a1d6f961cd6 100644 --- a/src/traces/contourgl/index.js +++ b/src/traces/contourgl/index.js @@ -21,7 +21,7 @@ ContourGl.plot = require('./convert'); ContourGl.moduleType = 'trace'; ContourGl.name = 'contourgl'; ContourGl.basePlotModule = require('../../plots/gl2d'); -ContourGl.categories = ['gl2d', '2dMap']; +ContourGl.categories = ['gl', 'gl2d', '2dMap']; ContourGl.meta = { description: [ 'WebGL contour (beta)' diff --git a/src/traces/heatmapgl/index.js b/src/traces/heatmapgl/index.js index 19ac6fe15f4..dca703e7f85 100644 --- a/src/traces/heatmapgl/index.js +++ b/src/traces/heatmapgl/index.js @@ -21,7 +21,7 @@ HeatmapGl.plot = require('./convert'); HeatmapGl.moduleType = 'trace'; HeatmapGl.name = 'heatmapgl'; HeatmapGl.basePlotModule = require('../../plots/gl2d'); -HeatmapGl.categories = ['gl2d', '2dMap']; +HeatmapGl.categories = ['gl', 'gl2d', '2dMap']; HeatmapGl.meta = { description: [ 'WebGL version of the heatmap trace type.' diff --git a/src/traces/mesh3d/index.js b/src/traces/mesh3d/index.js index 8506fb81814..41d6b49b12b 100644 --- a/src/traces/mesh3d/index.js +++ b/src/traces/mesh3d/index.js @@ -20,7 +20,7 @@ Mesh3D.plot = require('./convert'); Mesh3D.moduleType = 'trace'; Mesh3D.name = 'mesh3d', Mesh3D.basePlotModule = require('../../plots/gl3d'); -Mesh3D.categories = ['gl3d']; +Mesh3D.categories = ['gl', 'gl3d']; Mesh3D.meta = { description: [ 'Draws sets of triangles with coordinates given by', diff --git a/src/traces/pointcloud/index.js b/src/traces/pointcloud/index.js index b5cef7bdd2c..dd710caafad 100644 --- a/src/traces/pointcloud/index.js +++ b/src/traces/pointcloud/index.js @@ -20,7 +20,7 @@ pointcloud.plot = require('./convert'); pointcloud.moduleType = 'trace'; pointcloud.name = 'pointcloud'; pointcloud.basePlotModule = require('../../plots/gl2d'); -pointcloud.categories = ['gl2d', 'showLegend']; +pointcloud.categories = ['gl', 'gl2d', 'showLegend']; pointcloud.meta = { description: [ 'The data visualized as a point cloud set in `x` and `y`', diff --git a/src/traces/scatter3d/index.js b/src/traces/scatter3d/index.js index 0e77661748c..a426d2dfd4b 100644 --- a/src/traces/scatter3d/index.js +++ b/src/traces/scatter3d/index.js @@ -20,7 +20,7 @@ Scatter3D.calc = require('./calc'); Scatter3D.moduleType = 'trace'; Scatter3D.name = 'scatter3d'; Scatter3D.basePlotModule = require('../../plots/gl3d'); -Scatter3D.categories = ['gl3d', 'symbols', 'markerColorscale', 'showLegend']; +Scatter3D.categories = ['gl', 'gl3d', 'symbols', 'markerColorscale', 'showLegend']; Scatter3D.meta = { hrName: 'scatter_3d', description: [ diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 35d292f1c90..6ad0666d16a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -23,7 +23,7 @@ ScatterGl.selectPoints = require('./select'); ScatterGl.moduleType = 'trace'; ScatterGl.name = 'scattergl'; ScatterGl.basePlotModule = require('../../plots/gl2d'); -ScatterGl.categories = ['gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; +ScatterGl.categories = ['gl', 'gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; ScatterGl.meta = { description: [ 'The data visualized as scatter point or lines is set in `x` and `y`', diff --git a/src/traces/surface/index.js b/src/traces/surface/index.js index 03e64f5762e..b756fe59144 100644 --- a/src/traces/surface/index.js +++ b/src/traces/surface/index.js @@ -20,7 +20,7 @@ Surface.plot = require('./convert'); Surface.moduleType = 'trace'; Surface.name = 'surface'; Surface.basePlotModule = require('../../plots/gl3d'); -Surface.categories = ['gl3d', '2dMap', 'noOpacity']; +Surface.categories = ['gl', 'gl3d', '2dMap', 'noOpacity']; Surface.meta = { description: [ 'The data the describes the coordinates of the surface is set in `z`.', From 368fe44f16b154aa44a1e828a9b1f546f4e5ca01 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 10:48:36 -0400 Subject: [PATCH 11/19] Fix karma tests in windows --- tasks/util/strict_d3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/util/strict_d3.js b/tasks/util/strict_d3.js index 1e51ab8912b..18b8ff25915 100644 --- a/tasks/util/strict_d3.js +++ b/tasks/util/strict_d3.js @@ -18,7 +18,7 @@ module.exports = transformTools.makeRequireTransform('requireTransform', var pathOut; if(pathIn === 'd3' && opts.file !== pathToStrictD3Module) { - pathOut = 'require(\'' + pathToStrictD3Module + '\')'; + pathOut = 'require(' + JSON.stringify(pathToStrictD3Module) + ')'; } if(pathOut) return cb(null, pathOut); From 4710c1265d5c614fe349c186e6e48fe23602c889 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 11:27:41 -0400 Subject: [PATCH 12/19] Update canvas size properly --- src/plot_api/plot_api.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 20141cf1cb1..2c124625bdf 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3051,8 +3051,6 @@ function makePlotFramework(gd) { .attr('class', function(d) { return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); }) - .attr('width', fullLayout.width) - .attr('height', fullLayout.height) .style('position', 'absolute') .style('top', 0) .style('left', 0) @@ -3065,6 +3063,10 @@ function makePlotFramework(gd) { } } + fullLayout._glcanvas + .attr('width', fullLayout.width) + .attr('height', fullLayout.width); + fullLayout._paperdiv.selectAll('.main-svg').remove(); fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') From 6ebd70cd2cbe876428e3e4b866cd08ca62a65735 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 11:34:01 -0400 Subject: [PATCH 13/19] Revert 3d-plots shared context changes --- src/plots/gl3d/scene.js | 6 ++++-- src/traces/mesh3d/index.js | 2 +- src/traces/scatter3d/index.js | 2 +- src/traces/surface/index.js | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 98f0f329359..071310a78ec 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -136,9 +136,9 @@ function render(scene) { function initializeGLPlot(scene, fullLayout, canvas, gl) { var glplotOptions = { + canvas: canvas, gl: gl, container: scene.container, - canvas: scene.container.querySelector('.gl-canvas-focus'), axes: scene.axesOptions, spikes: scene.spikeOptions, pickRadius: 10, @@ -228,7 +228,8 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) { function Scene(options, fullLayout) { // create sub container for plot - var sceneContainer = fullLayout._glcontainer.node(); + var sceneContainer = document.createElement('div'); + var plotContainer = options.container; // keep a ref to the graph div to fire hover+click events this.graphDiv = options.graphDiv; @@ -250,6 +251,7 @@ function Scene(options, fullLayout) { sceneContainer.style.position = 'absolute'; sceneContainer.style.top = sceneContainer.style.left = '0px'; sceneContainer.style.width = sceneContainer.style.height = '100%'; + plotContainer.appendChild(sceneContainer); this.fullLayout = fullLayout; this.id = options.id || 'scene'; diff --git a/src/traces/mesh3d/index.js b/src/traces/mesh3d/index.js index 41d6b49b12b..8506fb81814 100644 --- a/src/traces/mesh3d/index.js +++ b/src/traces/mesh3d/index.js @@ -20,7 +20,7 @@ Mesh3D.plot = require('./convert'); Mesh3D.moduleType = 'trace'; Mesh3D.name = 'mesh3d', Mesh3D.basePlotModule = require('../../plots/gl3d'); -Mesh3D.categories = ['gl', 'gl3d']; +Mesh3D.categories = ['gl3d']; Mesh3D.meta = { description: [ 'Draws sets of triangles with coordinates given by', diff --git a/src/traces/scatter3d/index.js b/src/traces/scatter3d/index.js index a426d2dfd4b..0e77661748c 100644 --- a/src/traces/scatter3d/index.js +++ b/src/traces/scatter3d/index.js @@ -20,7 +20,7 @@ Scatter3D.calc = require('./calc'); Scatter3D.moduleType = 'trace'; Scatter3D.name = 'scatter3d'; Scatter3D.basePlotModule = require('../../plots/gl3d'); -Scatter3D.categories = ['gl', 'gl3d', 'symbols', 'markerColorscale', 'showLegend']; +Scatter3D.categories = ['gl3d', 'symbols', 'markerColorscale', 'showLegend']; Scatter3D.meta = { hrName: 'scatter_3d', description: [ diff --git a/src/traces/surface/index.js b/src/traces/surface/index.js index b756fe59144..03e64f5762e 100644 --- a/src/traces/surface/index.js +++ b/src/traces/surface/index.js @@ -20,7 +20,7 @@ Surface.plot = require('./convert'); Surface.moduleType = 'trace'; Surface.name = 'surface'; Surface.basePlotModule = require('../../plots/gl3d'); -Surface.categories = ['gl', 'gl3d', '2dMap', 'noOpacity']; +Surface.categories = ['gl3d', '2dMap', 'noOpacity']; Surface.meta = { description: [ 'The data the describes the coordinates of the surface is set in `z`.', From 8546576ab74ac4f888779ea328c5fcc08e2a4d78 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 12:31:01 -0400 Subject: [PATCH 14/19] Fix glcanvas resizing typo --- src/plot_api/plot_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 2c124625bdf..de28d120abc 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3065,7 +3065,7 @@ function makePlotFramework(gd) { fullLayout._glcanvas .attr('width', fullLayout.width) - .attr('height', fullLayout.width); + .attr('height', fullLayout.height); fullLayout._paperdiv.selectAll('.main-svg').remove(); From 7ed4484dd6ca28ddd659a728db805af7210e818c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 15:13:17 -0400 Subject: [PATCH 15/19] Make hasCategory method --- src/plots/plots.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/plots/plots.js b/src/plots/plots.js index e8ff7e50bc8..9e29fcbfa9b 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -458,6 +458,7 @@ plots.supplyDefaults = function(gd) { // attach helper method to check whether a plot type is present on graph newFullLayout._has = plots._hasPlotType.bind(newFullLayout); + newFullLayout._hasCategory = plots._hasCategory.bind(newFullLayout); // special cases that introduce interactions between traces var _modules = newFullLayout._modules; @@ -576,6 +577,21 @@ plots._hasPlotType = function(category) { return false; }; +// check whether trace has a category +plots._hasCategory = function(category) { + var modules = this._modules || []; + + // create canvases only in case if there is at least one regl component + for(var i = 0; i < modules.length; i++) { + var _ = modules[i]; + if(_.categories && _.categories.indexOf(category) >= 0) { + return true; + } + } + + return false; +}; + plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { var i, j; From c399fd957bb1c7fc1586768d45983af0d8a255fd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 15:52:16 -0400 Subject: [PATCH 16/19] Make use of _hasCategory method --- src/plot_api/plot_api.js | 52 +++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index de28d120abc..3097eb0e9c9 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -194,6 +194,24 @@ Plotly.plot = function(gd, data, layout, config) { } } + if(fullLayout._hasCategory('gl') && fullLayout._glcanvas.empty()) { + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style('position', 'absolute') + .style('top', 0) + .style('left', 0) + .style('width', '100%') + .style('height', '100%') + .style('pointer-events', 'none') + .style('overflow', 'visible'); + } + + fullLayout._glcanvas + .attr('width', fullLayout.width) + .attr('height', fullLayout.height); + return Lib.syncOrAsync([ subroutines.layoutStyles ], gd); @@ -3029,11 +3047,10 @@ function makePlotFramework(gd) { fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') .data([{}]); - // FIXME: bring this constant to some plotly constants module - // it is taken from parcoords lineLayerModel - fullLayout._glcanvas = fullLayout._glcontainer.enter().append('div') - .classed('gl-container', true) - .selectAll('.gl-canvas') + fullLayout._glcontainer.enter().append('div') + .classed('gl-container', true); + + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas') .data([{ key: 'contextLayer' }, { @@ -3042,31 +3059,6 @@ function makePlotFramework(gd) { key: 'pickLayer' }]); - // create canvases only in case if there is at least one regl component - // FIXME: probably there is a better d3 way of doing so - for(var i = 0; i < fullLayout._modules.length; i++) { - var module = fullLayout._modules[i]; - if(module.categories && module.categories.indexOf('gl') >= 0) { - fullLayout._glcanvas.enter().append('canvas') - .attr('class', function(d) { - return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); - }) - .style('position', 'absolute') - .style('top', 0) - .style('left', 0) - .style('width', '100%') - .style('height', '100%') - .style('pointer-events', 'none') - .style('overflow', 'visible'); - - break; - } - } - - fullLayout._glcanvas - .attr('width', fullLayout.width) - .attr('height', fullLayout.height); - fullLayout._paperdiv.selectAll('.main-svg').remove(); fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') From 0454d649ffb35b371c04cd88806233d0a82d281b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 16:30:09 -0400 Subject: [PATCH 17/19] Fix canvas counting cases --- test/jasmine/tests/gl_plot_interact_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 7e06a5de92f..39682498707 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1286,7 +1286,7 @@ describe('Test gl plot side effects', function() { return Plotly.plot(gd, data); }).then(function() { - countCanvases(1); + countCanvases(3); return Plotly.purge(gd); }).then(function() { @@ -1294,7 +1294,7 @@ describe('Test gl plot side effects', function() { return Plotly.plot(gd, data); }).then(function() { - countCanvases(1); + countCanvases(3); return Plotly.deleteTraces(gd, [0]); }).then(function() { From 9b73d70584b0fd56daca0107cd83f7ae565725e8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 18:19:50 -0400 Subject: [PATCH 18/19] Make proper canvas recycling --- src/plot_api/plot_api.js | 46 +++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 3097eb0e9c9..5d536b1d504 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -194,24 +194,30 @@ Plotly.plot = function(gd, data, layout, config) { } } - if(fullLayout._hasCategory('gl') && fullLayout._glcanvas.empty()) { - fullLayout._glcanvas.enter().append('canvas') - .attr('class', function(d) { - return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); - }) - .style('position', 'absolute') - .style('top', 0) - .style('left', 0) - .style('width', '100%') - .style('height', '100%') - .style('pointer-events', 'none') - .style('overflow', 'visible'); - } - - fullLayout._glcanvas + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._hasCategory('gl') ? [{ + key: 'contextLayer' + }, { + key: 'focusLayer' + }, { + key: 'pickLayer' + }] : []); + + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style('position', 'absolute') + .style('top', 0) + .style('left', 0) + .style('width', '100%') + .style('height', '100%') + .style('pointer-events', 'none') + .style('overflow', 'visible') .attr('width', fullLayout.width) .attr('height', fullLayout.height); + fullLayout._glcanvas.exit().remove(); + return Lib.syncOrAsync([ subroutines.layoutStyles ], gd); @@ -3049,15 +3055,7 @@ function makePlotFramework(gd) { fullLayout._glcontainer.enter().append('div') .classed('gl-container', true); - - fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas') - .data([{ - key: 'contextLayer' - }, { - key: 'focusLayer' - }, { - key: 'pickLayer' - }]); + fullLayout._glcanvas; fullLayout._paperdiv.selectAll('.main-svg').remove(); From dbc23e53f869bf0b2ed6918ea95f12f0c89f0845 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 18:27:48 -0400 Subject: [PATCH 19/19] Poke CI --- src/plot_api/plot_api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 5d536b1d504..c17f12befaa 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3055,7 +3055,9 @@ function makePlotFramework(gd) { fullLayout._glcontainer.enter().append('div') .classed('gl-container', true); - fullLayout._glcanvas; + + // That is initialized in drawFramework if there are `gl` traces + fullLayout._glcanvas = null; fullLayout._paperdiv.selectAll('.main-svg').remove();