11import { InternMap , cumsum , greatest , group , groupSort , max , min , rollup , sum } from "d3" ;
2- import { ascendingDefined } from "../defined.js" ;
2+ import { ascendingDefined , descendingDefined } from "../defined.js" ;
33import { withTip } from "../mark.js" ;
44import { maybeApplyInterval , maybeColumn , maybeZ , maybeZero } from "../options.js" ;
55import { column , field , mid , one , range , valueof } from "../options.js" ;
@@ -81,20 +81,20 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) {
8181 const [ Y2 , setY2 ] = column ( y ) ;
8282 Y1 . hint = Y2 . hint = lengthy ;
8383 offset = maybeOffset ( offset ) ;
84- order = maybeOrder ( order , offset , ky ) ; // TODO shorthand -order with reverse?
84+ order = maybeOrder ( order , offset , ky ) ;
8585 return [
8686 basic ( options , ( data , facets , plotOptions ) => {
8787 const X = x == null ? undefined : setX ( maybeApplyInterval ( valueof ( data , x ) , plotOptions ?. [ kx ] ) ) ;
8888 const Y = valueof ( data , y , Float64Array ) ;
8989 const Z = valueof ( data , z ) ;
90- const O = order && order ( data , X , Y , Z ) ;
90+ const compare = order && order ( data , X , Y , Z ) ;
9191 const n = data . length ;
9292 const Y1 = setY1 ( new Float64Array ( n ) ) ;
9393 const Y2 = setY2 ( new Float64Array ( n ) ) ;
9494 const facetstacks = [ ] ;
9595 for ( const facet of facets ) {
9696 const stacks = X ? Array . from ( group ( facet , ( i ) => X [ i ] ) . values ( ) ) : [ facet ] ;
97- if ( O ) applyOrder ( stacks , O ) ;
97+ if ( compare ) for ( const stack of stacks ) stack . sort ( compare ) ;
9898 for ( const stack of stacks ) {
9999 let yn = 0 ;
100100 let yp = 0 ;
@@ -228,43 +228,44 @@ function offsetCenterFacets(facetstacks, Y1, Y2) {
228228}
229229
230230function maybeOrder ( order , offset , ky ) {
231- if ( order === undefined && offset === offsetWiggle ) return orderInsideOut ;
231+ if ( order === undefined && offset === offsetWiggle ) return orderInsideOut ( ascendingDefined ) ;
232232 if ( order == null ) return ;
233233 if ( typeof order === "string" ) {
234- switch ( order . toLowerCase ( ) ) {
234+ const negate = order . startsWith ( "-" ) ;
235+ const compare = negate ? descendingDefined : ascendingDefined ;
236+ switch ( ( negate ? order . slice ( 1 ) : order ) . toLowerCase ( ) ) {
235237 case "value" :
236238 case ky :
237- return orderY ;
239+ return orderY ( compare ) ;
238240 case "z" :
239- return orderZ ;
241+ return orderZ ( compare ) ;
240242 case "sum" :
241- return orderSum ;
243+ return orderSum ( compare ) ;
242244 case "appearance" :
243- return orderAppearance ;
245+ return orderAppearance ( compare ) ;
244246 case "inside-out" :
245- return orderInsideOut ;
247+ return orderInsideOut ( compare ) ;
246248 }
247- return orderFunction ( field ( order ) ) ;
249+ return orderAccessor ( field ( order ) ) ;
248250 }
249- if ( typeof order === "function" ) return orderFunction ( order ) ;
251+ if ( typeof order === "function" ) return ( order . length === 1 ? orderAccessor : orderComparator ) ( order ) ;
250252 if ( Array . isArray ( order ) ) return orderGiven ( order ) ;
251253 throw new Error ( `invalid order: ${ order } ` ) ;
252254}
253255
254256// by value
255- function orderY ( data , X , Y ) {
256- return Y ;
257+ function orderY ( compare ) {
258+ return ( data , X , Y ) => ( i , j ) => compare ( Y [ i ] , Y [ j ] ) ;
257259}
258260
259261// by location
260- function orderZ ( order , X , Y , Z ) {
261- return Z ;
262+ function orderZ ( compare ) {
263+ return ( data , X , Y , Z ) => ( i , j ) => compare ( Z [ i ] , Z [ j ] ) ;
262264}
263265
264266// by sum of value (a.k.a. “ascending”)
265- function orderSum ( data , X , Y , Z ) {
266- return orderZDomain (
267- Z ,
267+ function orderSum ( compare ) {
268+ return orderZDomain ( compare , ( data , X , Y , Z ) =>
268269 groupSort (
269270 range ( data ) ,
270271 ( I ) => sum ( I , ( i ) => Y [ i ] ) ,
@@ -274,9 +275,8 @@ function orderSum(data, X, Y, Z) {
274275}
275276
276277// by x = argmax of value
277- function orderAppearance ( data , X , Y , Z ) {
278- return orderZDomain (
279- Z ,
278+ function orderAppearance ( compare ) {
279+ return orderZDomain ( compare , ( data , X , Y , Z ) =>
280280 groupSort (
281281 range ( data ) ,
282282 ( I ) => X [ greatest ( I , ( i ) => Y [ i ] ) ] ,
@@ -287,52 +287,57 @@ function orderAppearance(data, X, Y, Z) {
287287
288288// by x = argmax of value, but rearranged inside-out by alternating series
289289// according to the sign of a running divergence of sums
290- function orderInsideOut ( data , X , Y , Z ) {
291- const I = range ( data ) ;
292- const K = groupSort (
293- I ,
294- ( I ) => X [ greatest ( I , ( i ) => Y [ i ] ) ] ,
295- ( i ) => Z [ i ]
296- ) ;
297- const sums = rollup (
298- I ,
299- ( I ) => sum ( I , ( i ) => Y [ i ] ) ,
300- ( i ) => Z [ i ]
301- ) ;
302- const Kp = [ ] ,
303- Kn = [ ] ;
304- let s = 0 ;
305- for ( const k of K ) {
306- if ( s < 0 ) {
307- s += sums . get ( k ) ;
308- Kp . push ( k ) ;
309- } else {
310- s -= sums . get ( k ) ;
311- Kn . push ( k ) ;
290+ function orderInsideOut ( compare ) {
291+ return orderZDomain ( compare , ( data , X , Y , Z ) => {
292+ const I = range ( data ) ;
293+ const K = groupSort (
294+ I ,
295+ ( I ) => X [ greatest ( I , ( i ) => Y [ i ] ) ] ,
296+ ( i ) => Z [ i ]
297+ ) ;
298+ const sums = rollup (
299+ I ,
300+ ( I ) => sum ( I , ( i ) => Y [ i ] ) ,
301+ ( i ) => Z [ i ]
302+ ) ;
303+ const Kp = [ ] ,
304+ Kn = [ ] ;
305+ let s = 0 ;
306+ for ( const k of K ) {
307+ if ( s < 0 ) {
308+ s += sums . get ( k ) ;
309+ Kp . push ( k ) ;
310+ } else {
311+ s -= sums . get ( k ) ;
312+ Kn . push ( k ) ;
313+ }
312314 }
313- }
314- return orderZDomain ( Z , Kn . reverse ( ) . concat ( Kp ) ) ;
315+ return Kn . reverse ( ) . concat ( Kp ) ;
316+ } ) ;
315317}
316318
317- function orderFunction ( f ) {
318- return ( data ) => valueof ( data , f ) ;
319+ function orderAccessor ( f ) {
320+ return ( data ) => {
321+ const O = valueof ( data , f ) ;
322+ return ( i , j ) => ascendingDefined ( O [ i ] , O [ j ] ) ;
323+ } ;
319324}
320325
321- function orderGiven ( domain ) {
322- return ( data , X , Y , Z ) => orderZDomain ( Z , domain ) ;
326+ function orderComparator ( f ) {
327+ return ( data ) => ( i , j ) => f ( data [ i ] , data [ j ] ) ;
323328}
324329
325- // Given an explicit ordering of distinct values in z, returns a parallel column
326- // O that can be used with applyOrder to sort stacks. Note that this is a series
327- // order: it will be consistent across stacks.
328- function orderZDomain ( Z , domain ) {
329- if ( ! Z ) throw new Error ( "missing channel: z" ) ;
330- domain = new InternMap ( domain . map ( ( d , i ) => [ d , i ] ) ) ;
331- return Z . map ( ( z ) => domain . get ( z ) ) ;
330+ function orderGiven ( domain ) {
331+ return orderZDomain ( ascendingDefined , ( ) => domain ) ;
332332}
333333
334- function applyOrder ( stacks , O ) {
335- for ( const stack of stacks ) {
336- stack . sort ( ( i , j ) => ascendingDefined ( O [ i ] , O [ j ] ) ) ;
337- }
334+ // Given an ordering (domain) of distinct values in z that can be derived from
335+ // the data, returns a comparator that can be used to sort stacks. Note that
336+ // this is a series order: it will be consistent across stacks.
337+ function orderZDomain ( compare , domain ) {
338+ return ( data , X , Y , Z ) => {
339+ if ( ! Z ) throw new Error ( "missing channel: z" ) ;
340+ const map = new InternMap ( domain ( data , X , Y , Z ) . map ( ( d , i ) => [ d , i ] ) ) ;
341+ return ( i , j ) => compare ( map . get ( Z [ i ] ) , map . get ( Z [ j ] ) ) ;
342+ } ;
338343}
0 commit comments