1
- import { create , cross , difference , groups , InternMap } from "d3" ;
1
+ import { create , cross , difference , groups , InternMap , 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" ;
5
5
import { Dimensions } from "./dimensions.js" ;
6
6
import { Legends , exposeLegends } from "./legends.js" ;
7
- import { arrayify , isOptions , keyword , range , first , second , where } from "./options.js" ;
7
+ import { arrayify , isOptions , keyword , range , first , second , where , take } from "./options.js" ;
8
8
import { Scales , ScaleFunctions , autoScaleRange , applyScales , exposeScales } from "./scales.js" ;
9
9
import { applyInlineStyles , maybeClassName , styles } from "./style.js" ;
10
10
import { basic } from "./transforms/basic.js" ;
@@ -95,12 +95,22 @@ export function plot(options = {}) {
95
95
. call ( applyInlineStyles , style )
96
96
. node ( ) ;
97
97
98
+ let initialValue ;
98
99
for ( const mark of marks ) {
99
100
const channels = markChannels . get ( mark ) ?? [ ] ;
100
101
const values = applyScales ( channels , scales ) ;
101
102
const index = filter ( markIndex . get ( mark ) , channels , values ) ;
102
103
const node = mark . render ( index , scales , values , dimensions , axes ) ;
103
- if ( node != null ) svg . appendChild ( node ) ;
104
+ if ( node != null ) {
105
+ // TODO A more explicit indication that a mark defines a value (e.g., a symbol)?
106
+ if ( node . selection !== undefined ) {
107
+ initialValue = markValue ( mark , node . selection ) ;
108
+ node . addEventListener ( "input" , ( ) => {
109
+ figure . value = markValue ( mark , node . selection ) ;
110
+ } ) ;
111
+ }
112
+ svg . appendChild ( node ) ;
113
+ }
104
114
}
105
115
106
116
// Wrap the plot in a figure with a caption, if desired.
@@ -119,6 +129,7 @@ export function plot(options = {}) {
119
129
120
130
figure . scale = exposeScales ( scaleDescriptors ) ;
121
131
figure . legend = exposeLegends ( scaleDescriptors , options ) ;
132
+ figure . value = initialValue ;
122
133
return figure ;
123
134
}
124
135
@@ -189,6 +200,10 @@ function markify(mark) {
189
200
return mark instanceof Mark ? mark : new Render ( mark ) ;
190
201
}
191
202
203
+ function markValue ( mark , selection ) {
204
+ return selection === null ? mark . data : take ( mark . data , selection ) ;
205
+ }
206
+
192
207
class Render extends Mark {
193
208
constructor ( render ) {
194
209
super ( ) ;
@@ -263,16 +278,17 @@ class Facet extends Mark {
263
278
}
264
279
return { index, channels : [ ...channels , ...subchannels ] } ;
265
280
}
266
- render ( I , scales , channels , dimensions , axes ) {
267
- const { marks, marksChannels, marksIndexByFacet} = this ;
281
+ render ( I , scales , _ , dimensions , axes ) {
282
+ const { data , channels , marks, marksChannels, marksIndexByFacet} = this ;
268
283
const { fx, fy} = scales ;
269
284
const fyDomain = fy && fy . domain ( ) ;
270
285
const fxDomain = fx && fx . domain ( ) ;
271
286
const fyMargins = fy && { marginTop : 0 , marginBottom : 0 , height : fy . bandwidth ( ) } ;
272
287
const fxMargins = fx && { marginRight : 0 , marginLeft : 0 , width : fx . bandwidth ( ) } ;
273
288
const subdimensions = { ...dimensions , ...fxMargins , ...fyMargins } ;
274
289
const marksValues = marksChannels . map ( channels => applyScales ( channels , scales ) ) ;
275
- return create ( "svg:g" )
290
+ let selectionByFacet ;
291
+ const parent = create ( "svg:g" )
276
292
. call ( g => {
277
293
if ( fy && axes . y ) {
278
294
const axis1 = axes . y , axis2 = nolabel ( axis1 ) ;
@@ -316,10 +332,25 @@ class Facet extends Mark {
316
332
const values = marksValues [ i ] ;
317
333
const index = filter ( marksFacetIndex [ i ] , marksChannels [ i ] , values ) ;
318
334
const node = marks [ i ] . render ( index , scales , values , subdimensions ) ;
319
- if ( node != null ) this . appendChild ( node ) ;
335
+ if ( node != null ) {
336
+ if ( node . selection !== undefined ) {
337
+ if ( marks [ i ] . data !== data ) throw new Error ( "selection must use facet data" ) ;
338
+ if ( selectionByFacet === undefined ) selectionByFacet = facetMap ( channels ) ;
339
+ selectionByFacet . set ( key , node . selection ) ;
340
+ node . addEventListener ( "input" , ( ) => {
341
+ selectionByFacet . set ( key , node . selection ) ;
342
+ parent . selection = facetSelection ( selectionByFacet ) ;
343
+ } ) ;
344
+ }
345
+ this . appendChild ( node ) ;
346
+ }
320
347
}
321
348
} ) )
322
349
. node ( ) ;
350
+ if ( selectionByFacet !== undefined ) {
351
+ parent . selection = facetSelection ( selectionByFacet ) ;
352
+ }
353
+ return parent ;
323
354
}
324
355
}
325
356
@@ -362,6 +393,20 @@ function facetTranslate(fx, fy) {
362
393
: ky => `translate(0,${ fy ( ky ) } )` ;
363
394
}
364
395
396
+ // If multiple facets define a selection, then the overall selection is the
397
+ // union of the defined selections. As with non-faceted plots, we assume that
398
+ // only a single mark is defining the selection; if multiple marks define a
399
+ // selection, generally speaking the last one wins, although the behavior is not
400
+ // explicitly defined.
401
+ function facetSelection ( selectionByFacet ) {
402
+ let selection = null ;
403
+ for ( const value of selectionByFacet . values ( ) ) {
404
+ if ( value === null ) continue ;
405
+ selection = selection === null ? value : union ( selection , value ) ;
406
+ }
407
+ return selection ;
408
+ }
409
+
365
410
function facetMap ( channels ) {
366
411
return new ( channels . length > 1 ? FacetMap2 : FacetMap ) ;
367
412
}
@@ -379,6 +424,9 @@ class FacetMap {
379
424
set ( key , value ) {
380
425
return this . _ . set ( key , value ) , this ;
381
426
}
427
+ values ( ) {
428
+ return this . _ . values ( ) ;
429
+ }
382
430
}
383
431
384
432
// A Map-like interface that supports paired keys.
@@ -397,4 +445,9 @@ class FacetMap2 extends FacetMap {
397
445
else super . set ( key1 , new InternMap ( [ [ key2 , value ] ] ) ) ;
398
446
return this ;
399
447
}
448
+ * values ( ) {
449
+ for ( const map of this . _ . values ( ) ) {
450
+ yield * map . values ( ) ;
451
+ }
452
+ }
400
453
}
0 commit comments