@@ -42,8 +42,10 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
42
42
43
43
// Either create or just recompute this:
44
44
ret . bindingsByValue = { } ;
45
+ ret . valueByBindings = { } ;
45
46
46
- var binding = exports . hasSimpleAPICommandBindings ( gd , commandList , ret . bindingsByValue ) ;
47
+ var binding = exports . hasSimpleAPICommandBindings ( gd , commandList ,
48
+ ret . bindingsByValue , ret . valueByBindings ) ;
47
49
48
50
if ( container && container . _commandObserver ) {
49
51
if ( ! binding ) {
@@ -78,14 +80,15 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
78
80
if ( update . changed && onchange ) {
79
81
// Disable checks for the duration of this command in order to avoid
80
82
// infinite loops:
81
- if ( ret . bindingsByValue [ update . value ] !== undefined ) {
83
+ var index = getCommandIndex ( ret , update . value ) ;
84
+ if ( index !== undefined ) {
82
85
ret . disable ( ) ;
83
86
Promise . resolve ( onchange ( {
84
87
value : update . value ,
85
88
type : binding . type ,
86
89
prop : binding . prop ,
87
90
traces : binding . traces ,
88
- index : ret . bindingsByValue [ update . value ]
91
+ index : index
89
92
} ) ) . then ( ret . enable , ret . enable ) ;
90
93
}
91
94
}
@@ -117,6 +120,7 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
117
120
Lib . warn ( 'Unable to automatically bind plot updates to API command' ) ;
118
121
119
122
ret . bindingsByValue = { } ;
123
+ ret . valueByBindings = { } ;
120
124
ret . remove = function ( ) { } ;
121
125
}
122
126
@@ -135,6 +139,39 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
135
139
return ret ;
136
140
} ;
137
141
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
+
138
175
/*
139
176
* This function checks to see if an array of objects containing
140
177
* method and args properties is compatible with automatic two-way
@@ -144,13 +181,12 @@ exports.manageCommandObserver = function(gd, container, commandList, onchange) {
144
181
* 2. only one property may be affected
145
182
* 3. the same property must be affected by all commands
146
183
*/
147
- exports . hasSimpleAPICommandBindings = function ( gd , commandList , bindingsByValue ) {
148
- var i ;
184
+ exports . hasSimpleAPICommandBindings = function ( gd , commandList , bindingsByValue , valueByBindings ) {
149
185
var n = commandList . length ;
150
186
151
187
var refBinding ;
152
188
153
- for ( i = 0 ; i < n ; i ++ ) {
189
+ for ( var i = 0 ; i < n ; i ++ ) {
154
190
var binding ;
155
191
var command = commandList [ i ] ;
156
192
var method = command . method ;
@@ -201,13 +237,11 @@ exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue)
201
237
binding = bindings [ 0 ] ;
202
238
var value = binding . value ;
203
239
if ( Array . isArray ( value ) ) {
204
- if ( value . length === 1 ) {
205
- value = value [ 0 ] ;
206
- } else {
207
- return false ;
208
- }
240
+ // an array value can't be an index,
241
+ // use valueByBindings instead
242
+ if ( valueByBindings ) valueByBindings [ i ] = value ;
209
243
}
210
- if ( bindingsByValue ) {
244
+ else if ( bindingsByValue ) {
211
245
bindingsByValue [ value ] = i ;
212
246
}
213
247
}
@@ -216,31 +250,68 @@ exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue)
216
250
} ;
217
251
218
252
function bindingValueHasChanged ( gd , binding , cache ) {
219
- var container , value , obj ;
220
- var changed = false ;
221
-
222
253
if ( binding . type === 'data' ) {
223
- // If it's data, we need to get a trace. Based on the limited scope
224
- // of what we cover, we can just take the first trace from the list,
225
- // or otherwise just the first trace:
226
- container = gd . _fullData [ binding . traces !== null ? binding . traces [ 0 ] : 0 ] ;
227
- } else if ( binding . type === 'layout' ) {
228
- container = gd . _fullLayout ;
229
- } else {
230
- return false ;
254
+ return dataBindingValueHasChanged ( gd , binding , cache ) ;
255
+ }
256
+
257
+ if ( binding . type === 'layout' ) {
258
+ return layoutBindingValueHasChanged ( gd , binding , cache ) ;
231
259
}
232
260
233
- value = Lib . nestedProperty ( container , binding . prop ) . get ( ) ;
261
+ return false ;
262
+ }
263
+
264
+ function layoutBindingValueHasChanged ( gd , binding , cache ) {
265
+ // get value from container
266
+ var container = gd . _fullLayout ,
267
+ value = Lib . nestedProperty ( container , binding . prop ) . get ( ) ;
234
268
235
- obj = cache [ binding . type ] = cache [ binding . type ] || { } ;
269
+ // update value in cache
270
+ var thisCache = cache [ binding . type ] = cache [ binding . type ] || { } ,
271
+ changed = false ;
236
272
237
- if ( obj . hasOwnProperty ( binding . prop ) ) {
238
- if ( obj [ binding . prop ] !== value ) {
273
+ if ( ! thisCache . hasOwnProperty ( binding . prop ) ) {
274
+ if ( thisCache [ binding . prop ] !== value ) {
239
275
changed = true ;
240
276
}
241
277
}
242
278
243
- 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 ;
244
315
245
316
return {
246
317
changed : changed ,
0 commit comments