diff --git a/src/plots/polar/index.js b/src/plots/polar/index.js index 9c8695245f6..4233661e3fe 100644 --- a/src/plots/polar/index.js +++ b/src/plots/polar/index.js @@ -53,6 +53,9 @@ function plot(gd) { function clean(newFullData, newFullLayout, oldFullData, oldFullLayout) { var oldIds = oldFullLayout._subplots[name] || []; + var hadGl = (oldFullLayout._has && oldFullLayout._has('gl')); + var hasGl = (newFullLayout._has && newFullLayout._has('gl')); + var mustCleanScene = hadGl && !hasGl; for(var i = 0; i < oldIds.length; i++) { var id = oldIds[i]; @@ -66,6 +69,11 @@ function clean(newFullData, newFullLayout, oldFullData, oldFullLayout) { oldSubplot.clipPaths[k].remove(); } } + + if(mustCleanScene && oldSubplot._scene) { + oldSubplot._scene.destroy(); + oldSubplot._scene = null; + } } } diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index c3068932a5b..5c1eddeb569 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -42,8 +42,8 @@ function calc(gd, trace) { var xa = AxisIDs.getFromId(gd, trace.xaxis); var ya = AxisIDs.getFromId(gd, trace.yaxis); var subplot = fullLayout._plots[trace.xaxis + trace.yaxis]; - var count = trace._length; - var count2 = count * 2; + var len = trace._length; + var len2 = len * 2; var stash = {}; var i, xx, yy; @@ -52,8 +52,8 @@ function calc(gd, trace) { // we need hi-precision for scatter2d, // regl-scatter2d uses NaNs for bad/missing values - var positions = new Array(count2); - for(i = 0; i < count; i++) { + var positions = new Array(len2); + for(i = 0; i < len; i++) { xx = x[i]; yy = y[i]; positions[i * 2] = xx === BADNUM ? NaN : xx; @@ -61,12 +61,12 @@ function calc(gd, trace) { } if(xa.type === 'log') { - for(i = 0; i < count2; i += 2) { + for(i = 0; i < len2; i += 2) { positions[i] = xa.c2l(positions[i]); } } if(ya.type === 'log') { - for(i = 1; i < count2; i += 2) { + for(i = 1; i < len2; i += 2) { positions[i] = ya.c2l(positions[i]); } } @@ -77,8 +77,8 @@ function calc(gd, trace) { // FIXME: delegate this to webworker stash.tree = cluster(positions); } else { - var ids = stash.ids = new Array(count); - for(i = 0; i < count; i++) { + var ids = stash.ids = new Array(len); + for(i = 0; i < len; i++) { ids[i] = i; } } @@ -92,12 +92,9 @@ function calc(gd, trace) { // For graphs with very large number of points and array marker.size, // use average marker size instead to speed things up. setFirstScatter(fullLayout, trace); - var ppad; - if(count < TOO_MANY_POINTS) { - ppad = calcMarkerSize(trace, count); - } else if(opts.marker) { - ppad = 2 * (opts.marker.sizeAvg || Math.max(opts.marker.size, 3)); - } + var ppad = len < TOO_MANY_POINTS ? + calcMarkerSize(trace, len) : + 2 * (opts.marker.sizeAvg || Math.max(opts.marker.size, 3)); calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); if(opts.errorX) expandForErrorBars(trace, xa, opts.errorX); if(opts.errorY) expandForErrorBars(trace, ya, opts.errorY); @@ -111,7 +108,7 @@ function calc(gd, trace) { // FIXME: organize it in a more appropriate manner, probably in sceneOptions // put point-cluster instance for optimized regl calc - if(opts.marker && count >= TOO_MANY_POINTS) { + if(opts.marker && len >= TOO_MANY_POINTS) { opts.marker.cluster = stash.tree; } @@ -133,8 +130,6 @@ function calc(gd, trace) { stash.x = x; stash.y = y; stash.positions = positions; - stash.count = count; - scene.count++; return [{x: false, y: false, t: stash, trace: trace}]; @@ -554,6 +549,7 @@ function plot(gd, subplot, cdata) { var trace = cd0.trace; var stash = cd0.t; var index = stash.index; + var len = trace._length; var x = stash.x; var y = stash.y; @@ -574,7 +570,7 @@ function plot(gd, subplot, cdata) { selDict[selPts[j]] = 1; } var unselPts = []; - for(j = 0; j < stash.count; j++) { + for(j = 0; j < len; j++) { if(!selDict[j]) unselPts.push(j); } scene.unselectBatch[index] = unselPts; @@ -585,9 +581,9 @@ function plot(gd, subplot, cdata) { // - spin that in a webworker // - compute selection from polygons in data coordinates // (maybe just for linear axes) - var xpx = stash.xpx = new Array(stash.count); - var ypx = stash.ypx = new Array(stash.count); - for(j = 0; j < stash.count; j++) { + var xpx = stash.xpx = new Array(len); + var ypx = stash.ypx = new Array(len); + for(j = 0; j < len; j++) { xpx[j] = xaxis.c2p(x[j]); ypx[j] = yaxis.c2p(y[j]); } @@ -849,6 +845,7 @@ function selectPoints(searchInfo, selectionTester) { var selection = []; var trace = cd[0].trace; var stash = cd[0].t; + var len = trace._length; var x = stash.x; var y = stash.y; var scene = stash._scene; @@ -868,7 +865,7 @@ function selectPoints(searchInfo, selectionTester) { var i; if(selectionTester !== false && !selectionTester.degenerate) { els = [], unels = []; - for(i = 0; i < stash.count; i++) { + for(i = 0; i < len; i++) { if(selectionTester.contains([stash.xpx[i], stash.ypx[i]], false, i, searchInfo)) { els.push(i); selection.push({ @@ -882,7 +879,7 @@ function selectPoints(searchInfo, selectionTester) { } } } else { - unels = arrayRange(stash.count); + unels = arrayRange(len); } // make sure selectBatch is created @@ -916,6 +913,7 @@ function selectPoints(searchInfo, selectionTester) { function styleTextSelection(cd) { var cd0 = cd[0]; + var trace = cd0.trace; var stash = cd0.t; var scene = stash._scene; var index = stash.index; @@ -932,8 +930,7 @@ function styleTextSelection(cd) { var utc = unselOpts.color; var base = baseOpts.color; var hasArrayBase = Array.isArray(base); - opts.color = new Array(stash.count); - + opts.color = new Array(trace._length); for(i = 0; i < els.length; i++) { j = els[i]; @@ -965,7 +962,6 @@ module.exports = { hoverPoints: hoverPoints, selectPoints: selectPoints, - sceneOptions: sceneOptions, sceneUpdate: sceneUpdate, calcHover: calcHover, diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index f317682eb90..b2f7f67f17f 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -13,47 +13,63 @@ var isNumeric = require('fast-isnumeric'); var ScatterGl = require('../scattergl'); var calcColorscales = require('../scatter/colorscale_calc'); +var calcMarkerSize = require('../scatter/calc').calcMarkerSize; +var convert = require('../scattergl/convert'); + +var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); var makeHoverPointText = require('../scatterpolar/hover').makeHoverPointText; -var subTypes = require('../scatter/subtypes'); var TOO_MANY_POINTS = require('../scattergl/constants').TOO_MANY_POINTS; -function calc(container, trace) { - var layout = container._fullLayout; +function calc(gd, trace) { + var fullLayout = gd._fullLayout; var subplotId = trace.subplot; - var radialAxis = layout[subplotId].radialaxis; - var angularAxis = layout[subplotId].angularaxis; + var radialAxis = fullLayout[subplotId].radialaxis; + var angularAxis = fullLayout[subplotId].angularaxis; var rArray = radialAxis.makeCalcdata(trace, 'r'); var thetaArray = angularAxis.makeCalcdata(trace, 'theta'); + var len = trace._length; var stash = {}; - if(trace._length < rArray.length) rArray = rArray.slice(0, trace._length); - if(trace._length < thetaArray.length) thetaArray = thetaArray.slice(0, trace._length); - - calcColorscales(trace); + if(len < rArray.length) rArray = rArray.slice(0, len); + if(len < thetaArray.length) thetaArray = thetaArray.slice(0, len); stash.r = rArray; stash.theta = thetaArray; - trace._extremes.x = Axes.findExtremes(radialAxis, rArray, {tozero: true}); + calcColorscales(trace); + + // only compute 'style' options in calc, as position options + // depend on the radial range and must be set in plot + var opts = stash.opts = convert.style(gd, trace); + + // For graphs with very large number of points and array marker.size, + // use average marker size instead to speed things up. + var ppad = len < TOO_MANY_POINTS ? + calcMarkerSize(trace, len) : + 2 * (opts.marker.sizeAvg || Math.max(opts.marker.size, 3)); + trace._extremes.x = Axes.findExtremes(radialAxis, rArray, {ppad: ppad}); return [{x: false, y: false, t: stash, trace: trace}]; } -function plot(container, subplot, cdata) { +function plot(gd, subplot, cdata) { + if(!cdata.length) return; + var radialAxis = subplot.radialAxis; var angularAxis = subplot.angularAxis; + var scene = ScatterGl.sceneUpdate(gd, subplot); - var scene = ScatterGl.sceneUpdate(container, subplot); - - cdata.forEach(function(cdscatter, traceIndex) { + cdata.forEach(function(cdscatter) { if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; var cd = cdscatter[0]; var trace = cd.trace; var stash = cd.t; + var len = trace._length; var rArray = stash.r; var thetaArray = stash.theta; + var opts = stash.opts; var i; var subRArray = rArray.slice(); @@ -67,12 +83,11 @@ function plot(container, subplot, cdata) { } } - var count = rArray.length; - var positions = new Array(count * 2); - var x = Array(count); - var y = Array(count); + var positions = new Array(len * 2); + var x = Array(len); + var y = Array(len); - for(i = 0; i < count; i++) { + for(i = 0; i < len; i++) { var r = subRArray[i]; var xx, yy; @@ -88,43 +103,56 @@ function plot(container, subplot, cdata) { y[i] = positions[i * 2 + 1] = yy; } - var options = ScatterGl.sceneOptions(container, subplot, trace, positions); - - // set flags to create scene renderers - if(options.fill && !scene.fill2d) scene.fill2d = true; - if(options.marker && !scene.scatter2d) scene.scatter2d = true; - if(options.line && !scene.line2d) scene.line2d = true; - if((options.errorX || options.errorY) && !scene.error2d) scene.error2d = true; - if(options.text && !scene.glText) scene.glText = true; - stash.tree = cluster(positions); // FIXME: see scattergl.js#109 - if(options.marker && count >= TOO_MANY_POINTS) { - options.marker.cluster = stash.tree; + if(opts.marker && len >= TOO_MANY_POINTS) { + opts.marker.cluster = stash.tree; } - // bring positions to selected/unselected options - if(subTypes.hasMarkers(trace)) { - options.markerSel.positions = options.markerUnsel.positions = options.marker.positions; + if(opts.marker) { + opts.markerSel.positions = opts.markerUnsel.positions = opts.marker.positions = positions; } - // save scene options batch - scene.lineOptions.push(options.line); - scene.errorXOptions.push(options.errorX); - scene.errorYOptions.push(options.errorY); - scene.fillOptions.push(options.fill); - scene.markerOptions.push(options.marker); - scene.markerSelectedOptions.push(options.markerSel); - scene.markerUnselectedOptions.push(options.markerUnsel); - scene.textOptions.push(options.text); - scene.textSelectedOptions.push(options.textSel); - scene.textUnselectedOptions.push(options.textUnsel); - scene.count = cdata.length; - - // stash scene ref - stash._scene = scene; - stash.index = traceIndex; + if(opts.line && positions.length > 1) { + Lib.extendFlat( + opts.line, + convert.linePositions(gd, trace, positions) + ); + } + + if(opts.text) { + Lib.extendFlat( + opts.text, + {positions: positions}, + convert.textPosition(gd, trace, opts.text, opts.marker) + ); + Lib.extendFlat( + opts.textSel, + {positions: positions}, + convert.textPosition(gd, trace, opts.text, opts.markerSel) + ); + Lib.extendFlat( + opts.textUnsel, + {positions: positions}, + convert.textPosition(gd, trace, opts.text, opts.markerUnsel) + ); + } + + if(opts.fill && !scene.fill2d) scene.fill2d = true; + if(opts.marker && !scene.scatter2d) scene.scatter2d = true; + if(opts.line && !scene.line2d) scene.line2d = true; + if(opts.text && !scene.glText) scene.glText = true; + + scene.lineOptions.push(opts.line); + scene.fillOptions.push(opts.fill); + scene.markerOptions.push(opts.marker); + scene.markerSelectedOptions.push(opts.markerSel); + scene.markerUnselectedOptions.push(opts.markerUnsel); + scene.textOptions.push(opts.text); + scene.textSelectedOptions.push(opts.textSel); + scene.textUnselectedOptions.push(opts.textUnsel); + stash.x = x; stash.y = y; stash.rawx = x; @@ -132,10 +160,12 @@ function plot(container, subplot, cdata) { stash.r = rArray; stash.theta = thetaArray; stash.positions = positions; - stash.count = count; + stash._scene = scene; + stash.index = scene.count; + scene.count++; }); - return ScatterGl.plot(container, subplot, cdata); + return ScatterGl.plot(gd, subplot, cdata); } function hoverPoints(pointData, xval, yval, hovermode) { diff --git a/test/image/baselines/glpolar_scatter.png b/test/image/baselines/glpolar_scatter.png index d631ae1d118..36001048a0e 100644 Binary files a/test/image/baselines/glpolar_scatter.png and b/test/image/baselines/glpolar_scatter.png differ diff --git a/test/image/baselines/glpolar_style.png b/test/image/baselines/glpolar_style.png index 8e27ce4e66c..cd7b776f669 100644 Binary files a/test/image/baselines/glpolar_style.png and b/test/image/baselines/glpolar_style.png differ diff --git a/test/image/baselines/glpolar_subplots.png b/test/image/baselines/glpolar_subplots.png index 7fd76151d3f..645d2858869 100644 Binary files a/test/image/baselines/glpolar_subplots.png and b/test/image/baselines/glpolar_subplots.png differ diff --git a/test/image/baselines/polar_r0dr-theta0dtheta.png b/test/image/baselines/polar_r0dr-theta0dtheta.png index b11d35e2c42..b41f8c9497f 100644 Binary files a/test/image/baselines/polar_r0dr-theta0dtheta.png and b/test/image/baselines/polar_r0dr-theta0dtheta.png differ diff --git a/test/jasmine/tests/scatterpolargl_test.js b/test/jasmine/tests/scatterpolargl_test.js index 85b24df3704..d3e279577d7 100644 --- a/test/jasmine/tests/scatterpolargl_test.js +++ b/test/jasmine/tests/scatterpolargl_test.js @@ -1,10 +1,12 @@ var Plotly = require('@lib'); var Lib = require('@src/lib'); +var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var failTest = require('../assets/fail_test'); var mouseEvent = require('../assets/mouse_event'); +var readPixel = require('../assets/read_pixel'); var customAssertions = require('../assets/custom_assertions'); var assertHoverLabelContent = customAssertions.assertHoverLabelContent; @@ -102,3 +104,150 @@ describe('Test scatterpolargl hover:', function() { }); }); }); + +describe('Test scatterpolargl interactions:', function() { + var gd; + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + function countCanvases() { + return d3.selectAll('canvas').size(); + } + + function totalPixels() { + return readPixel(gd.querySelector('.gl-canvas-context'), 0, 0, 400, 400) + .reduce(function(acc, v) { return acc + v; }, 0); + } + + it('@gl should be able to toggle from svg to gl', function(done) { + gd = createGraphDiv(); + + var scene; + + Plotly.plot(gd, [{ + type: 'scatterpolar', + r: [1, 2, 1], + }], { + width: 400, + height: 400 + }) + .then(function() { + expect(countCanvases()).toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1); + + return Plotly.restyle(gd, 'type', 'scatterpolargl'); + }) + .then(function() { + expect(countCanvases()).toBe(3); + expect(totalPixels()).not.toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0); + + scene = gd._fullLayout.polar._subplot._scene; + spyOn(scene, 'destroy').and.callThrough(); + + return Plotly.restyle(gd, 'type', 'scatterpolar'); + }) + .then(function() { + expect(countCanvases()).toBe(0); + expect(scene.destroy).toHaveBeenCalledTimes(1); + expect(gd._fullLayout.polar._subplot._scene).toBe(null); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1); + + return Plotly.restyle(gd, 'type', 'scatterpolargl'); + }) + .then(function() { + expect(countCanvases()).toBe(3); + // this here was failing before + // https://github.com/plotly/plotly.js/issues/3094 + // got fixed + expect(totalPixels()).not.toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0); + }) + .catch(failTest) + .then(done); + }); + + it('@gl should be able to toggle from svg to gl (on graph with scattergl subplot)', function(done) { + gd = createGraphDiv(); + + var sceneXY, scenePolar; + + Plotly.plot(gd, [{ + type: 'scattergl', + y: [1, 2, 1] + }, { + type: 'scatterpolargl', + r: [1, 2, 1] + }], { + grid: {rows: 1, columns: 2}, + yaxis: {domain: {column: 0}}, + polar: {domain: {column: 1}}, + width: 400, + height: 400 + }) + .then(function() { + expect(countCanvases()).toBe(3); + expect(totalPixels()).not.toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0); + + sceneXY = gd._fullLayout._plots.xy._scene; + spyOn(sceneXY, 'destroy').and.callThrough(); + + scenePolar = gd._fullLayout.polar._subplot._scene; + spyOn(scenePolar, 'destroy').and.callThrough(); + + return Plotly.restyle(gd, 'type', 'scatterpolar', [1]); + }) + .then(function() { + expect(countCanvases()).toBe(3); + expect(totalPixels()).not.toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1); + + expect(sceneXY.destroy).toHaveBeenCalledTimes(0); + expect(gd._fullLayout._plots.xy._scene).not.toBe(null); + + // N.B. does not destroy scene in this case, + // we don't need as the same gl canvases are still there + expect(scenePolar.destroy).toHaveBeenCalledTimes(0); + expect(gd._fullLayout.polar._subplot._scene).not.toBe(null); + + return Plotly.restyle(gd, 'type', 'scatterpolargl', [1]); + }) + .then(function() { + expect(countCanvases()).toBe(3); + expect(totalPixels()).not.toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0); + + return Plotly.restyle(gd, 'type', 'scatter', [0]); + }) + .then(function() { + expect(countCanvases()).toBe(3); + expect(totalPixels()).not.toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1); + + // Similarly, does not destroy scene in this case, + // we don't need as the same gl canvases are still there + expect(sceneXY.destroy).toHaveBeenCalledTimes(0); + expect(gd._fullLayout._plots.xy._scene).not.toBe(null); + + expect(scenePolar.destroy).toHaveBeenCalledTimes(0); + expect(gd._fullLayout.polar._subplot._scene).not.toBe(null); + + return Plotly.restyle(gd, 'type', 'scatterpolar', [1]); + }) + .then(function() { + expect(countCanvases()).toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(2); + + expect(sceneXY.destroy).toHaveBeenCalledTimes(1); + expect(gd._fullLayout._plots.xy._scene).toBe(null); + expect(scenePolar.destroy).toHaveBeenCalledTimes(1); + expect(gd._fullLayout.polar._subplot._scene).toBe(null); + }) + .catch(failTest) + .then(done); + }); +});