11import { create , cross , difference , groups , InternMap , select } from "d3" ;
22import { Axes , autoAxisTicks , autoScaleLabels } from "./axes.js" ;
3- import { Channel , channelSort } from "./channel.js" ;
3+ import { Channel , channelObject , channelSort , valueObject } from "./channel.js" ;
44import { defined } from "./defined.js" ;
55import { Dimensions } from "./dimensions.js" ;
66import { 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" ;
910import { applyInlineStyles , maybeClassName , maybeClip , styles } from "./style.js" ;
1011import { basic } from "./transforms/basic.js" ;
1112import { consumeWarnings } from "./warnings.js" ;
@@ -29,25 +30,35 @@ export function plot(options = {}) {
2930 // A Map from scale name to an array of associated channels.
3031 const channelsByScale = new Map ( ) ;
3132
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+
3243 // Faceting!
3344 let facets ; // array of facet definitions (e.g. [["foo", [0, 1, 3, …]], …])
3445 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}}
3647 let facetsIndex ; // nested array of facet indexes [[0, 1, 3, …], [2, 5, …], …]
3748 let facetsExclude ; // lazily-constructed opposite of facetsIndex
3849 if ( facet !== undefined ) {
3950 const { x, y} = facet ;
4051 if ( x != null || y != null ) {
4152 const facetData = arrayify ( facet . data ) ;
42- facetChannels = [ ] ;
53+ facetChannels = { } ;
4354 if ( x != null ) {
4455 const fx = Channel ( facetData , { value : x , scale : "fx" } ) ;
45- facetChannels . push ( [ "fx" , fx ] ) ;
56+ facetChannels . fx = fx ;
4657 channelsByScale . set ( "fx" , [ fx ] ) ;
4758 }
4859 if ( y != null ) {
4960 const fy = Channel ( facetData , { value : y , scale : "fy" } ) ;
50- facetChannels . push ( [ "fy" , fy ] ) ;
61+ facetChannels . fy = fy ;
5162 channelsByScale . set ( "fy" , [ fy ] ) ;
5263 }
5364 facetIndex = range ( facetData ) ;
@@ -56,33 +67,20 @@ export function plot(options = {}) {
5667 }
5768 }
5869
59- // Initialize the marks’ channels, indexing them by mark and scale as needed .
70+ // Initialize the marks’ state .
6071 for ( const mark of marks ) {
6172 if ( stateByMark . has ( mark ) ) throw new Error ( "duplicate mark" ) ;
62- const markFacets = facets === undefined ? undefined
73+ const markFacets = facetsIndex === undefined ? undefined
6374 : mark . facet === "auto" ? mark . data === facet . data ? facetsIndex : undefined
6475 : mark . facet === "include" ? facetsIndex
6576 : mark . facet === "exclude" ? facetsExclude || ( facetsExclude = facetsIndex . map ( f => Uint32Array . from ( difference ( facetIndex , f ) ) ) )
6677 : 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 ) } ) ;
8380 }
8481
85- const scaleDescriptors = Scales ( channelsByScale , options ) ;
82+ // Initalize the scales and axes.
83+ const scaleDescriptors = Scales ( addScaleChannels ( channelsByScale , stateByMark ) , options ) ;
8684 const scales = ScaleFunctions ( scaleDescriptors ) ;
8785 const axes = Axes ( scaleDescriptors , options ) ;
8886 const dimensions = Dimensions ( scaleDescriptors , axes , options ) ;
@@ -91,9 +89,30 @@ export function plot(options = {}) {
9189 autoScaleLabels ( channelsByScale , scaleDescriptors , axes , dimensions , options ) ;
9290 autoAxisTicks ( scaleDescriptors , axes ) ;
9391
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+
94113 // Compute value objects, applying scales as needed.
95114 for ( const state of stateByMark . values ( ) ) {
96- state . values = applyScales ( state . channels , scales ) ;
115+ state . values = valueObject ( state . channels , scales ) ;
97116 }
98117
99118 const { width, height} = dimensions ;
@@ -175,16 +194,16 @@ export function plot(options = {}) {
175194 . attr ( "transform" , facetTranslate ( fx , fy ) )
176195 . each ( function ( key ) {
177196 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 ) ;
181200 if ( node != null ) this . appendChild ( node ) ;
182201 }
183202 } ) ;
184203 } 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 ) ;
188207 if ( node != null ) svg . appendChild ( node ) ;
189208 }
190209 }
@@ -227,6 +246,7 @@ export class Mark {
227246 const { facet = "auto" , sort, dx, dy, clip} = options ;
228247 const names = new Set ( ) ;
229248 this . data = data ;
249+ this . reinitialize = options . initialize ;
230250 this . sort = isOptions ( sort ) ? sort : null ;
231251 this . facet = facet == null || facet === false ? null : keyword ( facet === true ? "include" : facet , "facet" , [ "auto" , "include" , "exclude" ] ) ;
232252 const { transform} = basic ( options ) ;
@@ -249,25 +269,18 @@ export class Mark {
249269 this . dy = + dy || 0 ;
250270 this . clip = maybeClip ( clip ) ;
251271 }
252- initialize ( facetIndex , facetChannels ) {
272+ initialize ( facets , facetChannels ) {
253273 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 ) ;
265277 if ( this . sort != null ) channelSort ( channels , facetChannels , data , this . sort ) ;
266- return { index , channels} ;
278+ return { facets , channels} ;
267279 }
268280 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 ) {
271284 const value = values [ name ] ;
272285 index = index . filter ( i => filter ( value [ i ] ) ) ;
273286 }
@@ -298,6 +311,34 @@ class Render extends Mark {
298311 render ( ) { }
299312}
300313
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+
301342// Derives a copy of the specified axis with the label disabled.
302343function nolabel ( axis ) {
303344 return axis === undefined || axis . label === undefined
@@ -316,15 +357,17 @@ function facetKeys({fx, fy}) {
316357// Returns an array of [[key1, index1], [key2, index2], …] representing the data
317358// indexes associated with each facet. For two-dimensional faceting, each key
318359// 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 ) ;
321364}
322365
323- function facetGroup1 ( index , [ , { value : F } ] ) {
366+ function facetGroup1 ( index , { value : F } ) {
324367 return groups ( index , i => F [ i ] ) ;
325368}
326369
327- function facetGroup2 ( index , [ , { value : FX } ] , [ , { value : FY } ] ) {
370+ function facetGroup2 ( index , { value : FX } , { value : FY } ) {
328371 return groups ( index , i => FX [ i ] , i => FY [ i ] )
329372 . flatMap ( ( [ x , xgroup ] ) => xgroup
330373 . map ( ( [ y , ygroup ] ) => [ [ x , y ] , ygroup ] ) ) ;
@@ -337,8 +380,8 @@ function facetTranslate(fx, fy) {
337380 : ky => `translate(0,${ fy ( ky ) } )` ;
338381}
339382
340- function facetMap ( channels ) {
341- return new ( channels . length > 1 ? FacetMap2 : FacetMap ) ;
383+ function facetMap ( { fx , fy } ) {
384+ return new ( fx && fy ? FacetMap2 : FacetMap ) ;
342385}
343386
344387class FacetMap {
0 commit comments