1
1
import { create , cross , difference , groups , InternMap , select } from "d3" ;
2
2
import { Axes , autoAxisTicks , autoScaleLabels } from "./axes.js" ;
3
- import { Channel , channelSort } from "./channel.js" ;
3
+ import { Channel , channelObject , channelSort , valueObject } 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 , second , where } from "./options.js" ;
8
- import { Scales , ScaleFunctions , autoScaleRange , applyScales , exposeScales } from "./scales.js" ;
7
+ import { arrayify , isOptions , isScaleOptions , keyword , range , second , where , yes } from "./options.js" ;
8
+ import { Scales , ScaleFunctions , autoScaleRange , exposeScales } from "./scales.js" ;
9
+ import { registry as scaleRegistry } from "./scales/index.js" ;
9
10
import { applyInlineStyles , maybeClassName , maybeClip , styles } from "./style.js" ;
10
11
import { basic } from "./transforms/basic.js" ;
11
12
import { consumeWarnings } from "./warnings.js" ;
@@ -29,25 +30,35 @@ export function plot(options = {}) {
29
30
// A Map from scale name to an array of associated channels.
30
31
const channelsByScale = new Map ( ) ;
31
32
33
+ // If a scale is explicitly declared in options, initialize its associated
34
+ // channels to the empty array; this will guarantee that a corresponding scale
35
+ // will be created later (even if there are no other channels). But ignore
36
+ // facet scale declarations if faceting is not enabled.
37
+ for ( const key of scaleRegistry . keys ( ) ) {
38
+ if ( isScaleOptions ( options [ key ] ) && key !== "fx" && key !== "fy" ) {
39
+ channelsByScale . set ( key , [ ] ) ;
40
+ }
41
+ }
42
+
32
43
// Faceting!
33
44
let facets ; // array of facet definitions (e.g. [["foo", [0, 1, 3, …]], …])
34
45
let facetIndex ; // index over the facet data, e.g. [0, 1, 2, 3, …]
35
- let facetChannels ; // e.g. [["fx", {value}], ["fy", {value}]]
46
+ let facetChannels ; // e.g. {fx: {value}, fy: {value}}
36
47
let facetsIndex ; // nested array of facet indexes [[0, 1, 3, …], [2, 5, …], …]
37
48
let facetsExclude ; // lazily-constructed opposite of facetsIndex
38
49
if ( facet !== undefined ) {
39
50
const { x, y} = facet ;
40
51
if ( x != null || y != null ) {
41
52
const facetData = arrayify ( facet . data ) ;
42
- facetChannels = [ ] ;
53
+ facetChannels = { } ;
43
54
if ( x != null ) {
44
55
const fx = Channel ( facetData , { value : x , scale : "fx" } ) ;
45
- facetChannels . push ( [ "fx" , fx ] ) ;
56
+ facetChannels . fx = fx ;
46
57
channelsByScale . set ( "fx" , [ fx ] ) ;
47
58
}
48
59
if ( y != null ) {
49
60
const fy = Channel ( facetData , { value : y , scale : "fy" } ) ;
50
- facetChannels . push ( [ "fy" , fy ] ) ;
61
+ facetChannels . fy = fy ;
51
62
channelsByScale . set ( "fy" , [ fy ] ) ;
52
63
}
53
64
facetIndex = range ( facetData ) ;
@@ -56,33 +67,20 @@ export function plot(options = {}) {
56
67
}
57
68
}
58
69
59
- // Initialize the marks’ channels, indexing them by mark and scale as needed .
70
+ // Initialize the marks’ state .
60
71
for ( const mark of marks ) {
61
72
if ( stateByMark . has ( mark ) ) throw new Error ( "duplicate mark" ) ;
62
- const markFacets = facets === undefined ? undefined
73
+ const markFacets = facetsIndex === undefined ? undefined
63
74
: mark . facet === "auto" ? mark . data === facet . data ? facetsIndex : undefined
64
75
: mark . facet === "include" ? facetsIndex
65
76
: mark . facet === "exclude" ? facetsExclude || ( facetsExclude = facetsIndex . map ( f => Uint32Array . from ( difference ( facetIndex , f ) ) ) )
66
77
: undefined ;
67
- const { index, channels} = mark . initialize ( markFacets , facetChannels ) ;
68
- for ( const [ , channel ] of channels ) {
69
- const { scale} = channel ;
70
- if ( scale !== undefined ) {
71
- const channels = channelsByScale . get ( scale ) ;
72
- if ( channels !== undefined ) channels . push ( channel ) ;
73
- else channelsByScale . set ( scale , [ channel ] ) ;
74
- }
75
- }
76
- stateByMark . set ( mark , { index, channels, faceted : markFacets !== undefined } ) ;
77
- }
78
-
79
- // Apply scale transforms, mutating channel.value.
80
- for ( const [ scale , channels ] of channelsByScale ) {
81
- const { percent, transform = percent ? x => x * 100 : undefined } = options [ scale ] || { } ;
82
- if ( transform != null ) for ( const c of channels ) c . value = Array . from ( c . value , transform ) ;
78
+ const { facets, channels} = mark . initialize ( markFacets , facetChannels ) ;
79
+ stateByMark . set ( mark , { facets, channels : applyScaleTransforms ( channels , options ) } ) ;
83
80
}
84
81
85
- const scaleDescriptors = Scales ( channelsByScale , options ) ;
82
+ // Initalize the scales and axes.
83
+ const scaleDescriptors = Scales ( addScaleChannels ( channelsByScale , stateByMark ) , options ) ;
86
84
const scales = ScaleFunctions ( scaleDescriptors ) ;
87
85
const axes = Axes ( scaleDescriptors , options ) ;
88
86
const dimensions = Dimensions ( scaleDescriptors , axes , options ) ;
@@ -91,9 +89,30 @@ export function plot(options = {}) {
91
89
autoScaleLabels ( channelsByScale , scaleDescriptors , axes , dimensions , options ) ;
92
90
autoAxisTicks ( scaleDescriptors , axes ) ;
93
91
92
+ // Reinitialize; for deriving channels dependent on other channels.
93
+ const newByScale = new Set ( ) ;
94
+ for ( const [ mark , state ] of stateByMark ) {
95
+ if ( mark . reinitialize != null ) {
96
+ const { facets, channels} = mark . reinitialize ( state . facets , state . channels , scales ) ;
97
+ if ( facets !== undefined ) state . facets = facets ;
98
+ if ( channels !== undefined ) {
99
+ Object . assign ( state . channels , applyScaleTransforms ( channels , options ) ) ;
100
+ for ( const name in channels ) newByScale . add ( channels [ name ] . scale ) ;
101
+ }
102
+ }
103
+ }
104
+
105
+ // Reconstruct scales if new scaled channels were created during reinitialization.
106
+ if ( newByScale . size ) {
107
+ const newScaleDescriptors = Scales ( addScaleChannels ( new Map ( ) , stateByMark , key => newByScale . has ( key ) ) , options ) ;
108
+ const newScales = ScaleFunctions ( newScaleDescriptors ) ;
109
+ Object . assign ( scaleDescriptors , newScaleDescriptors ) ;
110
+ Object . assign ( scales , newScales ) ;
111
+ }
112
+
94
113
// Compute value objects, applying scales as needed.
95
114
for ( const state of stateByMark . values ( ) ) {
96
- state . values = applyScales ( state . channels , scales ) ;
115
+ state . values = valueObject ( state . channels , scales ) ;
97
116
}
98
117
99
118
const { width, height} = dimensions ;
@@ -175,16 +194,16 @@ export function plot(options = {}) {
175
194
. attr ( "transform" , facetTranslate ( fx , fy ) )
176
195
. each ( function ( key ) {
177
196
const j = indexByFacet . get ( key ) ;
178
- for ( const [ mark , { channels, values, index , faceted } ] of stateByMark ) {
179
- const renderIndex = mark . filter ( faceted ? index [ j ] : index , channels , values ) ;
180
- const node = mark . render ( renderIndex , scales , values , subdimensions ) ;
197
+ for ( const [ mark , { channels, values, facets } ] of stateByMark ) {
198
+ const facet = facets ? mark . filter ( facets [ j ] ?? facets [ 0 ] , channels , values ) : null ;
199
+ const node = mark . render ( facet , scales , values , subdimensions ) ;
181
200
if ( node != null ) this . appendChild ( node ) ;
182
201
}
183
202
} ) ;
184
203
} else {
185
- for ( const [ mark , { channels, values, index } ] of stateByMark ) {
186
- const renderIndex = mark . filter ( index , channels , values ) ;
187
- const node = mark . render ( renderIndex , scales , values , dimensions ) ;
204
+ for ( const [ mark , { channels, values, facets } ] of stateByMark ) {
205
+ const facet = facets ? mark . filter ( facets [ 0 ] , channels , values ) : null ;
206
+ const node = mark . render ( facet , scales , values , dimensions ) ;
188
207
if ( node != null ) svg . appendChild ( node ) ;
189
208
}
190
209
}
@@ -227,6 +246,7 @@ export class Mark {
227
246
const { facet = "auto" , sort, dx, dy, clip} = options ;
228
247
const names = new Set ( ) ;
229
248
this . data = data ;
249
+ this . reinitialize = options . initialize ;
230
250
this . sort = isOptions ( sort ) ? sort : null ;
231
251
this . facet = facet == null || facet === false ? null : keyword ( facet === true ? "include" : facet , "facet" , [ "auto" , "include" , "exclude" ] ) ;
232
252
const { transform} = basic ( options ) ;
@@ -249,25 +269,18 @@ export class Mark {
249
269
this . dy = + dy || 0 ;
250
270
this . clip = maybeClip ( clip ) ;
251
271
}
252
- initialize ( facetIndex , facetChannels ) {
272
+ initialize ( facets , facetChannels ) {
253
273
let data = arrayify ( this . data ) ;
254
- let index = facetIndex === undefined && data != null ? range ( data ) : facetIndex ;
255
- if ( data !== undefined && this . transform !== undefined ) {
256
- if ( facetIndex === undefined ) index = index . length ? [ index ] : [ ] ;
257
- ( { facets : index , data} = this . transform ( data , index ) ) ;
258
- data = arrayify ( data ) ;
259
- if ( facetIndex === undefined && index . length ) ( [ index ] = index ) ;
260
- }
261
- const channels = this . channels . map ( channel => {
262
- const { name} = channel ;
263
- return [ name == null ? undefined : `${ name } ` , Channel ( data , channel ) ] ;
264
- } ) ;
274
+ if ( facets === undefined && data != null ) facets = [ range ( data ) ] ;
275
+ if ( this . transform != null ) ( { facets, data} = this . transform ( data , facets ) ) , data = arrayify ( data ) ;
276
+ const channels = channelObject ( this . channels , data ) ;
265
277
if ( this . sort != null ) channelSort ( channels , facetChannels , data , this . sort ) ;
266
- return { index , channels} ;
278
+ return { facets , channels} ;
267
279
}
268
280
filter ( index , channels , values ) {
269
- for ( const [ name , { filter = defined } ] of channels ) {
270
- if ( name !== undefined && filter !== null ) {
281
+ for ( const name in channels ) {
282
+ const { filter = defined } = channels [ name ] ;
283
+ if ( filter !== null ) {
271
284
const value = values [ name ] ;
272
285
index = index . filter ( i => filter ( value [ i ] ) ) ;
273
286
}
@@ -298,6 +311,34 @@ class Render extends Mark {
298
311
render ( ) { }
299
312
}
300
313
314
+ // Note: mutates channel.value to apply the scale transform, if any.
315
+ function applyScaleTransforms ( channels , options ) {
316
+ for ( const name in channels ) {
317
+ const channel = channels [ name ] ;
318
+ const { scale} = channel ;
319
+ if ( scale != null ) {
320
+ const { percent, transform = percent ? x => x * 100 : undefined } = options [ scale ] || { } ;
321
+ if ( transform != null ) channel . value = Array . from ( channel . value , transform ) ;
322
+ }
323
+ }
324
+ return channels ;
325
+ }
326
+
327
+ function addScaleChannels ( channelsByScale , stateByMark , filter = yes ) {
328
+ for ( const { channels} of stateByMark . values ( ) ) {
329
+ for ( const name in channels ) {
330
+ const channel = channels [ name ] ;
331
+ const { scale} = channel ;
332
+ if ( scale != null && filter ( scale ) ) {
333
+ const channels = channelsByScale . get ( scale ) ;
334
+ if ( channels !== undefined ) channels . push ( channel ) ;
335
+ else channelsByScale . set ( scale , [ channel ] ) ;
336
+ }
337
+ }
338
+ }
339
+ return channelsByScale ;
340
+ }
341
+
301
342
// Derives a copy of the specified axis with the label disabled.
302
343
function nolabel ( axis ) {
303
344
return axis === undefined || axis . label === undefined
@@ -316,15 +357,17 @@ function facetKeys({fx, fy}) {
316
357
// Returns an array of [[key1, index1], [key2, index2], …] representing the data
317
358
// indexes associated with each facet. For two-dimensional faceting, each key
318
359
// is a two-element array; see also facetMap.
319
- function facetGroups ( index , channels ) {
320
- return ( channels . length > 1 ? facetGroup2 : facetGroup1 ) ( index , ...channels ) ;
360
+ function facetGroups ( index , { fx, fy} ) {
361
+ return fx && fy ? facetGroup2 ( index , fx , fy )
362
+ : fx ? facetGroup1 ( index , fx )
363
+ : facetGroup1 ( index , fy ) ;
321
364
}
322
365
323
- function facetGroup1 ( index , [ , { value : F } ] ) {
366
+ function facetGroup1 ( index , { value : F } ) {
324
367
return groups ( index , i => F [ i ] ) ;
325
368
}
326
369
327
- function facetGroup2 ( index , [ , { value : FX } ] , [ , { value : FY } ] ) {
370
+ function facetGroup2 ( index , { value : FX } , { value : FY } ) {
328
371
return groups ( index , i => FX [ i ] , i => FY [ i ] )
329
372
. flatMap ( ( [ x , xgroup ] ) => xgroup
330
373
. map ( ( [ y , ygroup ] ) => [ [ x , y ] , ygroup ] ) ) ;
@@ -337,8 +380,8 @@ function facetTranslate(fx, fy) {
337
380
: ky => `translate(0,${ fy ( ky ) } )` ;
338
381
}
339
382
340
- function facetMap ( channels ) {
341
- return new ( channels . length > 1 ? FacetMap2 : FacetMap ) ;
383
+ function facetMap ( { fx , fy } ) {
384
+ return new ( fx && fy ? FacetMap2 : FacetMap ) ;
342
385
}
343
386
344
387
class FacetMap {
0 commit comments