1+ import { ascending , descending , rollup , sort } from "d3" ;
12import { color } from "d3" ;
23import { nonempty } from "./defined.js" ;
34import { plot } from "./plot.js" ;
5+ import { registry } from "./scales/index.js" ;
46import { styles } from "./style.js" ;
57import { basic } from "./transforms/basic.js" ;
8+ import { maybeReduce } from "./transforms/group.js" ;
69
710// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
811const TypedArray = Object . getPrototypeOf ( Uint8Array ) ;
912const objectToString = Object . prototype . toString ;
1013
1114export class Mark {
1215 constructor ( data , channels = [ ] , options = { } , defaults ) {
13- const { facet = "auto" , dx, dy} = options ;
16+ const { facet = "auto" , sort , dx, dy} = options ;
1417 const names = new Set ( ) ;
1518 this . data = data ;
16- this . facet = facet ? keyword ( facet === true ? "include" : facet , "facet" , [ "auto" , "include" , "exclude" ] ) : null ;
19+ this . sort = isOptions ( sort ) ? sort : null ;
20+ this . facet = facet == null || facet === false ? null : keyword ( facet === true ? "include" : facet , "facet" , [ "auto" , "include" , "exclude" ] ) ;
1721 const { transform} = basic ( options ) ;
1822 this . transform = transform ;
1923 if ( defaults !== undefined ) channels = styles ( this , options , channels , defaults ) ;
@@ -34,7 +38,7 @@ export class Mark {
3438 this . dx = + dx || 0 ;
3539 this . dy = + dy || 0 ;
3640 }
37- initialize ( facets ) {
41+ initialize ( facets , facetChannels ) {
3842 let data = arrayify ( this . data ) ;
3943 let index = facets === undefined && data != null ? range ( data ) : facets ;
4044 if ( data !== undefined && this . transform !== undefined ) {
@@ -43,13 +47,12 @@ export class Mark {
4347 data = arrayify ( data ) ;
4448 if ( facets === undefined && index . length ) ( [ index ] = index ) ;
4549 }
46- return {
47- index,
48- channels : this . channels . map ( channel => {
49- const { name} = channel ;
50- return [ name == null ? undefined : name + "" , Channel ( data , channel ) ] ;
51- } )
52- } ;
50+ const channels = this . channels . map ( channel => {
51+ const { name} = channel ;
52+ return [ name == null ? undefined : name + "" , Channel ( data , channel ) ] ;
53+ } ) ;
54+ if ( this . sort != null ) channelSort ( channels , facetChannels , data , this . sort ) ;
55+ return { index, channels} ;
5356 }
5457 plot ( { marks = [ ] , ...options } = { } ) {
5558 return plot ( { ...options , marks : [ ...marks , this ] } ) ;
@@ -66,6 +69,43 @@ function Channel(data, {scale, type, value}) {
6669 } ;
6770}
6871
72+ function channelSort ( channels , facetChannels , data , options ) {
73+ const { reverse : defaultReverse , reduce : defaultReduce = true , limit : defaultLimit } = options ;
74+ for ( const x in options ) {
75+ if ( ! registry . has ( x ) ) continue ; // ignore unknown scale keys
76+ const { value : y , reverse = defaultReverse , reduce = defaultReduce , limit = defaultLimit } = maybeValue ( options [ x ] ) ;
77+ if ( reduce == null || reduce === false ) continue ; // disabled reducer
78+ const X = channels . find ( ( [ , { scale} ] ) => scale === x ) || facetChannels && facetChannels . find ( ( [ , { scale} ] ) => scale === x ) ;
79+ if ( ! X ) throw new Error ( `missing channel for scale: ${ x } ` ) ;
80+ const XV = X [ 1 ] . value ;
81+ const [ lo = 0 , hi = Infinity ] = limit && typeof limit [ Symbol . iterator ] === "function" ? limit : limit < 0 ? [ limit ] : [ 0 , limit ] ;
82+ if ( y == null ) {
83+ X [ 1 ] . domain = ( ) => {
84+ let domain = XV ;
85+ if ( reverse ) domain = domain . slice ( ) . reverse ( ) ;
86+ if ( lo !== 0 || hi !== Infinity ) domain = domain . slice ( lo , hi ) ;
87+ return domain ;
88+ } ;
89+ } else {
90+ let YV ;
91+ if ( y === "data" ) {
92+ YV = data ;
93+ } else {
94+ const Y = channels . find ( ( [ name ] ) => name === y ) ;
95+ if ( ! Y ) throw new Error ( `missing channel: ${ y } ` ) ;
96+ YV = Y [ 1 ] . value ;
97+ }
98+ const reducer = maybeReduce ( reduce === true ? "max" : reduce , YV ) ;
99+ X [ 1 ] . domain = ( ) => {
100+ let domain = rollup ( range ( XV ) , I => reducer . reduce ( I , YV ) , i => XV [ i ] ) ;
101+ domain = sort ( domain , reverse ? descendingGroup : ascendingGroup ) ;
102+ if ( lo !== 0 || hi !== Infinity ) domain = domain . slice ( lo , hi ) ;
103+ return domain . map ( first ) ;
104+ } ;
105+ }
106+ }
107+ }
108+
69109// This allows transforms to behave equivalently to channels.
70110export function valueof ( data , value , type ) {
71111 const array = type === undefined ? Array : type ;
@@ -143,6 +183,14 @@ export function arrayify(data, type) {
143183 : ( data instanceof type ? data : type . from ( data ) ) ) ;
144184}
145185
186+ // Disambiguates an options object (e.g., {y: "x2"}) from a channel value
187+ // definition expressed as a channel transform (e.g., {transform: …}).
188+ export function isOptions ( option ) {
189+ return option
190+ && option . toString === objectToString
191+ && typeof option . transform !== "function" ;
192+ }
193+
146194// For marks specified either as [0, x] or [x1, x2], such as areas and bars.
147195export function maybeZero ( x , x1 , x2 , x3 = identity ) {
148196 if ( x1 === undefined && x2 === undefined ) { // {x} or {}
@@ -189,6 +237,11 @@ export function range(data) {
189237 return Uint32Array . from ( data , indexOf ) ;
190238}
191239
240+ // Returns a filtered range of data given the test function.
241+ export function where ( data , test ) {
242+ return range ( data ) . filter ( i => test ( data [ i ] , i , data ) ) ;
243+ }
244+
192245// Returns an array [values[index[0]], values[index[1]], …].
193246export function take ( values , index ) {
194247 return Array . from ( index , i => values [ i ] ) ;
@@ -247,9 +300,7 @@ export function mid(x1, x2) {
247300
248301// This distinguishes between per-dimension options and a standalone value.
249302export function maybeValue ( value ) {
250- return value === undefined || ( value &&
251- value . toString === objectToString &&
252- typeof value . transform !== "function" ) ? value : { value} ;
303+ return value === undefined || isOptions ( value ) ? value : { value} ;
253304}
254305
255306export function numberChannel ( source ) {
@@ -292,3 +343,11 @@ export function marks(...marks) {
292343 marks . plot = Mark . prototype . plot ;
293344 return marks ;
294345}
346+
347+ function ascendingGroup ( [ ak , av ] , [ bk , bv ] ) {
348+ return ascending ( av , bv ) || ascending ( ak , bk ) ;
349+ }
350+
351+ function descendingGroup ( [ ak , av ] , [ bk , bv ] ) {
352+ return descending ( av , bv ) || ascending ( ak , bk ) ;
353+ }
0 commit comments