Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/selections/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,8 @@ function determineSearchTraces(gd, xAxes, yAxes, subplot) {

for(i = 0; i < gd.calcdata.length; i++) {
cd = gd.calcdata[i];
// guard against a transient empty/undefined calcdata entry (see epmtySplomSelectionBatch)
if(!cd || !cd[0]) continue;
trace = cd[0].trace;

if(trace.visible !== true || !trace._module || !trace._module.selectPoints) continue;
Expand Down Expand Up @@ -1286,6 +1288,10 @@ function epmtySplomSelectionBatch(gd) {
if(!cd) return;

for(var i = 0; i < cd.length; i++) {
// calcdata can transiently hold an empty/undefined entry (e.g. during
// react/relayout diffing); reselect runs on every redraw, so skip
// instead of throwing. See supplyDefaultsUpdateCalc for the same guard.
if(!cd[i] || !cd[i][0]) continue;
var cd0 = cd[i][0];
var trace = cd0.trace;
var splomScenes = gd._fullLayout._splomScenes;
Expand Down
30 changes: 30 additions & 0 deletions test/jasmine/tests/select_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ var d3SelectAll = require('../../strict-d3').selectAll;

var Plotly = require('../../../lib/index');
var Lib = require('../../../src/lib');
var Registry = require('../../../src/registry');
var click = require('../assets/click');
var doubleClick = require('../assets/double_click');
var DBLCLICKDELAY = require('../../../src/plot_api/plot_config').dfltConfig.doubleClickDelay;
Expand Down Expand Up @@ -3600,6 +3601,35 @@ describe('Test that selection styles propagate to range-slider plot:', function(
});
});

describe('Test reselect with malformed calcdata:', function() {
var gd;

beforeEach(function() {
gd = createGraphDiv();
});

afterEach(destroyGraphDiv);

// reselect runs unconditionally at the end of every redraw sequence
// (newPlot / react / relayout / resize) and dereferences gd.calcdata[i][0].
// calcdata can transiently hold an empty or undefined entry, which used to
// throw "Cannot read properties of undefined (reading '0')" and kill the
// chart even when no selection exists.
it('should not throw on an empty or undefined calcdata entry', function(done) {
var reselect = Registry.getComponentMethod('selections', 'reselect');

Plotly.newPlot(gd, [{y: [1, 2, 3]}, {y: [2, 3, 4]}])
.then(function() {
gd.calcdata[1] = undefined;
expect(function() { reselect(gd); }).not.toThrow();

gd.calcdata[0] = [];
expect(function() { reselect(gd); }).not.toThrow();
})
.then(done, done.fail);
});
});

// to make sure none of the above tests fail with extraneous invisible traces,
// add a bunch of them here
function addInvisible(fig, canHaveLegend) {
Expand Down