diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index e27eff1cb9b..66baee79534 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -154,6 +154,7 @@ exports.cleanLayout = function(layout) { // clean old Camera coords var cameraposition = scene.cameraposition; + if(Array.isArray(cameraposition) && cameraposition[0].length === 4) { var rotation = cameraposition[0], center = cameraposition[1], diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index e4a1e83d9bb..205994bf1a6 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -195,6 +195,43 @@ Plotly.plot = function(gd, data, layout, config) { } } + if(!fullLayout._glcanvas && fullLayout._has('gl')) { + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data([{ + key: 'contextLayer', + context: true, + pick: false + }, { + key: 'focusLayer', + context: false, + pick: false + }, { + key: 'pickLayer', + context: false, + pick: true + }], function(d) { return d.key; }); + + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style({ + 'position': 'absolute', + 'top': 0, + 'left': 0, + 'width': '100%', + 'height': '100%', + 'overflow': 'visible' + }) + .attr('width', fullLayout.width) + .attr('height', fullLayout.height); + + fullLayout._glcanvas.filter(function(d) { + return !d.pick; + }).style({ + 'pointer-events': 'none' + }); + } + return Lib.syncOrAsync([ subroutines.layoutStyles ], gd); @@ -1993,7 +2030,7 @@ function _relayout(gd, aobj) { else flags.plot = true; } else { - if(fullLayout._has('gl2d') && + if((fullLayout._has('gl2d') || fullLayout._has('regl')) && (ai === 'dragmode' && (vi === 'lasso' || vi === 'select') && !(vOld === 'lasso' || vOld === 'select')) @@ -2764,11 +2801,15 @@ function makePlotFramework(gd) { // right, rather than enter/exit which can muck up the order // TODO: sort out all the ordering so we don't have to // explicitly delete anything + // FIXME: parcoords reuses this object, not the best pattern fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') - .data([0]); + .data([{}]); fullLayout._glcontainer.enter().append('div') .classed('gl-container', true); + // That is initialized in drawFramework if there are `gl` traces + fullLayout._glcanvas = null; + fullLayout._paperdiv.selectAll('.main-svg').remove(); fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 06b8e24c072..1b7ca54a061 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -711,14 +711,35 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { return ax._length * (1 - scaleFactor) * FROM_TL[ax.constraintoward || 'middle']; } - for(i = 0; i < subplots.length; i++) { + // clear gl frame, if any, since we preserve drawing buffer + // FIXME: code duplication with cartesian.plot + if(fullLayout._glcanvas && fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function(d) { + if(d.regl) { + d.regl.clear({ + color: true + }); + } + }); + } + for(i = 0; i < subplots.length; i++) { var subplot = plotinfos[subplots[i]], xa2 = subplot.xaxis, ya2 = subplot.yaxis, editX2 = editX && !xa2.fixedrange && (xa.indexOf(xa2) !== -1), editY2 = editY && !ya2.fixedrange && (ya.indexOf(ya2) !== -1); + // scattergl translate + if(subplot._scene && subplot._scene.update) { + // FIXME: possibly we could update axis internal _r and _rl here + var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), + yaRange = Lib.simpleMap(ya2.range, ya2.r2l); + subplot._scene.update( + {range: [xaRange[0], yaRange[0], xaRange[1], yaRange[1]]} + ); + } + if(editX2) { xScaleFactor2 = xScaleFactor; clipDx = ew ? viewBox[0] : getShift(xa2, xScaleFactor2); diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 3ca43838d02..a9db08dd172 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -48,6 +48,17 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { } } + // clear gl frame, if any, since we preserve drawing buffer + if(fullLayout._glcanvas && fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function(d) { + if(d.regl) { + d.regl.clear({ + color: true + }); + } + }); + } + for(i = 0; i < subplots.length; i++) { var subplot = subplots[i], subplotInfo = fullLayout._plots[subplot]; diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index be7226e5590..e1429f6e911 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -262,7 +262,6 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { throttle.done(throttleID).then(function() { throttle.clear(throttleID); - if(!dragged && numclicks === 2) { // clear selection on doubleclick outlines.remove(); diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index e0db40f622a..2d8b3a74ffb 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -112,7 +112,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, @@ -140,7 +140,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( @@ -157,9 +157,11 @@ proto.makeFramework = function() { mouseContainer.style.position = 'absolute'; mouseContainer.style['pointer-events'] = 'auto'; + this.pickCanvas = this.container.querySelector('.gl-canvas-pick'); + + // append canvas, hover svg and mouse div to container var container = this.container; - container.appendChild(canvas); container.appendChild(svgContainer); container.appendChild(mouseContainer); @@ -370,7 +372,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); @@ -533,8 +534,10 @@ proto.updateTraces = function(fullData, calcData) { proto.updateFx = function(dragmode) { // switch to svg interactions in lasso/select mode if(dragmode === 'lasso' || dragmode === 'select') { + this.pickCanvas.style['pointer-events'] = 'none'; this.mouseContainer.style['pointer-events'] = 'none'; } else { + this.pickCanvas.style['pointer-events'] = 'auto'; this.mouseContainer.style['pointer-events'] = 'auto'; } diff --git a/src/plots/plots.js b/src/plots/plots.js index dec9828d58a..5d0515a0a42 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -569,15 +569,29 @@ plots.createTransitionData = function(gd) { // helper function to be bound to fullLayout to check // whether a certain plot type is present on plot +// or trace has a category plots._hasPlotType = function(category) { + // check plot + var basePlotModules = this._basePlotModules || []; + var i; - for(var i = 0; i < basePlotModules.length; i++) { + for(i = 0; i < basePlotModules.length; i++) { var _module = basePlotModules[i]; if(_module.name === category) return true; } + // check trace + var modules = this._modules || []; + + for(i = 0; i < modules.length; i++) { + var _ = modules[i]; + if(_.categories && _.categories.indexOf(category) >= 0) { + return true; + } + } + return false; }; @@ -595,6 +609,15 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou var hasPaper = !!oldFullLayout._paper; var hasInfoLayer = !!oldFullLayout._infolayer; + var hadGl = oldFullLayout._has && oldFullLayout._has('gl'); + var hasGl = newFullLayout._has && newFullLayout._has('gl'); + + if(hadGl && !hasGl) { + if(oldFullLayout._glcontainer !== undefined) { + oldFullLayout._glcontainer.selectAll('.gl-canvas').remove(); + oldFullLayout._glcanvas = null; + } + } oldLoop: for(i = 0; i < oldFullData.length; i++) { @@ -1308,7 +1331,11 @@ plots.purge = function(gd) { // a new plot, and may have been set outside of our scope. var fullLayout = gd._fullLayout || {}; - if(fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove(); + if(fullLayout._glcontainer !== undefined) { + fullLayout._glcontainer.selectAll('.gl-canvas').remove(); + fullLayout._glcontainer.remove(); + fullLayout._glcanvas = null; + } if(fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove(); // remove modebar @@ -1326,6 +1353,9 @@ plots.purge = function(gd) { } } + // remove any planned throttles + Lib.clearThrottle(); + // data and layout delete gd.data; delete gd.layout; diff --git a/src/traces/contourgl/index.js b/src/traces/contourgl/index.js index 9dfb61d0de5..fdb89747810 100644 --- a/src/traces/contourgl/index.js +++ b/src/traces/contourgl/index.js @@ -23,7 +23,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/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index e5be3285b75..c562787b289 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'; @@ -28,9 +27,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,22 +37,20 @@ 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) { + 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, + x: 0, + y: 0, + width: canvas.width, + height: canvas.height, preserveAspectRatio: 'none' }); } diff --git a/src/traces/parcoords/index.js b/src/traces/parcoords/index.js index 920cec76473..fb71f73692d 100644 --- a/src/traces/parcoords/index.js +++ b/src/traces/parcoords/index.js @@ -19,7 +19,7 @@ Parcoords.colorbar = require('./colorbar'); Parcoords.moduleType = 'trace'; Parcoords.name = 'parcoords'; Parcoords.basePlotModule = require('./base_plot'); -Parcoords.categories = ['gl', 'noOpacity']; +Parcoords.categories = ['gl', 'regl', 'noOpacity']; Parcoords.meta = { description: [ 'Parallel coordinates for multidimensional exploratory data analysis.', diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 92776dabf0a..b63b14b13f8 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -8,9 +8,8 @@ 'use strict'; -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'); @@ -165,7 +164,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, + domain = model.domain; + + 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: {}, @@ -189,13 +200,7 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDim var points = makePoints(sampleCount, dimensionCount, initialDims, color); var attributes = makeAttributes(sampleCount, points); - var regl = createREGL({ - canvas: canvasGL, - attributes: { - preserveDrawingBuffer: true, - antialias: !pick - } - }); + var regl = d.regl; var paletteTexture = regl.texture({ shape: [256, 1], @@ -248,6 +253,13 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDim } }, + viewport: { + x: regl.prop('viewportX'), + y: regl.prop('viewportY'), + width: regl.prop('viewportWidth'), + height: regl.prop('viewportHeight') + }, + dither: false, vert: pick ? pickVertexShaderSource : vertexShaderSource, @@ -297,7 +309,7 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDim 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);});}); @@ -341,10 +353,15 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDim 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, - scissorHeight: canvasPanelSizeY + scissorY: y + model.pad.b + model.layoutHeight * domain.y[0], + 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 }; } @@ -413,11 +430,15 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDim return pixelArray; } + function destroy() { + paletteTexture.destroy(); + } + return { setColorDomain: setColorDomain, render: renderGLParcoords, readPixel: readPixel, readPixels: readPixels, - destroy: regl.destroy + destroy: destroy }; }; diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 602d3ddf121..96b83392650 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -161,6 +161,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, @@ -228,18 +231,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(c.cn.axisExtentText, true) @@ -248,8 +239,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; @@ -296,38 +286,25 @@ module.exports = function(root, svg, styledData, layout, callbacks) { .map(model.bind(0, layout)) .map(viewModel); - root.selectAll('.' + c.cn.parcoordsLineLayers).remove(); - - var parcoordsLineLayers = root.selectAll('.' + c.cn.parcoordsLineLayers) - .data(vm, keyFun); - - parcoordsLineLayers.enter() - .insert('div', '.' + svg.attr('class').split(' ').join(' .')) // not hardcoding .main-svg - .classed(c.cn.parcoordsLineLayers, 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('.gl-canvas') + .each(function(d) { + // FIXME: figure out how to handle multiple instances + d.viewModel = vm[0]; + d.model = d.viewModel ? d.viewModel.model : null; }); - var parcoordsLineLayer = parcoordsLineLayers.selectAll('.' + c.cn.parcoordsLineLayer) - .data(lineLayerModel, keyFun); - var tweakables = {renderers: [], dimensions: []}; var lastHovered = null; - parcoordsLineLayer.enter() - .append('canvas') - .attr('class', function(d) {return c.cn.parcoordsLineLayer + ' ' + (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', 'absolute') - .filter(function(d) {return d.pick;}) + parcoordsLineLayer + .filter(function(d) { + return d.pick; + }) .on('mousemove', function(d) { if(linePickActive && d.lineLayer && callbacks && callbacks.hover) { var event = d3.event; @@ -364,14 +341,6 @@ module.exports = function(root, svg, styledData, layout, callbacks) { }); 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)'); @@ -473,8 +442,9 @@ module.exports = function(root, svg, styledData, layout, callbacks) { }); parcoordsLineLayer + .filter(function(d) {return !!d.viewModel;}) .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); @@ -508,8 +478,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; @@ -524,9 +494,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) { @@ -737,13 +707,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; } } @@ -764,9 +734,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..433938ec1b9 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -9,12 +9,27 @@ 'use strict'; var parcoords = require('./parcoords'); +var createRegl = require('regl'); 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; + + // make sure proper regl instances are created + fullLayout._glcanvas.each(function(d) { + if(d.regl) return; + d.regl = createRegl({ + canvas: this, + attributes: { + antialias: !d.pick, + preserveDrawingBuffer: true + }, + pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio + }); + }); var gdDimensions = {}; var gdDimensionsOriginalOrder = {}; @@ -98,6 +113,7 @@ module.exports = function plot(gd, cdparcoords) { parcoords( root, svg, + container, cdparcoords, { width: size.w, 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/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/test/image/baselines/gl2d_parcoords_2.png b/test/image/baselines/gl2d_parcoords_2.png index 2b19d3c107f..a31ada48935 100644 Binary files a/test/image/baselines/gl2d_parcoords_2.png and b/test/image/baselines/gl2d_parcoords_2.png differ diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 54dd23ee1aa..70609851a15 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -491,7 +491,7 @@ describe('@noCI Test gl2d lasso/select:', function() { function select(path) { return new Promise(function(resolve, reject) { gd.once('plotly_selected', resolve); - setTimeout(function() { reject('did not trigger *plotly_selected*');}, 100); + setTimeout(function() { reject('did not trigger *plotly_selected*');}, 200); drag(path); }); } diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 8d2cf6a11c3..f52f2d80485 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1290,7 +1290,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() { @@ -1298,7 +1298,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() { @@ -1307,6 +1307,30 @@ describe('Test gl plot side effects', function() { return Plotly.purge(gd); }).then(done); }); + + it('should be able to switch trace type', function(done) { + Plotly.newPlot(gd, [{ + type: 'parcoords', + x: [1, 2, 3], + y: [2, 1, 2], + dimensions: [ + { + constraintrange: [200, 700], + label: 'Block height', + values: [321, 534, 542, 674, 31, 674, 124, 246, 456, 743] + } + ] + }]) + .then(function() { + expect(d3.selectAll('canvas').size()).toEqual(3); + + return Plotly.restyle(gd, 'type', 'scatter'); + }) + .then(function() { + expect(d3.selectAll('canvas').size()).toEqual(0); + }) + .then(done); + }); }); describe('Test gl2d interactions', function() { diff --git a/test/jasmine/tests/parcoords_test.js b/test/jasmine/tests/parcoords_test.js index 66efc5b3df0..a632a4d21d2 100644 --- a/test/jasmine/tests/parcoords_test.js +++ b/test/jasmine/tests/parcoords_test.js @@ -788,7 +788,7 @@ describe('@noCI parcoords', function() { expect(gd.data.length).toEqual(1); Plotly.deleteTraces(gd, 0).then(function() { - expect(d3.selectAll('.parcoords-line-layers').node()).toEqual(null); + expect(d3.selectAll('.gl-canvas').node(0)).toEqual(null); expect(gd.data.length).toEqual(0); done(); }); @@ -815,13 +815,13 @@ describe('@noCI parcoords', function() { return Plotly.deleteTraces(gd, [0]); }) .then(function() { - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(1); + expect(document.querySelectorAll('.gl-canvas').length).toEqual(3); expect(document.querySelectorAll('.y-axis').length).toEqual(7); expect(gd.data.length).toEqual(1); return Plotly.deleteTraces(gd, 0); }) .then(function() { - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(0); + expect(document.querySelectorAll('.gl-canvas').length).toEqual(0); expect(document.querySelectorAll('.y-axis').length).toEqual(0); expect(gd.data.length).toEqual(0); done(); @@ -869,13 +869,13 @@ describe('@noCI parcoords', function() { mockCopy2.data[0].domain = {x: [0.55, 1]}; mockCopy2.data[0].dimensions.splice(3, 4); - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(0); + expect(document.querySelectorAll('.gl-container').length).toEqual(0); Plotly.plot(gd, mockCopy) .then(function() { expect(1).toEqual(1); - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(1); + expect(document.querySelectorAll('.gl-container').length).toEqual(1); expect(gd.data.length).toEqual(1); return Plotly.plot(gd, mockCopy2); @@ -883,7 +883,7 @@ describe('@noCI parcoords', function() { .then(function() { expect(1).toEqual(1); - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(2); + expect(document.querySelectorAll('.gl-container').length).toEqual(1); expect(gd.data.length).toEqual(2); done(); @@ -899,19 +899,18 @@ describe('@noCI parcoords', function() { mockCopy2.data[0].domain = {y: [0.65, 1]}; mockCopy2.data[0].dimensions.splice(3, 4); - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(0); + expect(document.querySelectorAll('.gl-container').length).toEqual(0); Plotly.plot(gd, mockCopy) .then(function() { - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(1); + expect(document.querySelectorAll('.gl-container').length).toEqual(1); expect(gd.data.length).toEqual(1); return Plotly.addTraces(gd, [mockCopy2.data[0]]); }) .then(function() { - - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(2); + expect(document.querySelectorAll('.gl-container').length).toEqual(1); expect(gd.data.length).toEqual(2); done(); @@ -945,12 +944,12 @@ describe('@noCI parcoords', function() { mockCopy2.data[0].dimensions[2].tickvals = [0, 1, 2, 2.5, 3]; mockCopy2.data[0].dimensions[2].values = mockCopy2.data[0].dimensions[2].values.map(numberUpdater); - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(0); + expect(document.querySelectorAll('.gl-container').length).toEqual(0); Plotly.plot(gd, mockCopy) .then(function() { - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(1); + expect(document.querySelectorAll('.gl-container').length).toEqual(1); expect(gd.data.length).toEqual(1); return Plotly.restyle(gd, { @@ -960,7 +959,7 @@ describe('@noCI parcoords', function() { }) .then(function() { - expect(document.querySelectorAll('.parcoords-line-layers').length).toEqual(1); + expect(document.querySelectorAll('.gl-container').length).toEqual(1); expect(gd.data.length).toEqual(1); done();