diff --git a/src/plots/plots.js b/src/plots/plots.js index 6dbf7780aa5..4d40ab31d20 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1171,11 +1171,29 @@ plots.supplyTraceDefaults = function(traceIn, colorIndex, layout, traceInIndex, return traceOut; }; +/** + * hasMakesDataTransform: does this trace have a transform that makes its own + * data, either by grabbing it from somewhere else or by creating it from input + * parameters? If so, we should still keep going with supplyDefaults + * even if the trace is invisible, which may just be because it has no data yet. + */ +function hasMakesDataTransform(traceIn) { + var transformsIn = traceIn.transforms; + if(Array.isArray(transformsIn) && transformsIn.length) { + for(var i = 0; i < transformsIn.length; i++) { + var _module = transformsRegistry[transformsIn[i].type]; + if(_module && _module.makesData) return true; + } + } + return false; +} + plots.supplyTransformDefaults = function(traceIn, traceOut, layout) { // For now we only allow transforms on 1D traces, ie those that specify a _length. // If we were to implement 2D transforms, we'd need to have each transform // describe its own applicability and disable itself when it doesn't apply. - if(!traceOut._length) return; + // Also allow transforms that make their own data, but not in globalTransforms + if(!(traceOut._length || hasMakesDataTransform(traceIn))) return; var globalTransforms = layout._globalTransforms || []; var transformModules = layout._transformModules || []; diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js index e9c04247329..2d946d383eb 100644 --- a/test/jasmine/tests/transform_multi_test.js +++ b/test/jasmine/tests/transform_multi_test.js @@ -281,6 +281,61 @@ describe('user-defined transforms:', function() { expect(calledSupplyLayoutDefaults).toBe(1); }); + it('handles `makesData` transforms when the incoming trace has no data', function() { + var transformIn = {type: 'linemaker', x0: 3, y0: 2, x1: 5, y1: 10, n: 3}; + var dataIn = [{transforms: [transformIn], mode: 'lines+markers'}]; + var fullData = []; + var layout = {}; + var fullLayout = Lib.extendDeep({}, mockFullLayout); + + var lineMakerModule = { + moduleType: 'transform', + name: 'linemaker', + makesData: true, + attributes: {}, + supplyDefaults: function(transformIn) { + return Lib.extendFlat({}, transformIn); + }, + transform: function(data, state) { + var transform = state.transform; + var trace = data[0]; + var n = transform.n; + var x = new Array(n); + var y = new Array(n); + + // our exciting transform - make a line! + for(var i = 0; i < n; i++) { + x[i] = transform.x0 + (i / (n - 1)) * (transform.x1 - transform.x0); + y[i] = transform.y0 + (i / (n - 1)) * (transform.y1 - transform.y0); + } + + // we didn't coerce mode before, because there was no data + expect(trace.mode).toBeUndefined(); + expect(trace.line).toBeUndefined(); + expect(trace.marker).toBeUndefined(); + + // just put the input trace back in here, it'll get coerced again after the transform + var traceOut = Lib.extendFlat(trace._input, {x: x, y: y}); + + return [traceOut]; + } + }; + + Plotly.register(lineMakerModule); + Plots.supplyDataDefaults(dataIn, fullData, layout, fullLayout); + delete Plots.transformsRegistry.linemaker; + + expect(fullData.length).toBe(1); + var traceOut = fullData[0]; + expect(traceOut.x).toEqual([3, 4, 5]); + expect(traceOut.y).toEqual([2, 6, 10]); + + // make sure we redid supplyDefaults after the data arrays were added + expect(traceOut.mode).toBe('lines+markers'); + expect(traceOut.line).toBeDefined(); + expect(traceOut.marker).toBeDefined(); + }); + }); describe('multiple transforms:', function() {