Skip to content

Commit 7e0a382

Browse files
committed
finance: ensure that user data isn't mutated in Plots.supplyDefaults
for ohlc and candlestick traces
1 parent a892b26 commit 7e0a382

File tree

5 files changed

+49
-31
lines changed

5 files changed

+49
-31
lines changed

src/plots/plots.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ function supplyTransformDefaults(traceIn, traceOut, layout) {
805805
if(!_module) Lib.warn('Unrecognized transform type ' + type + '.');
806806

807807
if(_module && _module.supplyDefaults) {
808-
transformOut = _module.supplyDefaults(transformIn, traceOut, layout);
808+
transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn);
809809
transformOut.type = type;
810810
}
811811
else {

src/traces/candlestick/transform.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ exports.name = 'candlestick';
1818

1919
exports.attributes = {};
2020

21-
exports.supplyDefaults = function(transformIn, traceOut) {
21+
exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) {
22+
helpers.clearEphemeralTransformOpts(traceIn);
2223
helpers.copyOHLC(transformIn, traceOut);
2324

2425
return transformIn;

src/traces/ohlc/helpers.js

+24-16
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ var Lib = require('../../lib');
1919
// Note that, we must mutate user data (here traceIn) as opposed
2020
// to full data (here traceOut) as - at the moment - transform
2121
// defaults (which are called after trace defaults) start
22-
// from a clear transforms container.
22+
// from a clear transforms container. The mutations inflicted are
23+
// cleared in exports.clearEphemeralTransformOpts.
2324
exports.pushDummyTransformOpts = function(traceIn, traceOut) {
2425
var transformOpts = {
2526

@@ -31,29 +32,36 @@ exports.pushDummyTransformOpts = function(traceIn, traceOut) {
3132
};
3233

3334
if(Array.isArray(traceIn.transforms)) {
34-
var transformsIn = traceIn.transforms;
35-
36-
// remove all ephemeral transforms,
37-
// before adding dummy transform
38-
for(var i = 0; i < transformsIn.length; i++) {
39-
if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1);
40-
}
41-
4235
traceIn.transforms.push(transformOpts);
4336
}
4437
else {
4538
traceIn.transforms = [transformOpts];
4639
}
4740
};
4841

49-
// This routine gets called during the transform supply-defaults step,
50-
// but only has an effect (because of the if-statements) during the
51-
// second round of transform defaults done on generated traces.
52-
//
53-
// This is a hacky way to pass 'ohlc' and 'candlestick' attributes
42+
// This routine gets called during the transform supply-defaults step
43+
// where it clears ephemeral transform opts in user data
44+
// and effectively put back user date in its pre-supplyDefaults state.
45+
exports.clearEphemeralTransformOpts = function(traceIn) {
46+
var transformsIn = traceIn.transforms;
47+
48+
if(!Array.isArray(transformsIn)) return;
49+
50+
for(var i = 0; i < transformsIn.length; i++) {
51+
if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1);
52+
}
53+
54+
if(transformsIn.length === 0) delete traceIn.transforms;
55+
};
56+
57+
// This routine gets called during the transform supply-defaults step
58+
// where it passes 'ohlc' and 'candlestick' attributes
5459
// (found the transform container via exports.makeTransform)
55-
// to the traceOut container such that they can be compatible with
56-
// filter and groupby transforms.
60+
// to the traceOut container such that they can
61+
// be compatible with filter and groupby transforms.
62+
//
63+
// Note that this routine only has an effect during the
64+
// second round of transform defaults done on generated traces
5765
exports.copyOHLC = function(container, traceOut) {
5866
if(container.open) traceOut.open = container.open;
5967
if(container.high) traceOut.high = container.high;

src/traces/ohlc/transform.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ exports.name = 'ohlc';
2020

2121
exports.attributes = {};
2222

23-
exports.supplyDefaults = function(transformIn, traceOut) {
23+
exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) {
24+
helpers.clearEphemeralTransformOpts(traceIn);
2425
helpers.copyOHLC(transformIn, traceOut);
2526

2627
return transformIn;

test/jasmine/tests/finance_test.js

+20-12
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('finance charts defaults:', function() {
5555
expect(directions).toEqual(['increasing', 'decreasing', 'increasing', 'decreasing']);
5656
});
5757

58-
it('unfortunately mutates user trace', function() {
58+
it('should not mutate user data', function() {
5959
var trace0 = Lib.extendDeep({}, mock0, {
6060
type: 'ohlc'
6161
});
@@ -65,18 +65,18 @@ describe('finance charts defaults:', function() {
6565
});
6666

6767
var out = _supply([trace0, trace1]);
68-
expect(out.data[0].transforms.length).toEqual(1);
69-
expect(out.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]);
70-
expect(out.data[1].transforms.length).toEqual(1);
71-
expect(out.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]);
68+
expect(out.data[0]).toBe(trace0);
69+
expect(out.data[0].transforms).toBeUndefined();
70+
expect(out.data[1]).toBe(trace1);
71+
expect(out.data[1].transforms).toBeUndefined();
7272

73-
// but at least in an idempotent way
73+
// ... and in an idempotent way
7474

7575
var out2 = _supply(out.data);
76-
expect(out2.data[0].transforms.length).toEqual(1);
77-
expect(out2.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]);
78-
expect(out2.data[0].transforms.length).toEqual(1);
79-
expect(out2.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]);
76+
expect(out2.data[0]).toBe(trace0);
77+
expect(out2.data[0].transforms).toBeUndefined();
78+
expect(out2.data[1]).toBe(trace1);
79+
expect(out2.data[1].transforms).toBeUndefined();
8080
});
8181

8282
it('should work with transforms', function() {
@@ -99,7 +99,15 @@ describe('finance charts defaults:', function() {
9999
expect(out.data.length).toEqual(2);
100100
expect(out._fullData.length).toEqual(4);
101101

102-
var transformTypes = out._fullData.map(function(fullTrace) {
102+
var transformTypesIn = out.data.map(function(trace) {
103+
return trace.transforms.map(function(opts) {
104+
return opts.type;
105+
});
106+
});
107+
108+
expect(transformTypesIn).toEqual([ ['filter'], ['filter'] ]);
109+
110+
var transformTypesOut = out._fullData.map(function(fullTrace) {
103111
return fullTrace.transforms.map(function(opts) {
104112
return opts.type;
105113
});
@@ -108,7 +116,7 @@ describe('finance charts defaults:', function() {
108116
// dummy 'ohlc' and 'candlestick' transforms are pushed at the end
109117
// of the 'transforms' array container
110118

111-
expect(transformTypes).toEqual([
119+
expect(transformTypesOut).toEqual([
112120
['filter', 'ohlc'], ['filter', 'ohlc'],
113121
['filter', 'candlestick'], ['filter', 'candlestick']
114122
]);

0 commit comments

Comments
 (0)