@@ -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,7 +181,7 @@ 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 ) {
184
+ exports . hasSimpleAPICommandBindings = function ( gd , commandList , bindingsByValue , valueByBindings ) {
148
185
var n = commandList . length ;
149
186
150
187
var refBinding ;
@@ -200,9 +237,11 @@ exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue)
200
237
binding = bindings [ 0 ] ;
201
238
var value = binding . value ;
202
239
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 ;
204
243
}
205
- if ( bindingsByValue ) {
244
+ else if ( bindingsByValue ) {
206
245
bindingsByValue [ value ] = i ;
207
246
}
208
247
}
@@ -211,31 +250,68 @@ exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue)
211
250
} ;
212
251
213
252
function bindingValueHasChanged ( gd , binding , cache ) {
214
- var container , value , obj ;
215
- var changed = false ;
216
-
217
253
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 ) ;
226
259
}
227
260
228
- value = Lib . nestedProperty ( container , binding . prop ) . get ( ) ;
261
+ return false ;
262
+ }
229
263
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 ( ) ;
231
268
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 ) {
234
275
changed = true ;
235
276
}
236
277
}
237
278
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 ;
239
315
240
316
return {
241
317
changed : changed ,
0 commit comments