Skip to content

Commit 4f3176c

Browse files
committed
command: improve detection of changes in traces
* Added lookup table to search commands by value. Fixes plotly#1169
1 parent a72f9da commit 4f3176c

File tree

1 file changed

+98
-22
lines changed

1 file changed

+98
-22
lines changed

src/plots/command.js

+98-22
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
4242

4343
// Either create or just recompute this:
4444
ret.bindingsByValue = {};
45+
ret.valueByBindings = {};
4546

46-
var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.bindingsByValue);
47+
var binding = exports.hasSimpleAPICommandBindings(gd, commandList,
48+
ret.bindingsByValue, ret.valueByBindings);
4749

4850
if(container && container._commandObserver) {
4951
if(!binding) {
@@ -78,14 +80,15 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
7880
if(update.changed && onchange) {
7981
// Disable checks for the duration of this command in order to avoid
8082
// infinite loops:
81-
if(ret.bindingsByValue[update.value] !== undefined) {
83+
var index = getCommandIndex(ret, update.value);
84+
if(index !== undefined) {
8285
ret.disable();
8386
Promise.resolve(onchange({
8487
value: update.value,
8588
type: binding.type,
8689
prop: binding.prop,
8790
traces: binding.traces,
88-
index: ret.bindingsByValue[update.value]
91+
index: index
8992
})).then(ret.enable, ret.enable);
9093
}
9194
}
@@ -117,6 +120,7 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
117120
Lib.warn('Unable to automatically bind plot updates to API command');
118121

119122
ret.bindingsByValue = {};
123+
ret.valueByBindings = {};
120124
ret.remove = function() {};
121125
}
122126

@@ -135,6 +139,39 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
135139
return ret;
136140
};
137141

142+
function getCommandIndex(binding, value) {
143+
if(Array.isArray(value)) {
144+
var commandIndex;
145+
146+
if(value.length === 1) {
147+
commandIndex = binding.bindingsByValue[value[0]];
148+
if(commandIndex !== undefined) return commandIndex;
149+
}
150+
151+
for(commandIndex in binding.valueByBindings) {
152+
var commandValue = binding.valueByBindings[commandIndex];
153+
if(commandValue.length !== value.length) continue;
154+
155+
var traces = binding.traces,
156+
areEqual = true;
157+
for(var i = 0; i < value.length; i++) {
158+
if(value[traces ? traces[i] : i] !== commandValue[i]) {
159+
areEqual = false;
160+
break;
161+
}
162+
}
163+
164+
if(areEqual) return +commandIndex;
165+
}
166+
167+
// if not found, return undefined
168+
return;
169+
}
170+
else {
171+
return binding.bindingsByValue[value];
172+
}
173+
}
174+
138175
/*
139176
* This function checks to see if an array of objects containing
140177
* method and args properties is compatible with automatic two-way
@@ -144,7 +181,7 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
144181
* 2. only one property may be affected
145182
* 3. the same property must be affected by all commands
146183
*/
147-
exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue) {
184+
exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue, valueByBindings) {
148185
var n = commandList.length;
149186

150187
var refBinding;
@@ -200,9 +237,11 @@ exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue)
200237
binding = bindings[0];
201238
var value = binding.value;
202239
if(Array.isArray(value)) {
203-
value = value[0];
240+
// an array value can't be an index,
241+
// use valueByBindings instead
242+
if(valueByBindings) valueByBindings[i] = value;
204243
}
205-
if(bindingsByValue) {
244+
else if(bindingsByValue) {
206245
bindingsByValue[value] = i;
207246
}
208247
}
@@ -211,31 +250,68 @@ exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue)
211250
};
212251

213252
function bindingValueHasChanged(gd, binding, cache) {
214-
var container, value, obj;
215-
var changed = false;
216-
217253
if(binding.type === 'data') {
218-
// If it's data, we need to get a trace. Based on the limited scope
219-
// of what we cover, we can just take the first trace from the list,
220-
// or otherwise just the first trace:
221-
container = gd._fullData[binding.traces !== null ? binding.traces[0] : 0];
222-
} else if(binding.type === 'layout') {
223-
container = gd._fullLayout;
224-
} else {
225-
return false;
254+
return dataBindingValueHasChanged(gd, binding, cache);
255+
}
256+
257+
if(binding.type === 'layout') {
258+
return layoutBindingValueHasChanged(gd, binding, cache);
226259
}
227260

228-
value = Lib.nestedProperty(container, binding.prop).get();
261+
return false;
262+
}
229263

230-
obj = cache[binding.type] = cache[binding.type] || {};
264+
function layoutBindingValueHasChanged(gd, binding, cache) {
265+
// get value from container
266+
var container = gd._fullLayout,
267+
value = Lib.nestedProperty(container, binding.prop).get();
231268

232-
if(obj.hasOwnProperty(binding.prop)) {
233-
if(obj[binding.prop] !== value) {
269+
// update value in cache
270+
var thisCache = cache[binding.type] = cache[binding.type] || {},
271+
changed = false;
272+
273+
if(!thisCache.hasOwnProperty(binding.prop)) {
274+
if(thisCache[binding.prop] !== value) {
234275
changed = true;
235276
}
236277
}
237278

238-
obj[binding.prop] = value;
279+
thisCache[binding.prop] = value;
280+
281+
return {
282+
changed: changed,
283+
value: value
284+
};
285+
}
286+
287+
function dataBindingValueHasChanged(gd, binding, cache) {
288+
// get value from container
289+
var fullData = gd._fullData,
290+
value = [],
291+
i;
292+
for(i = 0; i < fullData.length; i++) {
293+
value.push(Lib.nestedProperty(fullData[i], binding.prop).get());
294+
}
295+
296+
// update value in cache
297+
var thisCache = cache[binding.type] = cache[binding.type] || {},
298+
changed = false;
299+
300+
if(thisCache.hasOwnProperty(binding.prop)) {
301+
var cachedValue = thisCache[binding.prop];
302+
if(Array.isArray(cachedValue)) {
303+
for(i = 0; i < fullData.length; i++) {
304+
if(value[i] !== cachedValue[i]) changed = true;
305+
}
306+
}
307+
else {
308+
for(i = 0; i < fullData.length; i++) {
309+
if(value[i] !== cachedValue) changed = true;
310+
}
311+
}
312+
}
313+
314+
thisCache[binding.prop] = value;
239315

240316
return {
241317
changed: changed,

0 commit comments

Comments
 (0)