Skip to content

Commit d6da48e

Browse files
authored
Merge pull request #3766 from plotly/filter-enabled-false-when-empty-target-array
Set enabled:false when filter target array is empty
2 parents 6ae0349 + 84c3606 commit d6da48e

File tree

13 files changed

+138
-33
lines changed

13 files changed

+138
-33
lines changed

src/components/fx/hover.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,12 @@ function _hover(gd, evt, subplot, noHoverEvent) {
370370
cd = searchData[curvenum];
371371

372372
// filter out invisible or broken data
373-
if(!cd || !cd[0] || !cd[0].trace || cd[0].trace.visible !== true) continue;
373+
if(!cd || !cd[0] || !cd[0].trace) continue;
374374

375375
trace = cd[0].trace;
376376

377+
if(trace.visible !== true || trace._length === 0) continue;
378+
377379
// Explicitly bail out for these two. I don't know how to otherwise prevent
378380
// the rest of this function from running and failing
379381
if(['carpet', 'contourcarpet'].indexOf(trace._module.name) !== -1) continue;

src/lib/filter_visible.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ function baseFilter(item) {
3232
}
3333

3434
function calcDataFilter(item) {
35-
return item[0].trace.visible === true;
35+
var trace = item[0].trace;
36+
return trace.visible === true && trace._length !== 0;
3637
}
3738

3839
function isCalcData(cont) {

src/plot_api/subroutines.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ exports.redrawReglTraces = function(gd) {
642642
for(i = 0; i < fullData.length; i++) {
643643
var trace = fullData[i];
644644

645-
if(trace.visible === true) {
645+
if(trace.visible === true && trace._length !== 0) {
646646
if(trace.type === 'splom') {
647647
fullLayout._splomScenes[trace.uid].draw();
648648
} else if(trace.type === 'scattergl') {

src/plots/cartesian/set_convert.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ module.exports = function setConvert(ax, fullLayout) {
403403
return;
404404
}
405405

406-
if(ax.type === 'date') {
406+
if(ax.type === 'date' && !ax.autorange) {
407407
// check if milliseconds or js date objects are provided for range
408408
// and convert to date strings
409409
range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar);

src/plots/get_data.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ exports.getModuleCalcData = function(calcdata, arg1) {
6969
for(var i = 0; i < calcdata.length; i++) {
7070
var cd = calcdata[i];
7171
var trace = cd[0].trace;
72-
// N.B. 'legendonly' traces do not make it past here
73-
if(trace.visible !== true) continue;
72+
// N.B.
73+
// - 'legendonly' traces do not make it past here
74+
// - skip over 'visible' traces that got trimmed completely during calc transforms
75+
if(trace.visible !== true || trace._length === 0) continue;
7476

7577
// group calcdata trace not by 'module' (as the name of this function
7678
// would suggest), but by 'module plot method' so that if some traces

src/plots/gl3d/scene.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
502502

503503
for(i = 0; i < sceneData.length; ++i) {
504504
data = sceneData[i];
505-
if(data.visible !== true) continue;
505+
if(data.visible !== true || data._length === 0) continue;
506506

507507
computeTraceBounds(this, data, dataBounds);
508508
}
@@ -526,7 +526,7 @@ proto.plot = function(sceneData, fullLayout, layout) {
526526
// Update traces
527527
for(i = 0; i < sceneData.length; ++i) {
528528
data = sceneData[i];
529-
if(data.visible !== true) {
529+
if(data.visible !== true || data._length === 0) {
530530
continue;
531531
}
532532
trace = this.traces[data.uid];
@@ -551,7 +551,8 @@ proto.plot = function(sceneData, fullLayout, layout) {
551551
traceIdLoop:
552552
for(i = 0; i < traceIds.length; ++i) {
553553
for(j = 0; j < sceneData.length; ++j) {
554-
if(sceneData[j].uid === traceIds[i] && sceneData[j].visible === true) {
554+
if(sceneData[j].uid === traceIds[i] &&
555+
(sceneData[j].visible === true && sceneData[j]._length !== 0)) {
555556
continue traceIdLoop;
556557
}
557558
}

src/plots/plots.js

+13-10
Original file line numberDiff line numberDiff line change
@@ -2749,17 +2749,13 @@ plots.doCalcdata = function(gd, traces) {
27492749
);
27502750
}
27512751

2752-
setupAxisCategories(axList, fullData);
2753-
27542752
var hasCalcTransform = false;
27552753

2756-
// transform loop
2757-
for(i = 0; i < fullData.length; i++) {
2754+
function transformCalci(i) {
27582755
trace = fullData[i];
2756+
_module = trace._module;
27592757

27602758
if(trace.visible === true && trace.transforms) {
2761-
_module = trace._module;
2762-
27632759
// we need one round of trace module calc before
27642760
// the calc transform to 'fill in' the categories list
27652761
// used for example in the data-to-coordinate method
@@ -2786,9 +2782,6 @@ plots.doCalcdata = function(gd, traces) {
27862782
}
27872783
}
27882784

2789-
// clear stuff that should recomputed in 'regular' loop
2790-
if(hasCalcTransform) setupAxisCategories(axList, fullData);
2791-
27922785
function calci(i, isContainer) {
27932786
trace = fullData[i];
27942787
_module = trace._module;
@@ -2797,7 +2790,7 @@ plots.doCalcdata = function(gd, traces) {
27972790

27982791
var cd = [];
27992792

2800-
if(trace.visible === true) {
2793+
if(trace.visible === true && trace._length !== 0) {
28012794
// clear existing ref in case it got relinked
28022795
delete trace._indexToPoints;
28032796
// keep ref of index-to-points map object of the *last* enabled transform,
@@ -2833,6 +2826,16 @@ plots.doCalcdata = function(gd, traces) {
28332826
calcdata[i] = cd;
28342827
}
28352828

2829+
setupAxisCategories(axList, fullData);
2830+
2831+
// 'transform' loop - must calc container traces first
2832+
// so that if their dependent traces can get transform properly
2833+
for(i = 0; i < fullData.length; i++) calci(i, true);
2834+
for(i = 0; i < fullData.length; i++) transformCalci(i);
2835+
2836+
// clear stuff that should recomputed in 'regular' loop
2837+
if(hasCalcTransform) setupAxisCategories(axList, fullData);
2838+
28362839
// 'regular' loop - make sure container traces (eg carpet) calc before
28372840
// contained traces (eg contourcarpet)
28382841
for(i = 0; i < fullData.length; i++) calci(i, true);

src/plots/polar/set_convert.js

+3
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ function setConvertAngular(ax, polarLayout) {
174174
var catLen = ax._categories.length;
175175
var _period = ax.period ? Math.max(ax.period, catLen) : catLen;
176176

177+
// fallback in case all categories have been filtered out
178+
if(_period === 0) _period = 1;
179+
177180
c2rad = t2rad = function(v) { return v * 2 * Math.PI / _period; };
178181
rad2c = rad2t = function(v) { return v * _period / Math.PI / 2; };
179182

src/traces/scattermapbox/convert.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var convertTextOpts = require('../../plots/mapbox/convert_text_opts');
2323
module.exports = function convert(calcTrace) {
2424
var trace = calcTrace[0].trace;
2525

26-
var isVisible = (trace.visible === true);
26+
var isVisible = (trace.visible === true && trace._length !== 0);
2727
var hasFill = (trace.fill !== 'none');
2828
var hasLines = subTypes.hasLines(trace);
2929
var hasMarkers = subTypes.hasMarkers(trace);

src/transforms/filter.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,16 @@ exports.supplyDefaults = function(transformIn) {
138138
var enabled = coerce('enabled');
139139

140140
if(enabled) {
141+
var target = coerce('target');
142+
143+
if(Lib.isArrayOrTypedArray(target) && target.length === 0) {
144+
transformOut.enabled = false;
145+
return transformOut;
146+
}
147+
141148
coerce('preservegaps');
142149
coerce('operation');
143150
coerce('value');
144-
coerce('target');
145151

146152
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
147153
handleCalendarDefaults(transformIn, transformOut, 'valuecalendar', null);

test/jasmine/tests/funnel_test.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ describe('A funnel plot', function() {
10181018
.then(done);
10191019
});
10201020

1021-
it('should be able to deal with blank bars on transform', function(done) {
1021+
it('should be able to deal with transform that empty out the data coordinate arrays', function(done) {
10221022
Plotly.plot(gd, {
10231023
data: [{
10241024
type: 'funnel',
@@ -1038,14 +1038,11 @@ describe('A funnel plot', function() {
10381038
})
10391039
.then(function() {
10401040
var traceNodes = getAllTraceNodes(gd);
1041-
var funnelNodes = getAllFunnelNodes(traceNodes[0]);
1042-
var pathNode = funnelNodes[0].querySelector('path');
1041+
expect(traceNodes.length).toBe(0);
10431042

10441043
expect(gd.calcdata[0][0].x).toEqual(NaN);
10451044
expect(gd.calcdata[0][0].y).toEqual(NaN);
1046-
expect(gd.calcdata[0][0].isBlank).toBe(true);
1047-
1048-
expect(pathNode.outerHTML).toEqual('<path d="M0,0Z" style="vector-effect: non-scaling-stroke;"></path>');
1045+
expect(gd.calcdata[0][0].isBlank).toBe(undefined);
10491046
})
10501047
.catch(failTest)
10511048
.then(done);

test/jasmine/tests/transform_filter_test.js

+93
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,33 @@ describe('filter transforms defaults:', function() {
8686
expect(traceOut.transforms[2].target).toEqual('x');
8787
expect(traceOut.transforms[3].target).toEqual('marker.color');
8888
});
89+
90+
it('supplyTraceDefaults should set *enabled:false* and return early when *target* is an empty array', function() {
91+
// see https://github.com/plotly/plotly.js/issues/2908
92+
// this solves multiple problems downstream
93+
94+
traceIn = {
95+
x: [1, 2, 3],
96+
transforms: [{
97+
type: 'filter',
98+
target: []
99+
}]
100+
};
101+
traceOut = Plots.supplyTraceDefaults(traceIn, {type: 'scatter'}, 0, fullLayout);
102+
expect(traceOut.transforms[0].target).toEqual([]);
103+
expect(traceOut.transforms[0].enabled).toBe(false, 'set to false!');
104+
105+
traceIn = {
106+
x: new Float32Array([1, 2, 3]),
107+
transforms: [{
108+
type: 'filter',
109+
target: new Float32Array()
110+
}]
111+
};
112+
traceOut = Plots.supplyTraceDefaults(traceIn, {type: 'scatter'}, 0, fullLayout);
113+
expect(traceOut.transforms[0].target).toEqual(new Float32Array());
114+
expect(traceOut.transforms[0].enabled).toBe(false, 'set to false!');
115+
});
89116
});
90117

91118
describe('filter transforms calc:', function() {
@@ -1292,3 +1319,69 @@ describe('filter transforms interactions', function() {
12921319
.then(done);
12931320
});
12941321
});
1322+
1323+
describe('filter resulting in empty coordinate arrays', function() {
1324+
var gd;
1325+
1326+
afterEach(function(done) {
1327+
Plotly.purge(gd);
1328+
setTimeout(function() {
1329+
destroyGraphDiv();
1330+
done();
1331+
}, 200);
1332+
});
1333+
1334+
function filter2empty(mock) {
1335+
var fig = Lib.extendDeep({}, mock);
1336+
var data = fig.data || [];
1337+
1338+
data.forEach(function(trace) {
1339+
trace.transforms = [{
1340+
type: 'filter',
1341+
target: [null]
1342+
}];
1343+
});
1344+
1345+
return fig;
1346+
}
1347+
1348+
describe('svg mocks', function() {
1349+
var mockList = require('../assets/mock_lists').svg;
1350+
1351+
mockList.forEach(function(d) {
1352+
it(d[0], function(done) {
1353+
gd = createGraphDiv();
1354+
var fig = filter2empty(d[1]);
1355+
Plotly.newPlot(gd, fig).catch(failTest).then(done);
1356+
});
1357+
});
1358+
});
1359+
1360+
describe('gl mocks', function() {
1361+
var mockList = require('../assets/mock_lists').gl;
1362+
1363+
mockList.forEach(function(d) {
1364+
it('@gl ' + d[0], function(done) {
1365+
gd = createGraphDiv();
1366+
var fig = filter2empty(d[1]);
1367+
Plotly.newPlot(gd, fig).catch(failTest).then(done);
1368+
});
1369+
});
1370+
});
1371+
1372+
describe('mapbox mocks', function() {
1373+
var mockList = require('../assets/mock_lists').mapbox;
1374+
1375+
Plotly.setPlotConfig({
1376+
mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN
1377+
});
1378+
1379+
mockList.forEach(function(d) {
1380+
it('@noCI ' + d[0], function(done) {
1381+
gd = createGraphDiv();
1382+
var fig = filter2empty(d[1]);
1383+
Plotly.newPlot(gd, fig).catch(failTest).then(done);
1384+
});
1385+
});
1386+
});
1387+
});

test/jasmine/tests/waterfall_test.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ describe('A waterfall plot', function() {
976976
.then(done);
977977
});
978978

979-
it('should be able to deal with blank bars on transform', function(done) {
979+
it('should be able to deal with transform that empty out the data coordinate arrays', function(done) {
980980
Plotly.plot(gd, {
981981
data: [{
982982
type: 'waterfall',
@@ -993,14 +993,11 @@ describe('A waterfall plot', function() {
993993
})
994994
.then(function() {
995995
var traceNodes = getAllTraceNodes(gd);
996-
var waterfallNodes = getAllWaterfallNodes(traceNodes[0]);
997-
var pathNode = waterfallNodes[0].querySelector('path');
996+
expect(traceNodes.length).toBe(0);
998997

999998
expect(gd.calcdata[0][0].x).toEqual(NaN);
1000999
expect(gd.calcdata[0][0].y).toEqual(NaN);
1001-
expect(gd.calcdata[0][0].isBlank).toBe(true);
1002-
1003-
expect(pathNode.outerHTML).toEqual('<path d="M0,0Z" style="vector-effect: non-scaling-stroke;"></path>');
1000+
expect(gd.calcdata[0][0].isBlank).toBe(undefined);
10041001
})
10051002
.catch(failTest)
10061003
.then(done);

0 commit comments

Comments
 (0)