@@ -93,31 +93,43 @@ HTMLWidgets.widget({
93
93
// The pointsToKeys and keysToPoints functions let you convert
94
94
// between the two schemes.
95
95
96
- // To allow translation from keys to points in O(1) time, we
96
+ // Combine the name of a set and key into a single string, suitable for
97
+ // using as a keyCache key.
98
+ function joinSetAndKey ( set , key ) {
99
+ return set + "\n" + key ;
100
+ }
101
+
102
+ // To allow translation from sets+keys to points in O(1) time, we
97
103
// make a cache that lets us map keys to objects with
98
104
// {curveNumber, pointNumber} properties.
99
105
var keyCache = { } ;
100
106
for ( var curve = 0 ; curve < x . data . length ; curve ++ ) {
101
- if ( ! x . data [ curve ] . key ) {
107
+ var curveObj = x . data [ curve ] ;
108
+ if ( ! curveObj . key || ! curveObj . set ) {
102
109
continue ;
103
110
}
104
- for ( var pointIdx = 0 ; pointIdx < x . data [ curve ] . key . length ; pointIdx ++ ) {
105
- keyCache [ x . data [ curve ] . key [ pointIdx ] ] = { curveNumber : curve , pointNumber : pointIdx } ;
111
+ for ( var pointIdx = 0 ; pointIdx < curveObj . key . length ; pointIdx ++ ) {
112
+ keyCache [ joinSetAndKey ( curveObj . set , curveObj . key [ pointIdx ] ) ] =
113
+ { curveNumber : curve , pointNumber : pointIdx } ;
106
114
}
107
115
}
108
116
109
117
// Given an array of {curveNumber: x, pointNumber: y} objects,
110
- // return an array of key strings.
111
- // TODO: Throw proper error if any point is invalid or doesn't
112
- // have a key?
118
+ // return a hash of {[set1]: [key1, key2, ...], [set2]: [key3, key4, ...]}
113
119
function pointsToKeys ( points ) {
114
- var keys = [ ] ;
120
+ var keysBySet = { } ;
115
121
for ( var i = 0 ; i < points . length ; i ++ ) {
122
+ var curveObj = graphDiv . data [ points [ i ] . curveNumber ] ;
123
+ if ( ! curveObj . key || ! curveObj . set ) {
124
+ // If this curve isn't mapped to a set, ignore this point.
125
+ continue ;
126
+ }
116
127
// Look up the keys
117
- var key = graphDiv . data [ points [ i ] . curveNumber ] . key [ points [ i ] . pointNumber ] ;
118
- keys . push ( key ) ;
128
+ var key = curveObj . key [ points [ i ] . pointNumber ] ;
129
+ keysBySet [ curveObj . set ] = keysBySet [ curveObj . set ] || [ ] ;
130
+ keysBySet [ curveObj . set ] . push ( key ) ;
119
131
}
120
- return keys ;
132
+ return keysBySet ;
121
133
}
122
134
123
135
// Given an array of strings, return an object that hierarchically
@@ -136,10 +148,10 @@ HTMLWidgets.widget({
136
148
// "0": [1, 2],
137
149
// "2": [1]
138
150
// }
139
- function keysToPoints ( keys ) {
151
+ function keysToPoints ( set , keys ) {
140
152
var curves = { } ;
141
153
for ( var i = 0 ; i < keys . length ; i ++ ) {
142
- var pt = keyCache [ keys [ i ] ] ;
154
+ var pt = keyCache [ joinSetAndKey ( set , keys [ i ] ) ] ;
143
155
if ( ! pt ) {
144
156
throw new Error ( "Unknown key " + keys [ i ] ) ;
145
157
}
@@ -148,84 +160,115 @@ HTMLWidgets.widget({
148
160
}
149
161
return curves ;
150
162
}
163
+
164
+ // Gather all sets.
165
+ var crosstalkGroups = { } ;
166
+ var allSets = [ ] ;
167
+ for ( var curveIdx = 0 ; curveIdx < x . data . length ; curveIdx ++ ) {
168
+ if ( x . data [ curveIdx ] . set ) {
169
+ if ( ! crosstalkGroups [ x . data [ curveIdx ] . set ] ) {
170
+ allSets . push ( x . data [ curveIdx ] . set ) ;
171
+ crosstalkGroups [ x . data [ curveIdx ] . set ] = [ ] ;
172
+ }
173
+ crosstalkGroups [ x . data [ curveIdx ] . set ] . push ( curveIdx ) ;
174
+ }
175
+ }
151
176
152
- // Grab the specified crosstalk group.
153
- if ( x . set ) {
154
- var grp = crosstalk . group ( x . set ) ;
155
-
177
+ if ( allSets . length > 0 ) {
156
178
// When plotly selection changes, update crosstalk
157
179
graphDiv . on ( "plotly_selected" , function plotly_selecting ( e ) {
158
180
if ( e ) {
159
181
var selectedKeys = pointsToKeys ( e . points ) ;
160
- grp . var ( "selection" ) . set ( selectedKeys , { sender : el } ) ;
182
+ // Keys are group names, values are array of selected keys from group.
183
+ for ( var set in selectedKeys ) {
184
+ if ( selectedKeys . hasOwnProperty ( set ) )
185
+ crosstalk . group ( set ) . var ( "selection" ) . set ( selectedKeys [ set ] , { sender : el } ) ;
186
+ }
187
+ // Any groups that weren't represented in the selection, should be
188
+ // treated as if zero points were selected.
189
+ for ( var i = 0 ; i < allSets . length ; i ++ ) {
190
+ if ( ! selectedKeys [ allSets [ i ] ] ) {
191
+ crosstalk . group ( allSets [ i ] ) . var ( "selection" ) . set ( [ ] , { sender : el } ) ;
192
+ }
193
+ }
161
194
}
162
195
} ) ;
163
196
// When plotly selection is cleared, update crosstalk
164
197
graphDiv . on ( "plotly_deselect" , function plotly_deselect ( e ) {
165
- grp . var ( "selection" ) . set ( null ) ;
166
- } ) ;
167
-
168
- // When crosstalk selection changes, update plotly style
169
- grp . var ( "selection" ) . on ( "change" , function crosstalk_sel_change ( e ) {
170
- // e.value is either null, or an array of newly selected values
171
-
172
- if ( e . sender !== el ) {
173
- // If we're not the originator of this selection, and we have an
174
- // active selection outline box, we need to remove it. Otherwise
175
- // it could appear like there are two active brushes in one plot
176
- // group.
177
- var outlines = el . querySelectorAll ( ".select-outline" ) ;
178
- for ( var i = 0 ; i < outlines . length ; i ++ ) {
179
- outlines [ i ] . remove ( ) ;
180
- }
198
+ for ( var i = 0 ; i < allSets . length ; i ++ ) {
199
+ crosstalk . group ( allSets [ i ] ) . var ( "selection" ) . set ( null , { sender : el } ) ;
181
200
}
182
-
183
- // Restyle each relevant trace
184
- var selectedPoints = keysToPoints ( e . value || [ ] ) ;
185
-
186
- var opacityTraces = [ ] ;
187
- var traceIndices = [ ] ;
188
-
189
- for ( var i = 0 ; i < x . data . length ; i ++ ) {
190
- if ( ! x . data [ i ] . key ) {
191
- // Not a brushable trace apparently. Don't restyle.
192
- continue ;
193
- }
194
-
195
- // Make an opacity array, one element for each data point
196
- // in this trace.
197
- var opacity = new Array ( x . data [ i ] . x . length ) ;
198
-
199
- if ( typeof ( e . value ) === "undefined" || e . value === null ) {
200
- // The Crosstalk selection has been cleared. Full opacity
201
- for ( var k = 0 ; k < opacity . length ; k ++ ) {
202
- opacity [ k ] = 1 ;
201
+ } ) ;
202
+
203
+ for ( var i = 0 ; i < allSets . length ; i ++ ) {
204
+ ( function ( ) {
205
+ var set = allSets [ i ] ;
206
+ var grp = crosstalk . group ( set ) ;
207
+
208
+ // When crosstalk selection changes, update plotly style
209
+ grp . var ( "selection" ) . on ( "change" , function crosstalk_sel_change ( e ) {
210
+ // e.value is either null, or an array of newly selected values
211
+
212
+ if ( e . sender !== el ) {
213
+ // If we're not the originator of this selection, and we have an
214
+ // active selection outline box, we need to remove it. Otherwise
215
+ // it could appear like there are two active brushes in one plot
216
+ // group.
217
+ var outlines = el . querySelectorAll ( ".select-outline" ) ;
218
+ for ( var i = 0 ; i < outlines . length ; i ++ ) {
219
+ outlines [ i ] . remove ( ) ;
220
+ }
203
221
}
204
- } else {
205
- // Array of pointNumber numbers that should be highlighted
206
- var theseSelectedPoints = selectedPoints [ i ] || [ ] ;
207
222
208
- for ( var j = 0 ; j < opacity . length ; j ++ ) {
209
- if ( theseSelectedPoints . indexOf ( j ) >= 0 ) {
210
- opacity [ j ] = 1 ;
223
+ // Restyle each relevant trace
224
+ var selectedPoints = keysToPoints ( set , e . value || [ ] ) ;
225
+
226
+ var opacityTraces = [ ] ;
227
+ var relevantTraces = crosstalkGroups [ set ] ;
228
+
229
+ for ( var i = 0 ; i < relevantTraces . length ; i ++ ) {
230
+ var trace = x . data [ relevantTraces [ i ] ] ;
231
+
232
+ // Make an opacity array, one element for each data point
233
+ // in this trace.
234
+ var opacity = new Array ( trace . x . length ) ;
235
+
236
+ if ( typeof ( e . value ) === "undefined" || e . value === null ) {
237
+ // The Crosstalk selection has been cleared. Full opacity
238
+ for ( var k = 0 ; k < opacity . length ; k ++ ) {
239
+ opacity [ k ] = 1 ;
240
+ }
211
241
} else {
212
- opacity [ j ] = 0.2 ;
242
+ // Array of pointNumber numbers that should be highlighted
243
+ var theseSelectedPoints = selectedPoints [ relevantTraces [ i ] ] || [ ] ;
244
+
245
+ for ( var j = 0 ; j < opacity . length ; j ++ ) {
246
+ if ( theseSelectedPoints . indexOf ( j ) >= 0 ) {
247
+ opacity [ j ] = 1 ;
248
+ } else {
249
+ opacity [ j ] = 0.2 ;
250
+ }
251
+ }
213
252
}
253
+
254
+ opacityTraces . push ( opacity ) ;
214
255
}
215
- }
216
-
217
- opacityTraces . push ( opacity ) ;
218
- traceIndices . push ( i ) ;
219
- }
220
- // Restyle the current trace
221
- Plotly . restyle ( graphDiv , { "marker.opacity" : opacityTraces } , traceIndices ) ;
222
- } ) ;
256
+ console . log ( graphDiv . id , relevantTraces , opacityTraces )
257
+ // Restyle the current trace
258
+ Plotly . restyle ( graphDiv , { "marker.opacity" : opacityTraces } , relevantTraces ) ;
259
+ } ) ;
260
+
261
+ // Remove event listeners in the future
262
+ instance . onNextRender . push ( function ( ) {
263
+ grp . removeListener ( "selection" , crosstalk_sel_change ) ;
264
+ } ) ;
265
+ } ) ( ) ;
266
+ }
223
267
224
268
// Remove event listeners in the future
225
269
instance . onNextRender . push ( function ( ) {
226
270
graphDiv . removeListener ( "plotly_selecting" , plotly_selecting ) ;
227
271
graphDiv . removeListener ( "plotly_deselect" , plotly_deselect ) ;
228
- grp . removeListener ( "selection" , crosstalk_sel_change ) ;
229
272
} ) ;
230
273
}
231
274
0 commit comments