diff --git a/package.json b/package.json index ab53ee070f9..a33e5969c49 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "dependencies": { "@plotly/d3-sankey": "0.7.2", "alpha-shape": "^1.0.0", - "array-range": "^1.0.1", "canvas-fit": "^1.5.0", "color-normalize": "^1.3.0", "convex-hull": "^1.0.3", diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 407348c588f..c6ea11ee6d4 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -12,7 +12,6 @@ var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); var createError = require('regl-error2d'); var cluster = require('point-cluster'); -var arrayRange = require('array-range'); var Text = require('gl-text'); var Registry = require('../../registry'); @@ -116,7 +115,6 @@ function calc(gd, trace) { opts.marker.snap = stash.tree || TOO_MANY_POINTS; } - // save scene opts batch scene.lineOptions.push(opts.line); scene.errorXOptions.push(opts.errorX); scene.errorYOptions.push(opts.errorY); @@ -127,8 +125,9 @@ function calc(gd, trace) { scene.textOptions.push(opts.text); scene.textSelectedOptions.push(opts.textSel); scene.textUnselectedOptions.push(opts.textUnsel); + scene.selectBatch.push([]); + scene.unselectBatch.push([]); - // stash scene ref stash._scene = scene; stash.index = scene.count; stash.x = x; @@ -146,7 +145,6 @@ function expandForErrorBars(trace, ax, opts) { extremes.max = extremes.max.concat(errExt.max); } -// create scene options function sceneOptions(gd, subplot, trace, positions, x, y) { var opts = convert.style(gd, trace); @@ -193,13 +191,12 @@ function sceneOptions(gd, subplot, trace, positions, x, y) { return opts; } - // make sure scene exists on subplot, return it function sceneUpdate(gd, subplot) { var scene = subplot._scene; var resetOpts = { - // number of traces in subplot, since scene:subplot → 1:1 + // number of traces in subplot, since scene:subplot -> 1:1 count: 0, // whether scene requires init hook in plot call (dirty plot call) dirty: true, @@ -213,19 +210,20 @@ function sceneUpdate(gd, subplot) { errorYOptions: [], textOptions: [], textSelectedOptions: [], - textUnselectedOptions: [] + textUnselectedOptions: [], + // selection batches + selectBatch: [], + unselectBatch: [] }; + // regl- component stubs, initialized in dirty plot call var initOpts = { - selectBatch: null, - unselectBatch: null, - // regl- component stubs, initialized in dirty plot call fill2d: false, scatter2d: false, error2d: false, line2d: false, glText: false, - select2d: null + select2d: false }; if(!subplot._scene) { @@ -276,17 +274,22 @@ function sceneUpdate(gd, subplot) { if(scene.errorXOptions[i]) error2d.draw(i); if(scene.errorYOptions[i]) error2d.draw(i + count); } - if(scatter2d && scene.markerOptions[i] && (!selectBatch || !selectBatch[i])) { - scatter2d.draw(i); + if(scatter2d && scene.markerOptions[i]) { + if(unselectBatch[i].length) { + var arg = Lib.repeat([], scene.count); + arg[i] = unselectBatch[i]; + scatter2d.draw(arg); + } else if(!selectBatch[i].length) { + scatter2d.draw(i); + } } if(glText[i] && scene.textOptions[i]) { glText[i].render(); } } - if(scatter2d && select2d && selectBatch) { + if(select2d) { select2d.draw(selectBatch); - scatter2d.draw(unselectBatch); } scene.dirty = false; @@ -325,7 +328,7 @@ function sceneUpdate(gd, subplot) { }; } - // In case if we have scene from the last calc - reset data + // in case if we have scene from the last calc - reset data if(!scene.dirty) { Lib.extendFlat(scene, resetOpts); } @@ -363,6 +366,7 @@ function plot(gd, subplot, cdata) { return; } + var count = scene.count; var regl = fullLayout._glcanvas.data()[0].regl; // that is needed for fills @@ -383,28 +387,28 @@ function plot(gd, subplot, cdata) { scene.fill2d = createLine(regl); } if(scene.glText === true) { - scene.glText = new Array(scene.count); - for(i = 0; i < scene.count; i++) { + scene.glText = new Array(count); + for(i = 0; i < count; i++) { scene.glText[i] = new Text(regl); } } // update main marker options if(scene.glText) { - if(scene.count > scene.glText.length) { + if(count > scene.glText.length) { // add gl text marker - var textsToAdd = scene.count - scene.glText.length; + var textsToAdd = count - scene.glText.length; for(i = 0; i < textsToAdd; i++) { scene.glText.push(new Text(regl)); } - } else if(scene.count < scene.glText.length) { + } else if(count < scene.glText.length) { // remove gl text marker - var textsToRemove = scene.glText.length - scene.count; - var removedTexts = scene.glText.splice(scene.count, textsToRemove); + var textsToRemove = scene.glText.length - count; + var removedTexts = scene.glText.splice(count, textsToRemove); removedTexts.forEach(function(text) { text.destroy(); }); } - for(i = 0; i < scene.count; i++) { + for(i = 0; i < count; i++) { scene.glText[i].update(scene.textOptions[i]); } } @@ -437,7 +441,7 @@ function plot(gd, subplot, cdata) { } // fill requires linked traces, so we generate it's positions here - scene.fillOrder = Lib.repeat(null, scene.count); + scene.fillOrder = Lib.repeat(null, count); if(scene.fill2d) { scene.fillOptions = scene.fillOptions.map(function(fillOptions, i) { var cdscatter = cdata[i]; @@ -556,13 +560,11 @@ function plot(gd, subplot, cdata) { } // form batch arrays, and check for selected points - scene.selectBatch = null; - scene.unselectBatch = null; var dragmode = fullLayout.dragmode; var selectMode = dragmode === 'lasso' || dragmode === 'select'; var clickSelectEnabled = fullLayout.clickmode.indexOf('select') > -1; - for(i = 0; i < cdata.length; i++) { + for(i = 0; i < count; i++) { var cd0 = cdata[i][0]; var trace = cd0.trace; var stash = cd0.t; @@ -574,11 +576,6 @@ function plot(gd, subplot, cdata) { if(trace.selectedpoints || selectMode || clickSelectEnabled) { if(!selectMode) selectMode = true; - if(!scene.selectBatch) { - scene.selectBatch = []; - scene.unselectBatch = []; - } - // regenerate scene batch, if traces number changed during selection if(trace.selectedpoints) { var selPts = scene.selectBatch[index] = Lib.selIndices2selPoints(trace); @@ -610,21 +607,24 @@ function plot(gd, subplot, cdata) { } } - if(selectMode) { - // create select2d + // create scatter instance by cloning scatter2d if(!scene.select2d) { - // create scatter instance by cloning scatter2d scene.select2d = createScatter(fullLayout._glcanvas.data()[1].regl); } - if(scene.scatter2d && scene.selectBatch && scene.selectBatch.length) { - // update only traces with selection - scene.scatter2d.update(scene.markerUnselectedOptions.map(function(opts, i) { - return scene.selectBatch[i] ? opts : null; - })); + // use unselected styles on 'context' canvas + if(scene.scatter2d) { + var unselOpts = new Array(count); + for(i = 0; i < count; i++) { + unselOpts[i] = scene.selectBatch[i].length || scene.unselectBatch[i].length ? + scene.markerUnselectedOptions[i] : + {}; + } + scene.scatter2d.update(unselOpts); } + // use selected style on 'focus' canvas if(scene.select2d) { scene.select2d.update(scene.markerOptions); scene.select2d.update(scene.markerSelectedOptions); @@ -639,9 +639,9 @@ function plot(gd, subplot, cdata) { }); } } else { + // reset 'context' scatter2d opts to base opts, + // thus unsetting markerUnselectedOptions from selection if(scene.scatter2d) { - // reset scatter2d opts to base opts, - // thus unsetting markerUnselectedOptions from selection scene.scatter2d.update(scene.markerOptions); } } @@ -680,7 +680,6 @@ function plot(gd, subplot, cdata) { } } - function hoverPoints(pointData, xval, yval, hovermode) { var cd = pointData.cd; var stash = cd[0].t; @@ -758,7 +757,6 @@ function hoverPoints(pointData, xval, yval, hovermode) { return [pointData]; } - function calcHover(pointData, x, y, trace) { var xa = pointData.xa; var ya = pointData.ya; @@ -861,7 +859,6 @@ function calcHover(pointData, x, y, trace) { return pointData; } - function selectPoints(searchInfo, selectionTester) { var cd = searchInfo.cd; var selection = []; @@ -871,23 +868,23 @@ function selectPoints(searchInfo, selectionTester) { var x = stash.x; var y = stash.y; var scene = stash._scene; + var index = stash.index; if(!scene) return selection; var hasText = subTypes.hasText(trace); var hasMarkers = subTypes.hasMarkers(trace); var hasOnlyLines = !hasMarkers && !hasText; + if(trace.visible !== true || hasOnlyLines) return selection; + var els = []; + var unels = []; + // degenerate polygon does not enable selection // filter out points by visible scatter ones - var els = null; - var unels = null; - // FIXME: clearing selection does not work here - var i; if(selectionTester !== false && !selectionTester.degenerate) { - els = [], unels = []; - for(i = 0; i < len; i++) { + for(var i = 0; i < len; i++) { if(selectionTester.contains([stash.xpx[i], stash.ypx[i]], false, i, searchInfo)) { els.push(i); selection.push({ @@ -899,32 +896,27 @@ function selectPoints(searchInfo, selectionTester) { unels.push(i); } } - } else { - unels = arrayRange(len); } - // make sure selectBatch is created - if(!scene.selectBatch) { - scene.selectBatch = []; - scene.unselectBatch = []; - } + if(hasMarkers) { + var scatter2d = scene.scatter2d; - if(!scene.selectBatch[stash.index]) { - // enter every trace select mode - for(i = 0; i < scene.count; i++) { - scene.selectBatch[i] = []; - scene.unselectBatch[i] = []; - } - // we should turn scatter2d into unselected once we have any points selected - if(hasMarkers) { - scene.scatter2d.update(scene.markerUnselectedOptions); + if(!els.length && !unels.length) { + // reset to base styles when clearing + var baseOpts = new Array(scene.count); + baseOpts[index] = scene.markerOptions[index]; + scatter2d.update.apply(scatter2d, baseOpts); + } else if(!scene.selectBatch[index].length && !scene.unselectBatch[index].length) { + // set unselected styles on 'context' canvas (if not done already) + var unselOpts = new Array(scene.count); + unselOpts[index] = scene.markerUnselectedOptions[index]; + scatter2d.update.apply(scatter2d, unselOpts); } } - scene.selectBatch[stash.index] = els; - scene.unselectBatch[stash.index] = unels; + scene.selectBatch[index] = els; + scene.unselectBatch[index] = unels; - // update text options if(hasText) { styleTextSelection(cd); } @@ -946,7 +938,7 @@ function styleTextSelection(cd) { var opts = Lib.extendFlat({}, baseOpts); var i, j; - if(els && unels) { + if(els.length || unels.length) { var stc = selOpts.color; var utc = unselOpts.color; var base = baseOpts.color; diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index 222096174ea..b94984217d5 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -155,6 +155,8 @@ function plot(gd, subplot, cdata) { scene.textOptions.push(opts.text); scene.textSelectedOptions.push(opts.textSel); scene.textUnselectedOptions.push(opts.textUnsel); + scene.selectBatch.push([]); + scene.unselectBatch.push([]); stash.x = x; stash.y = y; diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js index 41013bfd666..209cce173f8 100644 --- a/src/traces/splom/base_plot.js +++ b/src/traces/splom/base_plot.js @@ -75,7 +75,7 @@ function dragOne(gd, trace, scene) { } } - if(scene.selectBatch) { + if(scene.selectBatch.length || scene.unselectBatch.length) { scene.matrix.update({ranges: ranges}, {ranges: ranges}); } else { scene.matrix.update({ranges: ranges}); @@ -201,30 +201,6 @@ function clean(newFullData, newFullLayout, oldFullData, oldFullLayout) { Cartesian.clean(newFullData, newFullLayout, oldFullData, oldFullLayout); } -function updateFx(gd) { - Cartesian.updateFx(gd); - - var fullLayout = gd._fullLayout; - var dragmode = fullLayout.dragmode; - - // unset selection styles when coming out of a selection mode - if(dragmode === 'zoom' || dragmode === 'pan') { - var cd = gd.calcdata; - - for(var i = 0; i < cd.length; i++) { - var cd0 = cd[i][0]; - var trace = cd0.trace; - - if(trace.type === 'splom') { - var scene = fullLayout._splomScenes[trace.uid]; - if(scene.selectBatch === null) { - scene.matrix.update(scene.matrixOptions, null); - } - } - } - } -} - module.exports = { name: SPLOM, attr: Cartesian.attr, @@ -236,6 +212,6 @@ module.exports = { drag: drag, updateGrid: updateGrid, clean: clean, - updateFx: updateFx, + updateFx: Cartesian.updateFx, toSVG: Cartesian.toSVG }; diff --git a/src/traces/splom/index.js b/src/traces/splom/index.js index 557c4427fdb..416c4a46504 100644 --- a/src/traces/splom/index.js +++ b/src/traces/splom/index.js @@ -9,7 +9,6 @@ 'use strict'; var createMatrix = require('regl-splom'); -var arrayRange = require('array-range'); var Registry = require('../../registry'); var Grid = require('../../components/grid'); @@ -127,10 +126,9 @@ function sceneUpdate(gd, trace) { var reset = {dirty: true}; var first = { - selectBatch: null, - unselectBatch: null, matrix: false, - select: null + selectBatch: [], + unselectBatch: [] }; var scene = splomScenes[trace.uid]; @@ -140,7 +138,7 @@ function sceneUpdate(gd, trace) { scene.draw = function draw() { if(scene.matrix && scene.matrix.draw) { - if(scene.selectBatch) { + if(scene.selectBatch.length || scene.unselectBatch.length) { scene.matrix.draw(scene.unselectBatch, scene.selectBatch); } else { scene.matrix.draw(); @@ -237,17 +235,11 @@ function plotOne(gd, cd0) { var clickSelectEnabled = fullLayout.clickmode.indexOf('select') > -1; var selectMode = dragmode === 'lasso' || dragmode === 'select' || !!trace.selectedpoints || clickSelectEnabled; - scene.selectBatch = null; - scene.unselectBatch = null; + var needsBaseUpdate = true; if(selectMode) { var commonLength = trace._length; - if(!scene.selectBatch) { - scene.selectBatch = []; - scene.unselectBatch = []; - } - // regenerate scene batch, if traces number changed during selection if(trace.selectedpoints) { scene.selectBatch = trace.selectedpoints; @@ -288,18 +280,19 @@ function plotOne(gd, cd0) { } } - if(scene.selectBatch) { - scene.matrix.update(matrixOpts, matrixOpts); - scene.matrix.update(scene.unselectedOptions, scene.selectedOptions); - scene.matrix.update(viewOpts, viewOpts); - } else { - // delete selection pass - scene.matrix.update(viewOpts, null); + if(scene.selectBatch.length || scene.unselectBatch.length) { + var unselOpts = Lib.extendFlat({}, matrixOpts, scene.unselectedOptions, viewOpts); + var selOpts = Lib.extendFlat({}, matrixOpts, scene.selectedOptions, viewOpts); + scene.matrix.update(unselOpts, selOpts); + needsBaseUpdate = false; } } else { + stash.xpx = stash.ypx = null; + } + + if(needsBaseUpdate) { var opts = Lib.extendFlat({}, matrixOpts, viewOpts); scene.matrix.update(opts, null); - stash.xpx = stash.ypx = null; } } @@ -374,7 +367,6 @@ function selectPoints(searchInfo, selectionTester) { var xa = searchInfo.xaxis; var ya = searchInfo.yaxis; var selection = []; - var i; if(!scene) return selection; @@ -389,14 +381,13 @@ function selectPoints(searchInfo, selectionTester) { var ypx = stash.ypx[yi]; var x = cdata[xi]; var y = cdata[yi]; + var els = []; + var unels = []; // degenerate polygon does not enable selection // filter out points by visible scatter ones - var els = null; - var unels = null; if(selectionTester !== false && !selectionTester.degenerate) { - els = [], unels = []; - for(i = 0; i < x.length; i++) { + for(var i = 0; i < x.length; i++) { if(selectionTester.contains([xpx[i], ypx[i]], null, i, searchInfo)) { els.push(i); selection.push({ @@ -408,30 +399,22 @@ function selectPoints(searchInfo, selectionTester) { unels.push(i); } } - } else { - unels = arrayRange(stash.count); } - // make sure selectBatch is created - if(!scene.selectBatch) { - scene.selectBatch = []; - scene.unselectBatch = []; - } + var matrixOpts = scene.matrixOptions; - if(!scene.selectBatch) { - // enter every trace select mode - for(i = 0; i < scene.count; i++) { - scene.selectBatch = []; - scene.unselectBatch = []; - } - // we should turn scatter2d into unselected once we have any points selected - scene.matrix.update(scene.unselectedOptions, scene.selectedOptions); + if(!els.length && !unels.length) { + scene.matrix.update(matrixOpts, null); + } else if(!scene.selectBatch.length && !scene.unselectBatch.length) { + scene.matrix.update( + scene.unselectedOptions, + Lib.extendFlat({}, matrixOpts, scene.selectedOptions, scene.viewOpts) + ); } scene.selectBatch = els; scene.unselectBatch = unels; - return selection; } diff --git a/test/jasmine/tests/gl2d_double_click_test.js b/test/jasmine/tests/gl2d_double_click_test.js index a9ed24461de..3881223558d 100644 --- a/test/jasmine/tests/gl2d_double_click_test.js +++ b/test/jasmine/tests/gl2d_double_click_test.js @@ -422,13 +422,15 @@ describe('Test gl2d lasso/select:', function() { .then(done); }); - it('@gl should behave correctly during select+doubleclick+pan scenarios', function(done) { - gd = createGraphDiv(); - - // See https://github.com/plotly/plotly.js/issues/2767 + function grabScene() { + return gd.calcdata[0][0].t._scene; + } - function grabScene() { - return gd.calcdata[0][0].t._scene; + describe('select+doubleclick+pan scenarios:', function() { + function init() { + var scene = grabScene(); + spyOn(scene.scatter2d, 'update').and.callThrough(); + spyOn(scene.scatter2d, 'draw').and.callThrough(); } function _assert(msg, exp) { @@ -454,14 +456,14 @@ describe('Test gl2d lasso/select:', function() { ); updateCalls.forEach(function(d, i) { d.args.forEach(function(arg, j) { - if('range' in arg[0]) { + if(Array.isArray(arg) && 'range' in arg[0]) { // no need to assert range value in detail expect(exp.updateArgs[i][j]).toBe( 'range', 'scatter.update range update - ' + msg ); } else { - expect(arg).toBe( + expect(arg).toEqual( exp.updateArgs[i][j], 'scatter.update call' + i + ' arg' + j + ' - ' + msg ); @@ -475,7 +477,7 @@ describe('Test gl2d lasso/select:', function() { ); drawCalls.forEach(function(d, i) { d.args.forEach(function(arg, j) { - expect(arg).toBe( + expect(arg).toEqual( exp.drawArgs[i][j], 'scatter.draw call' + i + ' arg' + j + ' - ' + msg ); @@ -486,13 +488,229 @@ describe('Test gl2d lasso/select:', function() { scene.scatter2d.draw.calls.reset(); } - var unselectBatchOld; + it('@gl should behave correctly during select -> doubleclick -> pan:', function(done) { + gd = createGraphDiv(); + + // See https://github.com/plotly/plotly.js/issues/2767 + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + y: [1, 2, 1], + marker: {size: 30} + }], { + dragmode: 'select', + margin: {t: 0, b: 0, l: 0, r: 0}, + width: 500, + height: 500 + }) + .then(delay(20)) + .then(init) + .then(function() { + _assert('base', { + selectBatch: [[]], + unselectBatch: [[]], + updateArgs: [], + drawArgs: [] + }); + }) + .then(function() { return select([[20, 20], [480, 250]]); }) + .then(function() { + var scene = grabScene(); + _assert('after select', { + selectBatch: [[1]], + unselectBatch: [[0, 2]], + updateArgs: [ + // N.B. scatter2d now draws unselected options + scene.markerUnselectedOptions, + ], + drawArgs: [ + // draw unselectBatch + [scene.unselectBatch] + ] + }); + }) + .then(function() { return doubleClick(250, 250); }) + .then(function() { + var scene = grabScene(); + _assert('after doubleclick', { + selectBatch: [[]], + unselectBatch: [[]], + updateArgs: [ + // N.B. bring scatter2d back to 'base' marker options + [scene.markerOptions[0]] + ], + drawArgs: [ + // call data[0] batch + [0] + ] + }); + }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) + .then(function() { + _assert('after relayout to *pan*', { + selectBatch: [[]], + unselectBatch: [[]], + // nothing to do when relayouting to 'pan' + updateArgs: [], + drawArgs: [] + }); + }) + .then(function() { return drag([[200, 200], [250, 250]]); }) + .then(function() { + var scene = grabScene(); + _assert('after pan', { + selectBatch: [[]], + unselectBatch: [[]], + // drag triggers: + // - 2 scene.update() calls, which each invoke + // + 1 scatter2d.update (updating viewport) + // + 1 scatter2d.draw (same as after double-click) + // + // replot on mouseup triggers: + // - 1 scatter2d.update resetting markerOptions + // - 1 scatter2d.update updating viewport + // - 1 (full) scene.draw() + updateArgs: [ + ['range'], + ['range'], + // N.B. bring scatter2d back to 'base' marker options + [scene.markerOptions], + ['range'] + ], + drawArgs: [ + // call data[0] batch + [0], + [0], + [0] + ] + }); + }) + .catch(failTest) + .then(done); + }); + + it('@gl should behave correctly when doubleclick before selecting anything', function(done) { + gd = createGraphDiv(); + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + y: [1, 2, 1], + marker: {size: 30} + }], { + dragmode: 'select', + margin: {t: 0, b: 0, l: 0, r: 0}, + width: 500, + height: 500 + }) + .then(delay(20)) + .then(init) + .then(function() { return doubleClick(250, 250); }) + .then(function() { + var scene = grabScene(); + _assert('after doublclick', { + selectBatch: [[]], + unselectBatch: [[]], + updateArgs: [ + // N.B. bring scatter2d back to 'base' marker options + [scene.markerOptions[0]] + ], + drawArgs: [ + // call data[0] batch + [0] + ] + }); + }) + .catch(failTest) + .then(done); + }); + + it('@gl should behave correctly during select -> doubleclick -> dragmode:mode -> dragmode:select', function(done) { + gd = createGraphDiv(); + + // https://github.com/plotly/plotly.js/issues/2958 + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + y: [1, 2, 1], + marker: {size: 30} + }], { + dragmode: 'select', + margin: {t: 0, b: 0, l: 0, r: 0}, + width: 500, + height: 500 + }) + .then(delay(20)) + .then(init) + .then(function() { + _assert('base', { + selectBatch: [[]], + unselectBatch: [[]], + updateArgs: [], + drawArgs: [] + }); + }) + .then(function() { return select([[20, 20], [480, 250]]); }) + .then(function() { return doubleClick(250, 250); }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) + .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) + .then(function() { + var scene = grabScene(); + _assert('after', { + selectBatch: [[]], + unselectBatch: [[]], + updateArgs: [ + scene.markerUnselectedOptions, + [scene.markerOptions[0]], + [[{}]], + ['range'] + ], + drawArgs: [ + [[[0, 2]]], + [0], + [0] + ] + }); + }) + .catch(failTest) + .then(done); + }); + }); + + it('@gl should draw parts in correct order during selections', function(done) { + gd = createGraphDiv(); + + // https://github.com/plotly/plotly.js/issues/3740 - Plotly.newPlot('graph', [{ + var tracker = []; + + function _assert(msg, exp) { + expect(tracker.length).toBe(exp.length, msg + ' same # of sub-draw calls'); + + tracker.forEach(function(d, i) { + var expi = exp[i]; + expect(d[0]).toBe(expi[0], msg + ' ' + i + 'th sub-draw call'); + + expect(d[1].length).toBe(expi[1].length, msg + ' same # of sub-draw args|' + i + 'th call'); + expi[1].forEach(function(e, j) { + expect(d[1][j]).toEqual(e, ['arg', j, msg + ' in call', i].join(' ')); + }); + }); + + tracker = []; + } + + Plotly.newPlot(gd, [{ type: 'scattergl', mode: 'markers', y: [1, 2, 1], marker: {size: 30} + }, { + type: 'scattergl', + mode: 'lines', + y: [1, 2, 1] }], { dragmode: 'select', margin: {t: 0, b: 0, l: 0, r: 0}, @@ -502,91 +720,39 @@ describe('Test gl2d lasso/select:', function() { .then(delay(20)) .then(function() { var scene = grabScene(); - spyOn(scene.scatter2d, 'update').and.callThrough(); - spyOn(scene.scatter2d, 'draw').and.callThrough(); - }) - .then(function() { - _assert('base', { - selectBatch: [], - unselectBatch: [], - updateArgs: [], - drawArgs: [] + spyOn(scene.scatter2d, 'draw').and.callFake(function() { + tracker.push(['scatter2d', arguments]); }); - }) - .then(function() { return select([[20, 20], [480, 250]]); }) - .then(function() { - var scene = grabScene(); - _assert('after select', { - selectBatch: [[1]], - unselectBatch: [[0, 2]], - updateArgs: [ - // N.B. scatter2d now draws unselected options - [scene.markerUnselectedOptions], - ], - drawArgs: [ - [scene.unselectBatch] - ] + spyOn(scene.line2d, 'draw').and.callFake(function() { + tracker.push(['line2d', arguments]); + }); + spyOn(scene.select2d, 'draw').and.callFake(function() { + tracker.push(['select2d', arguments]); }); }) - .then(function() { return doubleClick(250, 250); }) + .then(function() { return Plotly.relayout(gd, 'xaxis.range', [0, 4]); }) .then(function() { - var scene = grabScene(); - _assert('after doubleclick', { - selectBatch: [null], - unselectBatch: [[0, 1, 2]], - updateArgs: [], - drawArgs: [ - // call in no-selection loop (can we get rid of this?) - [0], - // call with unselectBatch - [scene.unselectBatch] - ] - }); + _assert('base', [ + ['scatter2d', [0]], + ['line2d', [1]], + ['select2d', [[[], []]]] + ]); }) - .then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) + .then(function() { return select([[20, 20], [480, 250]]); }) .then(function() { - _assert('after relayout to *pan*', { - selectBatch: [null], - unselectBatch: [[0, 1, 2]], - // nothing to do when relayouting to 'pan' - updateArgs: [], - drawArgs: [] - }); - - // keep ref for next _assert() - var scene = grabScene(); - unselectBatchOld = scene.unselectBatch; + _assert('on selection', [ + ['scatter2d', [[[0, 2], []]]], + ['line2d', [1]], + ['select2d', [[[1], []]]] + ]); }) - .then(function() { return drag([[200, 200], [250, 250]]); }) + .then(function() { return doubleClick(250, 250); }) .then(function() { - var scene = grabScene(); - _assert('after pan', { - selectBatch: null, - unselectBatch: null, - // drag triggers: - // - 2 scene.update() calls, which each invoke - // + 1 scatter2d.update (updating viewport) - // + 2 scatter2d.draw (same as after double-click) - // - // replot on mouseup triggers: - // - 1 scatter2d.update updating viewport - // - 1 scatter2d.update resetting markerOptions - // - 1 (full) scene.draw() - updateArgs: [ - ['range'], - ['range'], - // N.B. bring scatter2d back to 'base' marker options - [scene.markerOptions], - ['range'] - ], - drawArgs: [ - [0], - [unselectBatchOld], - [0], - [unselectBatchOld], - [0] - ] - }); + _assert('after double-click', [ + ['scatter2d', [0]], + ['line2d', [1]], + ['select2d', [[[], []]]] + ]); }) .catch(failTest) .then(done); diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index 5ee7b040dc8..2ff8b2e4f46 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -884,7 +884,7 @@ describe('Test gl2d plots', function() { it('@gl should restyle opacity', function(done) { // #2299 - spyOn(ScatterGl, 'calc'); + spyOn(ScatterGl, 'calc').and.callThrough(); var dat = [{ 'x': [1, 2, 3], @@ -944,8 +944,8 @@ describe('Test gl2d plots', function() { var scene = gd._fullLayout._plots.xy._scene; expect(scene.count).toBe(2); - expect(scene.selectBatch).toEqual([[0]]); - expect(scene.unselectBatch).toEqual([[]]); + expect(scene.selectBatch).toEqual([[0], []]); + expect(scene.unselectBatch).toEqual([[], []]); expect(scene.markerOptions.length).toBe(2); expect(scene.markerOptions[1].color).toEqual(new Uint8Array([255, 0, 0, 255])); expect(scene.textOptions.length).toBe(2); @@ -958,8 +958,8 @@ describe('Test gl2d plots', function() { var scene = gd._fullLayout._plots.xy._scene; var msg = 'clearing under dragmode select'; - expect(scene.selectBatch).toEqual([], msg); - expect(scene.unselectBatch).toEqual([], msg); + expect(scene.selectBatch).toEqual([[], []], msg); + expect(scene.unselectBatch).toEqual([[], []], msg); // scattergl uses different pathways for select/lasso & zoom/pan return Plotly.relayout(gd, 'dragmode', 'pan'); @@ -968,8 +968,8 @@ describe('Test gl2d plots', function() { var scene = gd._fullLayout._plots.xy._scene; var msg = 'cleared under dragmode pan'; - expect(scene.selectBatch).toEqual([], msg); - expect(scene.unselectBatch).toEqual([], msg); + expect(scene.selectBatch).toEqual([[], []], msg); + expect(scene.unselectBatch).toEqual([[], []], msg); return Plotly.restyle(gd, 'selectedpoints', [[1, 2], [0]]); }) @@ -986,8 +986,8 @@ describe('Test gl2d plots', function() { var scene = gd._fullLayout._plots.xy._scene; var msg = 'clearing under dragmode pan'; - expect(scene.selectBatch).toBe(null, msg); - expect(scene.unselectBatch).toBe(null, msg); + expect(scene.selectBatch).toEqual([[], []], msg); + expect(scene.unselectBatch).toEqual([[], []], msg); }) .catch(failTest) .then(done); diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js index 20535070488..9352b6f6f9b 100644 --- a/test/jasmine/tests/splom_test.js +++ b/test/jasmine/tests/splom_test.js @@ -1771,16 +1771,16 @@ describe('Test splom select:', function() { updateCnt: 0, drawCnt: 0, matrixTraces: 1, - selectBatch: null, - unselectBatch: null + selectBatch: [], + unselectBatch: [] }); }) .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) .then(function() { _assert('under dragmode:select', { - updateCnt: 3, // updates positions, viewport and style in 3 calls + updateCnt: 1, // updates positions, viewport and style in 1 call drawCnt: 1, // results in a 'plot' edit - matrixTraces: 2, + matrixTraces: 1, selectBatch: [], unselectBatch: [] }); @@ -1788,7 +1788,7 @@ describe('Test splom select:', function() { .then(function() { return _select([[5, 5], [100, 100]]); }) .then(function() { _assert('after selection', { - updateCnt: 0, + updateCnt: 1, // update to [un]selected styles drawCnt: 1, matrixTraces: 2, selectBatch: [1], @@ -1808,7 +1808,7 @@ describe('Test splom select:', function() { .then(function() { return Plotly.relayout(gd, 'dragmode', 'select'); }) .then(function() { _assert('back dragmode:select', { - updateCnt: 3, + updateCnt: 1, drawCnt: 1, // a 'plot' edit (again) matrixTraces: 2, selectBatch: [1], @@ -1818,20 +1818,20 @@ describe('Test splom select:', function() { .then(function() { return doubleClick(100, 100); }) .then(function() { _assert('after dblclick clearing selection', { - updateCnt: 0, + updateCnt: 1, // reset to 'base' styles drawCnt: 1, - matrixTraces: 2, - selectBatch: null, + matrixTraces: 1, + selectBatch: [], unselectBatch: [] }); }) .then(function() { return Plotly.relayout(gd, 'dragmode', 'pan'); }) .then(function() { _assert('under dragmode:pan with NO active selection', { - updateCnt: 1, // to clear off 1 matrixTrace + updateCnt: 0, drawCnt: 0, - matrixTraces: 1, // N.B. back to '1' here - selectBatch: null, + matrixTraces: 1, + selectBatch: [], unselectBatch: [] }); })