1
- import { create , cross , difference , groups , InternMap } from "d3" ;
1
+ import { create , cross , difference , groups , InternMap , select , union } from "d3" ;
2
2
import { Axes , autoAxisTicks , autoScaleLabels } from "./axes.js" ;
3
3
import { Channel , channelSort } from "./channel.js" ;
4
4
import { defined } from "./defined.js" ;
@@ -93,18 +93,19 @@ export function plot(options = {}) {
93
93
. call ( applyInlineStyles , style )
94
94
. node ( ) ;
95
95
96
- let value ;
96
+ let initialValue ;
97
97
for ( const mark of marks ) {
98
98
const channels = markChannels . get ( mark ) ?? [ ] ;
99
99
const values = applyScales ( channels , scales ) ;
100
100
const index = filter ( markIndex . get ( mark ) , channels , values ) ;
101
101
const node = mark . render ( index , scales , values , dimensions , axes ) ;
102
102
if ( node != null ) {
103
- // TODO More explicit indication that a mark defines a value?
104
- // TODO Will the name “selection” lead to a false positive on random SVG elements?
103
+ // TODO A more explicit indication that a mark defines a value (e.g., a symbol)?
105
104
if ( node . selection !== undefined ) {
106
- value = take ( mark . data , node . selection ) ;
107
- node . addEventListener ( "input" , ( ) => figure . value = take ( mark . data , node . selection ) ) ;
105
+ initialValue = markValue ( mark , node . selection ) ;
106
+ node . addEventListener ( "input" , ( ) => {
107
+ figure . value = markValue ( mark , node . selection ) ;
108
+ } ) ;
108
109
}
109
110
svg . appendChild ( node ) ;
110
111
}
@@ -126,7 +127,7 @@ export function plot(options = {}) {
126
127
127
128
figure . scale = exposeScales ( scaleDescriptors ) ;
128
129
figure . legend = exposeLegends ( scaleDescriptors , options ) ;
129
- figure . value = value ;
130
+ figure . value = initialValue ;
130
131
return figure ;
131
132
}
132
133
@@ -197,6 +198,10 @@ function markify(mark) {
197
198
return mark instanceof Mark ? mark : new Render ( mark ) ;
198
199
}
199
200
201
+ function markValue ( mark , selection ) {
202
+ return selection === null ? mark . data : take ( mark . data , selection ) ;
203
+ }
204
+
200
205
class Render extends Mark {
201
206
constructor ( render ) {
202
207
super ( ) ;
@@ -271,16 +276,17 @@ class Facet extends Mark {
271
276
}
272
277
return { index, channels : [ ...channels , ...subchannels ] } ;
273
278
}
274
- render ( I , scales , channels , dimensions , axes ) {
275
- const { marks, marksChannels, marksIndexByFacet} = this ;
279
+ render ( I , scales , _ , dimensions , axes ) {
280
+ const { data , channels , marks, marksChannels, marksIndexByFacet} = this ;
276
281
const { fx, fy} = scales ;
277
282
const fyDomain = fy && fy . domain ( ) ;
278
283
const fxDomain = fx && fx . domain ( ) ;
279
284
const fyMargins = fy && { marginTop : 0 , marginBottom : 0 , height : fy . bandwidth ( ) } ;
280
285
const fxMargins = fx && { marginRight : 0 , marginLeft : 0 , width : fx . bandwidth ( ) } ;
281
286
const subdimensions = { ...dimensions , ...fxMargins , ...fyMargins } ;
282
287
const marksValues = marksChannels . map ( channels => applyScales ( channels , scales ) ) ;
283
- return create ( "svg:g" )
288
+ let selectionByFacet ;
289
+ const parent = create ( "svg:g" )
284
290
. call ( g => {
285
291
if ( fy && axes . y ) {
286
292
const axis1 = axes . y , axis2 = nolabel ( axis1 ) ;
@@ -324,10 +330,25 @@ class Facet extends Mark {
324
330
const values = marksValues [ i ] ;
325
331
const index = filter ( marksFacetIndex [ i ] , marksChannels [ i ] , values ) ;
326
332
const node = marks [ i ] . render ( index , scales , values , subdimensions ) ;
327
- if ( node != null ) this . appendChild ( node ) ;
333
+ if ( node != null ) {
334
+ if ( node . selection !== undefined ) {
335
+ if ( marks [ i ] . data !== data ) throw new Error ( "selection must use facet data" ) ;
336
+ if ( selectionByFacet === undefined ) selectionByFacet = facetMap ( channels ) ;
337
+ selectionByFacet . set ( key , node . selection ) ;
338
+ node . addEventListener ( "input" , ( ) => {
339
+ selectionByFacet . set ( key , node . selection ) ;
340
+ parent . selection = facetSelection ( selectionByFacet ) ;
341
+ } ) ;
342
+ }
343
+ this . appendChild ( node ) ;
344
+ }
328
345
}
329
346
} ) )
330
347
. node ( ) ;
348
+ if ( selectionByFacet !== undefined ) {
349
+ parent . selection = facetSelection ( selectionByFacet ) ;
350
+ }
351
+ return parent ;
331
352
}
332
353
}
333
354
@@ -370,6 +391,17 @@ function facetTranslate(fx, fy) {
370
391
: ky => `translate(0,${ fy ( ky ) } )` ;
371
392
}
372
393
394
+ // If multiple facets define a selection, then the overall selection is the
395
+ // union of the defined selections.
396
+ function facetSelection ( selectionByFacet ) {
397
+ let selection = null ;
398
+ for ( const value of selectionByFacet . values ( ) ) {
399
+ if ( value === null ) continue ;
400
+ selection = selection === null ? value : union ( selection , value ) ;
401
+ }
402
+ return selection ;
403
+ }
404
+
373
405
function facetMap ( channels ) {
374
406
return new ( channels . length > 1 ? FacetMap2 : FacetMap ) ;
375
407
}
@@ -387,6 +419,9 @@ class FacetMap {
387
419
set ( key , value ) {
388
420
return this . _ . set ( key , value ) , this ;
389
421
}
422
+ values ( ) {
423
+ return this . _ . values ( ) ;
424
+ }
390
425
}
391
426
392
427
// A Map-like interface that supports paired keys.
@@ -405,4 +440,9 @@ class FacetMap2 extends FacetMap {
405
440
else super . set ( key1 , new InternMap ( [ [ key2 , value ] ] ) ) ;
406
441
return this ;
407
442
}
443
+ * values ( ) {
444
+ for ( const map of this . _ . values ( ) ) {
445
+ yield * map . values ( ) ;
446
+ }
447
+ }
408
448
}
0 commit comments