1
1
import { InternMap , cumsum , greatest , group , groupSort , max , min , rollup , sum } from "d3" ;
2
- import { ascendingDefined } from "../defined.js" ;
2
+ import { ascendingDefined , descendingDefined } from "../defined.js" ;
3
3
import { withTip } from "../mark.js" ;
4
4
import { maybeApplyInterval , maybeColumn , maybeZ , maybeZero } from "../options.js" ;
5
5
import { column , field , mid , one , range , valueof } from "../options.js" ;
@@ -81,20 +81,20 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) {
81
81
const [ Y2 , setY2 ] = column ( y ) ;
82
82
Y1 . hint = Y2 . hint = lengthy ;
83
83
offset = maybeOffset ( offset ) ;
84
- order = maybeOrder ( order , offset , ky ) ; // TODO shorthand -order with reverse?
84
+ order = maybeOrder ( order , offset , ky ) ;
85
85
return [
86
86
basic ( options , ( data , facets , plotOptions ) => {
87
87
const X = x == null ? undefined : setX ( maybeApplyInterval ( valueof ( data , x ) , plotOptions ?. [ kx ] ) ) ;
88
88
const Y = valueof ( data , y , Float64Array ) ;
89
89
const Z = valueof ( data , z ) ;
90
- const O = order && order ( data , X , Y , Z ) ;
90
+ const compare = order && order ( data , X , Y , Z ) ;
91
91
const n = data . length ;
92
92
const Y1 = setY1 ( new Float64Array ( n ) ) ;
93
93
const Y2 = setY2 ( new Float64Array ( n ) ) ;
94
94
const facetstacks = [ ] ;
95
95
for ( const facet of facets ) {
96
96
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 ) ;
98
98
for ( const stack of stacks ) {
99
99
let yn = 0 ;
100
100
let yp = 0 ;
@@ -228,43 +228,44 @@ function offsetCenterFacets(facetstacks, Y1, Y2) {
228
228
}
229
229
230
230
function maybeOrder ( order , offset , ky ) {
231
- if ( order === undefined && offset === offsetWiggle ) return orderInsideOut ;
231
+ if ( order === undefined && offset === offsetWiggle ) return orderInsideOut ( ascendingDefined ) ;
232
232
if ( order == null ) return ;
233
233
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 ( ) ) {
235
237
case "value" :
236
238
case ky :
237
- return orderY ;
239
+ return orderY ( compare ) ;
238
240
case "z" :
239
- return orderZ ;
241
+ return orderZ ( compare ) ;
240
242
case "sum" :
241
- return orderSum ;
243
+ return orderSum ( compare ) ;
242
244
case "appearance" :
243
- return orderAppearance ;
245
+ return orderAppearance ( compare ) ;
244
246
case "inside-out" :
245
- return orderInsideOut ;
247
+ return orderInsideOut ( compare ) ;
246
248
}
247
- return orderFunction ( field ( order ) ) ;
249
+ return orderAccessor ( field ( order ) ) ;
248
250
}
249
- if ( typeof order === "function" ) return orderFunction ( order ) ;
251
+ if ( typeof order === "function" ) return ( order . length === 1 ? orderAccessor : orderComparator ) ( order ) ;
250
252
if ( Array . isArray ( order ) ) return orderGiven ( order ) ;
251
253
throw new Error ( `invalid order: ${ order } ` ) ;
252
254
}
253
255
254
256
// 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 ] ) ;
257
259
}
258
260
259
261
// 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 ] ) ;
262
264
}
263
265
264
266
// 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 ) =>
268
269
groupSort (
269
270
range ( data ) ,
270
271
( I ) => sum ( I , ( i ) => Y [ i ] ) ,
@@ -274,9 +275,8 @@ function orderSum(data, X, Y, Z) {
274
275
}
275
276
276
277
// 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 ) =>
280
280
groupSort (
281
281
range ( data ) ,
282
282
( I ) => X [ greatest ( I , ( i ) => Y [ i ] ) ] ,
@@ -287,52 +287,57 @@ function orderAppearance(data, X, Y, Z) {
287
287
288
288
// by x = argmax of value, but rearranged inside-out by alternating series
289
289
// 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
+ }
312
314
}
313
- }
314
- return orderZDomain ( Z , Kn . reverse ( ) . concat ( Kp ) ) ;
315
+ return Kn . reverse ( ) . concat ( Kp ) ;
316
+ } ) ;
315
317
}
316
318
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
+ } ;
319
324
}
320
325
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 ] ) ;
323
328
}
324
329
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 ) ;
332
332
}
333
333
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
+ } ;
338
343
}
0 commit comments