1
- import { InternMap , InternSet , select , sort , sum } from "d3" ;
1
+ import { InternMap , InternSet , cross , select , sort , sum } from "d3" ;
2
2
import { Axes , autoAxisTicks , autoScaleLabels } from "./axes.js" ;
3
3
import { Channel , Channels , channelDomain , valueObject } from "./channel.js" ;
4
4
import { Context , create } from "./context.js" ;
@@ -440,28 +440,35 @@ export function plot(options = {}) {
440
440
// Create the facet scales and array of subplots
441
441
const facetScales = Scales ( channelsByScale ) ;
442
442
443
- let facets ;
444
- if ( facetScales . fx ) facets = ( facets || [ { } ] ) . flatMap ( ( d ) => facetScales . fx . domain . map ( ( x ) => ( { ...d , x} ) ) ) ;
445
- if ( facetScales . fy ) facets = ( facets || [ { } ] ) . flatMap ( ( d ) => facetScales . fy . domain . map ( ( y ) => ( { ...d , y} ) ) ) ;
446
- if ( facets ) facets . forEach ( ( d , j ) => ( d . j = j ) ) ;
443
+ // TODO Avoid j here? TODO Document this data structure. Note that empty is
444
+ // mutated below after we’ve computed the per-mark facet channels.
445
+ let facets =
446
+ facetScales . fx && facetScales . fy
447
+ ? cross ( facetScales . fx . domain , facetScales . fy . domain ) . map ( ( [ x , y ] , j ) => ( { x, y, j, empty : true } ) )
448
+ : facetScales . fx
449
+ ? facetScales . fx . domain . map ( ( x , j ) => ( { x, j, empty : true } ) )
450
+ : facetScales . fy
451
+ ? facetScales . fy . domain . map ( ( y , j ) => ( { y, j, empty : true } ) )
452
+ : null ;
447
453
const facetLength = facets && facets . length ;
448
454
449
455
// Compute the top-level facet index
450
456
if ( facetDataIndex ) {
451
457
const { fx, fy} = facetChannels ;
452
458
facetsIndex = [ ] ;
453
- for ( const { x, y} of facets )
459
+ for ( const { x, y} of facets ) {
454
460
facetsIndex . push (
455
461
facetDataIndex . filter ( ( i ) => ( ! fx || facetKeyEquals ( fx . value [ i ] , x ) ) && ( ! fy || facetKeyEquals ( fy . value [ i ] , y ) ) )
456
462
) ;
463
+ }
457
464
}
458
465
459
466
// Compute a facet index for each mark
460
467
for ( const mark of marks ) {
461
468
if ( mark . facet === null ) continue ;
462
469
const state = stateByMark . get ( mark ) ;
463
-
464
470
const { x, y, method} = mark . facet ;
471
+
465
472
// Mark-level facet ? Compute an index for that mark’s data and options
466
473
if ( x !== undefined || y !== undefined ) {
467
474
if ( facets ) state . facetsIndex = filterFacets ( facets , state , facetChannels ) ;
@@ -474,12 +481,30 @@ export function plot(options = {}) {
474
481
state . facetsIndex = facetsIndex ;
475
482
}
476
483
477
- // A facet is empty if none of the faceted index has contents for any mark
484
+ // When faceting by both x and y (i.e., when both fx and fy scales are
485
+ // present), then the cross product of the domains of fx and fy can include
486
+ // fx-fy combinations for which no mark has an instance associated with that
487
+ // combination of fx and fy, and therefore we don’t want to render this facet
488
+ // (not even the frame). The same can occur if you specify the domain of fx
489
+ // and fy explicitly, but there is no mark instance associated with some
490
+ // values in the domain.
491
+ //
492
+ // TODO We need to do two (or three?) passes here. First, we need determine
493
+ // the domains of the fx and fy scales (as needed) based on the union of
494
+ // distinct channel values, including both mark-level facets and top-level
495
+ // facets. Then we need to check whether we have any mark instances (or
496
+ // top-level facet data “instances”) associated with each value, or
497
+ // cross-product of values, in the fx and fy scale domains. This probably
498
+ // means having an InternSet of fx?+fx? keys recording which facets are
499
+ // non-empty, similar to the FacetMap data structure we had before.
500
+
501
+ // A facet is empty if none of the faceted index has contents for any mark.
502
+ // TODO Can we do this more declaratively rather than re-assigning facets?
478
503
facets =
479
504
facets &&
480
505
facets . filter ( ( _ , j ) => {
481
506
let nonFaceted = true ;
482
- for ( const [ , { facetsIndex} ] of stateByMark ) {
507
+ for ( const { facetsIndex} of stateByMark . values ( ) ) {
483
508
if ( facetsIndex ) {
484
509
nonFaceted = false ;
485
510
if ( facetsIndex ?. [ j ] . length ) return true ;
0 commit comments