Skip to content

Commit 7ef0404

Browse files
committed
Add a lot of tests for binding computation
1 parent 65f618d commit 7ef0404

File tree

2 files changed

+218
-40
lines changed

2 files changed

+218
-40
lines changed

src/plots/command.js

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,51 +29,111 @@ exports.executeAPICommand = function(gd, method, args) {
2929
};
3030

3131
exports.computeAPICommandBindings = function(gd, method, args) {
32-
32+
var bindings;
3333
switch(method) {
3434
case 'restyle':
35-
var traces;
36-
37-
// Logic copied from Plotly.restyle:
38-
var astr = args[0];
39-
var val = args[1];
40-
var traces = args[2];
41-
var aobj = {};
42-
if(typeof astr === 'string') aobj[astr] = val;
43-
else if(Lib.isPlainObject(astr)) {
44-
// the 3-arg form
45-
aobj = astr;
46-
if(traces === undefined) traces = val;
47-
} else {
48-
// This is the failure case, but it's not a concern of this method to fail
49-
// on bad input. This will just return no bindings:
50-
return [];
51-
}
52-
53-
console.log('aobj:', aobj);
54-
55-
return ['data[0].marker.size'];
35+
bindings = computeDataBindings(gd, args);
36+
break;
37+
case 'relayout':
38+
bindings = computeLayoutBindings(gd, args);
39+
break;
40+
case 'animate':
41+
bindings = computeDataBindings(gd, [args[0], args[2]])
42+
.concat(computeLayoutBindings(gd, [args[1]]));
5643
break;
5744
default:
58-
// The unknown case. We'll elect to fail-non-fatal since this is a correct
59-
// answer and since this is not a validation method.
60-
return [];
45+
// We'll elect to fail-non-fatal since this is a correct
46+
// answer and since this is not a validation method.
47+
bindings = [];
6148
}
49+
return bindings;
6250
};
6351

64-
function crawl(attrs, callback, path) {
52+
function computeLayoutBindings (gd, args) {
53+
var bindings = [];
54+
55+
var astr = args[0];
56+
var aobj = {};
57+
if(typeof astr === 'string') {
58+
aobj[astr] = args[1];
59+
} else if(Lib.isPlainObject(astr)) {
60+
aobj = astr;
61+
} else {
62+
return bindings;
63+
}
64+
65+
crawl(aobj, function (path, attrName, attr) {
66+
bindings.push('layout' + path);
67+
});
68+
69+
return bindings;
70+
}
71+
72+
function computeDataBindings (gd, args) {
73+
var i, traces, astr, attr, val, traces, aobj;
74+
var bindings = [];
75+
76+
// Logic copied from Plotly.restyle:
77+
astr = args[0];
78+
val = args[1];
79+
traces = args[2];
80+
aobj = {};
81+
if(typeof astr === 'string') {
82+
aobj[astr] = val;
83+
} else if(Lib.isPlainObject(astr)) {
84+
// the 3-arg form
85+
aobj = astr;
86+
87+
if(traces === undefined) {
88+
traces = val;
89+
}
90+
} else {
91+
return bindings;
92+
}
93+
94+
if (traces === undefined) {
95+
traces = [];
96+
for (i = 0; i < gd.data.length; i++) {
97+
traces.push(i);
98+
}
99+
}
100+
101+
crawl(aobj, function (path, attrName, attr) {
102+
var nAttr;
103+
if (Array.isArray(attr)) {
104+
nAttr = Math.min(attr.length, traces.length);
105+
} else {
106+
nAttr = traces.length;
107+
}
108+
for (var j = 0; j < nAttr; j++) {
109+
bindings.push('data[' + traces[j] + ']' + path);
110+
}
111+
});
112+
113+
return bindings;
114+
}
115+
116+
function crawl(attrs, callback, path, depth) {
117+
if(depth === undefined) {
118+
depth = 0;
119+
}
120+
65121
if(path === undefined) {
66122
path = '';
67123
}
68124

69125
Object.keys(attrs).forEach(function(attrName) {
70126
var attr = attrs[attrName];
71127

72-
if(exports.UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return;
128+
if(attrName[0] === '_') return;
73129

74-
callback(attr, attrName, attrs, level);
130+
var thisPath = path + '.' + attrName;
75131

76-
if(isValObject(attr)) return;
77-
if(isPlainObject(attr)) crawl(attr, callback, level + 1);
132+
if(Lib.isPlainObject(attr)) {
133+
crawl(attr, callback, thisPath, depth + 1);
134+
} else {
135+
// Only execute the callback on leaf nodes:
136+
callback(thisPath, attrName, attr);
137+
}
78138
});
79139
}

test/jasmine/tests/command_test.js

Lines changed: 128 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,23 @@ describe('Plots.computeAPICommandBindings', function() {
7979
});
8080

8181
describe('restyle', function() {
82-
describe('astr + val notation', function() {
83-
describe('with a single attribute', function() {
82+
describe('with invalid notation', function() {
83+
it('with a scalar value', function() {
84+
var result = Plots.computeAPICommandBindings(gd, 'restyle', [['x']]);
85+
expect(result).toEqual([]);
86+
});
87+
});
88+
89+
describe('with astr + val notation', function() {
90+
describe('and a single attribute', function() {
8491
it('with a scalar value', function() {
8592
var result = Plots.computeAPICommandBindings(gd, 'restyle', ['marker.size', 7]);
8693
expect(result).toEqual(['data[0].marker.size', 'data[1].marker.size']);
8794
});
8895

8996
it('with an array value and no trace specified', function() {
9097
var result = Plots.computeAPICommandBindings(gd, 'restyle', ['marker.size', [7]]);
91-
expect(result).toEqual(['data[0].marker.size', 'data[1].marker.size']);
98+
expect(result).toEqual(['data[0].marker.size']);
9299
});
93100

94101
it('with trace specified', function() {
@@ -97,12 +104,12 @@ describe('Plots.computeAPICommandBindings', function() {
97104
});
98105

99106
it('with a different trace specified', function() {
100-
var result = Plots.computeAPICommandBindings(gd, 'restyle', ['marker.size', 7, [1]]);
101-
expect(result).toEqual(['data[1].marker.size']);
107+
var result = Plots.computeAPICommandBindings(gd, 'restyle', ['marker.size', 7, [0]]);
108+
expect(result).toEqual(['data[0].marker.size']);
102109
});
103110

104111
it('with an array value', function() {
105-
var result = Plots.computeAPICommandBindings(gd, 'restyle', ['marker.size', [7], [0]]);
112+
var result = Plots.computeAPICommandBindings(gd, 'restyle', ['marker.size', [7], [1]]);
106113
expect(result).toEqual(['data[1].marker.size']);
107114
});
108115

@@ -128,8 +135,8 @@ describe('Plots.computeAPICommandBindings', function() {
128135
});
129136
});
130137

131-
describe('aobj notation', function() {
132-
describe('with a single attribute', function() {
138+
describe('with aobj notation', function() {
139+
describe('and a single attribute', function() {
133140
it('with a scalar value', function() {
134141
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{'marker.size': 7}]);
135142
expect(result).toEqual(['data[0].marker.size', 'data[1].marker.size']);
@@ -146,7 +153,7 @@ describe('Plots.computeAPICommandBindings', function() {
146153
});
147154

148155
it('with an array value', function() {
149-
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{'marker.size': [7]}, [0]]);
156+
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{'marker.size': [7]}, [1]]);
150157
expect(result).toEqual(['data[1].marker.size']);
151158
});
152159

@@ -171,12 +178,123 @@ describe('Plots.computeAPICommandBindings', function() {
171178
});
172179
});
173180

174-
describe('with multiple attributes', function() {
181+
describe('and multiple attributes', function() {
175182
it('with a scalar value', function() {
176183
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{'marker.size': 7, 'text.color': 'blue'}]);
177184
expect(result).toEqual(['data[0].marker.size', 'data[1].marker.size', 'data[0].text.color', 'data[1].text.color']);
178185
});
179186
});
180187
});
188+
189+
describe('with mixed notation', function() {
190+
it('and nested object and nested attr', function() {
191+
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{
192+
y: [[3, 4, 5]],
193+
'marker.size': [10, 20, 25],
194+
'line.color': 'red',
195+
line: {
196+
width: [2, 8]
197+
}
198+
}]);
199+
200+
// The results are definitely not completely intuitive, so this
201+
// is based upon empirical results with a codepen example:
202+
expect(result).toEqual([
203+
'data[0].y',
204+
'data[0].marker.size',
205+
'data[1].marker.size',
206+
'data[0].line.color',
207+
'data[1].line.color',
208+
'data[0].line.width',
209+
'data[1].line.width',
210+
]);
211+
});
212+
213+
it('and traces specified', function() {
214+
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{
215+
y: [[3, 4, 5]],
216+
'marker.size': [10, 20, 25],
217+
'line.color': 'red',
218+
line: {
219+
width: [2, 8]
220+
}
221+
}, [1, 0]]);
222+
223+
// The results are definitely not completely intuitive, so this
224+
// is based upon empirical results with a codepen example:
225+
expect(result).toEqual([
226+
'data[1].y',
227+
'data[1].marker.size',
228+
'data[0].marker.size',
229+
'data[1].line.color',
230+
'data[0].line.color',
231+
'data[1].line.width',
232+
'data[0].line.width',
233+
]);
234+
});
235+
236+
it('and more data than traces', function() {
237+
var result = Plots.computeAPICommandBindings(gd, 'restyle', [{
238+
y: [[3, 4, 5]],
239+
'marker.size': [10, 20, 25],
240+
'line.color': 'red',
241+
line: {
242+
width: [2, 8]
243+
}
244+
}, [1]]);
245+
246+
// The results are definitely not completely intuitive, so this
247+
// is based upon empirical results with a codepen example:
248+
expect(result).toEqual([
249+
'data[1].y',
250+
'data[1].marker.size',
251+
'data[1].line.color',
252+
'data[1].line.width',
253+
]);
254+
});
255+
});
256+
});
257+
258+
describe('relayout', function() {
259+
describe('with invalid notation', function() {
260+
it('and a scalar value', function() {
261+
var result = Plots.computeAPICommandBindings(gd, 'relayout', [['x']]);
262+
expect(result).toEqual([]);
263+
});
264+
});
265+
266+
describe('with aobj notation', function() {
267+
it('and a single attribute', function () {
268+
var result = Plots.computeAPICommandBindings(gd, 'relayout', [{height: 500}]);
269+
expect(result).toEqual(['layout.height']);
270+
});
271+
272+
it('and two attributes', function () {
273+
var result = Plots.computeAPICommandBindings(gd, 'relayout', [{height: 500, width: 100}]);
274+
expect(result).toEqual(['layout.height', 'layout.width']);
275+
});
276+
});
277+
278+
describe('with astr + val notation', function() {
279+
it('and an attribute', function () {
280+
var result = Plots.computeAPICommandBindings(gd, 'relayout', ['width', 100]);
281+
expect(result).toEqual(['layout.width']);
282+
});
283+
284+
it('and nested atributes', function () {
285+
var result = Plots.computeAPICommandBindings(gd, 'relayout', ['margin.l', 10]);
286+
expect(result).toEqual(['layout.margin.l']);
287+
});
288+
});
289+
290+
describe('with mixed notation', function() {
291+
it('containing aob + astr', function () {
292+
var result = Plots.computeAPICommandBindings(gd, 'relayout', [{
293+
'width': 100,
294+
'margin.l': 10
295+
}]);
296+
expect(result).toEqual(['layout.width', 'layout.margin.l']);
297+
});
298+
});
181299
});
182300
});

0 commit comments

Comments
 (0)