1
- import { bin as binner , extent , thresholdFreedmanDiaconis , thresholdScott , thresholdSturges , utcTickInterval } from "d3" ;
1
+ import {
2
+ bisect ,
3
+ extent ,
4
+ thresholdFreedmanDiaconis ,
5
+ thresholdScott ,
6
+ thresholdSturges ,
7
+ ticks ,
8
+ tickIncrement ,
9
+ utcTickInterval
10
+ } from "d3" ;
2
11
import {
3
12
valueof ,
4
- range ,
5
13
identity ,
6
14
maybeColumn ,
7
15
maybeInterval ,
@@ -11,7 +19,8 @@ import {
11
19
mid ,
12
20
labelof ,
13
21
isTemporal ,
14
- isIterable
22
+ isIterable ,
23
+ map
15
24
} from "../options.js" ;
16
25
import { coerceDate , coerceNumber } from "../scales.js" ;
17
26
import { basic } from "./basic.js" ;
@@ -74,7 +83,7 @@ function binn(
74
83
gx , // optionally group on x (exclusive with bx and gy)
75
84
gy , // optionally group on y (exclusive with by and gx)
76
85
{
77
- data : reduceData = reduceIdentity ,
86
+ data : reduceData = reduceIdentity , // TODO avoid materializing when unused?
78
87
filter = reduceCount , // return only non-empty bins by default
79
88
sort,
80
89
reverse,
@@ -147,12 +156,11 @@ function binn(
147
156
const GZ = Z && setGZ ( [ ] ) ;
148
157
const GF = F && setGF ( [ ] ) ;
149
158
const GS = S && setGS ( [ ] ) ;
150
- const BX = bx ? bx ( data ) : [ [ , , ( I ) => I ] ] ;
151
- const BY = by ? by ( data ) : [ [ , , ( I ) => I ] ] ;
152
159
const BX1 = bx && setBX1 ( [ ] ) ;
153
160
const BX2 = bx && setBX2 ( [ ] ) ;
154
161
const BY1 = by && setBY1 ( [ ] ) ;
155
162
const BY2 = by && setBY2 ( [ ] ) ;
163
+ const bin = Bin ( bx ?. ( data ) , by ?. ( data ) ) ;
156
164
let i = 0 ;
157
165
for ( const o of outputs ) o . initialize ( data ) ;
158
166
if ( sort ) sort . initialize ( data ) ;
@@ -164,23 +172,18 @@ function binn(
164
172
if ( filter ) filter . scope ( "facet" , facet ) ;
165
173
for ( const [ f , I ] of maybeGroup ( facet , G ) ) {
166
174
for ( const [ k , g ] of maybeGroup ( I , K ) ) {
167
- for ( const [ x1 , x2 , fx ] of BX ) {
168
- const bb = fx ( g ) ;
169
- for ( const [ y1 , y2 , fy ] of BY ) {
170
- const extent = { x1, x2, y1, y2} ;
171
- const b = fy ( bb ) ;
172
- if ( filter && ! filter . reduce ( b , extent ) ) continue ;
173
- groupFacet . push ( i ++ ) ;
174
- groupData . push ( reduceData . reduce ( b , data , extent ) ) ;
175
- if ( K ) GK . push ( k ) ;
176
- if ( Z ) GZ . push ( G === Z ? f : Z [ b [ 0 ] ] ) ;
177
- if ( F ) GF . push ( G === F ? f : F [ b [ 0 ] ] ) ;
178
- if ( S ) GS . push ( G === S ? f : S [ b [ 0 ] ] ) ;
179
- if ( BX1 ) BX1 . push ( x1 ) , BX2 . push ( x2 ) ;
180
- if ( BY1 ) BY1 . push ( y1 ) , BY2 . push ( y2 ) ;
181
- for ( const o of outputs ) o . reduce ( b , extent ) ;
182
- if ( sort ) sort . reduce ( b ) ;
183
- }
175
+ for ( const [ b , extent ] of bin ( g ) ) {
176
+ if ( filter && ! filter . reduce ( b , extent ) ) continue ;
177
+ groupFacet . push ( i ++ ) ;
178
+ groupData . push ( reduceData . reduce ( b , data , extent ) ) ;
179
+ if ( K ) GK . push ( k ) ;
180
+ if ( Z ) GZ . push ( G === Z ? f : Z [ b [ 0 ] ] ) ;
181
+ if ( F ) GF . push ( G === F ? f : F [ b [ 0 ] ] ) ;
182
+ if ( S ) GS . push ( G === S ? f : S [ b [ 0 ] ] ) ;
183
+ if ( BX1 ) BX1 . push ( extent . x1 ) , BX2 . push ( extent . x2 ) ;
184
+ if ( BY1 ) BY1 . push ( extent . y1 ) , BY2 . push ( extent . y2 ) ;
185
+ for ( const o of outputs ) o . reduce ( b , extent ) ;
186
+ if ( sort ) sort . reduce ( b ) ;
184
187
}
185
188
}
186
189
}
@@ -224,39 +227,72 @@ function maybeBin(options) {
224
227
if ( options == null ) return ;
225
228
const { value, cumulative, domain = extent , thresholds} = options ;
226
229
const bin = ( data ) => {
227
- let V = valueof ( data , value , Array ) ; // d3.bin prefers Array input
228
- const bin = binner ( ) . value ( ( i ) => V [ i ] ) ;
230
+ let V = valueof ( data , value ) ;
231
+ let T ; // bin thresholds
229
232
if ( isTemporal ( V ) || isTimeThresholds ( thresholds ) ) {
230
- V = V . map ( coerceDate ) ;
233
+ V = map ( V , coerceDate , Float64Array ) ;
231
234
let [ min , max ] = typeof domain === "function" ? domain ( V ) : domain ;
232
235
let t = typeof thresholds === "function" && ! isInterval ( thresholds ) ? thresholds ( V , min , max ) : thresholds ;
233
236
if ( typeof t === "number" ) t = utcTickInterval ( min , max , t ) ;
234
237
if ( isInterval ( t ) ) {
235
238
if ( domain === extent ) {
236
239
min = t . floor ( min ) ;
237
- max = t . ceil ( new Date ( + max + 1 ) ) ;
240
+ max = t . offset ( t . floor ( max ) ) ;
238
241
}
239
- t = t . range ( min , max ) ;
242
+ t = t . range ( min , t . offset ( max ) ) ;
240
243
}
241
- bin . thresholds ( t ) . domain ( [ min , max ] ) ;
244
+ T = t ;
242
245
} else {
243
- V = V . map ( coerceNumber ) ;
244
- let d = domain ;
245
- let t = thresholds ;
246
- if ( isInterval ( t ) ) {
247
- let [ min , max ] = typeof d === "function" ? d ( V ) : d ;
248
- if ( d === extent ) {
246
+ V = map ( V , coerceNumber , Float64Array ) ; // TODO deduplicate with code above
247
+ let [ min , max ] = typeof domain === "function" ? domain ( V ) : domain ;
248
+ let t = typeof thresholds === "function" && ! isInterval ( thresholds ) ? thresholds ( V , min , max ) : thresholds ;
249
+ if ( typeof t === "number" ) {
250
+ // This differs from d3.ticks with regard to exclusive bounds: we want a
251
+ // first threshold less than or equal to the minimum, and a last
252
+ // threshold (strictly) greater than the maximum.
253
+ if ( domain === extent ) {
254
+ let step = tickIncrement ( min , max , t ) ;
255
+ if ( isFinite ( step ) ) {
256
+ if ( step > 0 ) {
257
+ let r0 = Math . round ( min / step ) ;
258
+ let r1 = Math . round ( max / step ) ;
259
+ if ( ! ( r0 * step <= min ) ) -- r0 ;
260
+ if ( ! ( r1 * step > max ) ) ++ r1 ;
261
+ let n = r1 - r0 + 1 ;
262
+ t = new Float64Array ( n ) ;
263
+ for ( let i = 0 ; i < n ; ++ i ) t [ i ] = ( r0 + i ) * step ;
264
+ } else if ( step < 0 ) {
265
+ step = - step ;
266
+ let r0 = Math . round ( min * step ) ;
267
+ let r1 = Math . round ( max * step ) ;
268
+ if ( ! ( r0 / step <= min ) ) -- r0 ;
269
+ if ( ! ( r1 / step > max ) ) ++ r1 ;
270
+ let n = r1 - r0 + 1 ;
271
+ t = new Float64Array ( n ) ;
272
+ for ( let i = 0 ; i < n ; ++ i ) t [ i ] = ( r0 + i ) / step ;
273
+ } else {
274
+ t = [ min ] ;
275
+ }
276
+ } else {
277
+ t = [ min ] ;
278
+ }
279
+ } else {
280
+ t = ticks ( min , max , t ) ;
281
+ }
282
+ } else if ( isInterval ( t ) ) {
283
+ if ( domain === extent ) {
249
284
min = t . floor ( min ) ;
250
285
max = t . offset ( t . floor ( max ) ) ;
251
- d = [ min , max ] ;
252
286
}
253
- t = t . range ( min , max ) ;
287
+ t = t . range ( min , t . offset ( max ) ) ;
254
288
}
255
- bin . thresholds ( t ) . domain ( d ) ;
289
+ T = t ;
256
290
}
257
- let bins = bin ( range ( data ) ) . map ( binset ) ;
258
- if ( cumulative ) bins = ( cumulative < 0 ? bins . reverse ( ) : bins ) . map ( bincumset ) ;
259
- return bins . map ( binfilter ) ;
291
+ const E = [ ] ;
292
+ if ( T . length === 1 ) E . push ( [ T [ 0 ] , T [ 0 ] ] ) ; // collapsed domain
293
+ else for ( let i = 1 ; i < T . length ; ++ i ) E . push ( [ T [ i - 1 ] , T [ i ] ] ) ;
294
+ E . bin = ( cumulative < 0 ? bin1cn : cumulative > 0 ? bin1cp : bin1 ) ( E , T , V ) ;
295
+ return E ;
260
296
} ;
261
297
bin . label = labelof ( value ) ;
262
298
return bin ;
@@ -305,38 +341,66 @@ function isInterval(t) {
305
341
return t ? typeof t . range === "function" : false ;
306
342
}
307
343
308
- function binset ( bin ) {
309
- return [ bin , new Set ( bin ) ] ;
310
- }
311
-
312
- function bincumset ( [ bin ] , j , bins ) {
313
- return [
314
- bin ,
315
- {
316
- get size ( ) {
317
- for ( let k = 0 ; k <= j ; ++ k ) {
318
- if ( bins [ k ] [ 1 ] . size ) {
319
- return 1 ; // a non-empty value
344
+ function Bin ( EX , EY ) {
345
+ return EX && EY
346
+ ? function * ( I ) {
347
+ const X = EX . bin ( I ) ; // first bin on x
348
+ for ( const [ ix , [ x1 , x2 ] ] of EX . entries ( ) ) {
349
+ const Y = EY . bin ( X [ ix ] ) ; // then bin on y
350
+ for ( const [ iy , [ y1 , y2 ] ] of EY . entries ( ) ) {
351
+ yield [ Y [ iy ] , { x1, y1, x2, y2} ] ;
320
352
}
321
353
}
322
- return 0 ;
323
- } ,
324
- has ( i ) {
325
- for ( let k = 0 ; k <= j ; ++ k ) {
326
- if ( bins [ k ] [ 1 ] . has ( i ) ) {
327
- return true ;
328
- }
354
+ }
355
+ : EX
356
+ ? function * ( I ) {
357
+ const X = EX . bin ( I ) ;
358
+ for ( const [ i , [ x1 , x2 ] ] of EX . entries ( ) ) {
359
+ yield [ X [ i ] , { x1, x2} ] ;
329
360
}
330
- return false ;
331
361
}
332
- }
333
- ] ;
362
+ : function * ( I ) {
363
+ const Y = EY . bin ( I ) ;
364
+ for ( const [ i , [ y1 , y2 ] ] of EY . entries ( ) ) {
365
+ yield [ Y [ i ] , { y1, y2} ] ;
366
+ }
367
+ } ;
334
368
}
335
369
336
- function binfilter ( [ { x0, x1} , set ] ) {
337
- return [ x0 , x1 , set . size ? ( I ) => I . filter ( set . has , set ) : binempty ] ;
370
+ // non-cumulative distribution
371
+ function bin1 ( E , T , V ) {
372
+ T = T . map ( coerceNumber ) ; // for faster bisection; TODO skip if already typed
373
+ return ( I ) => {
374
+ const B = E . map ( ( ) => [ ] ) ;
375
+ for ( const i of I ) B [ bisect ( T , V [ i ] ) - 1 ] ?. push ( i ) ; // TODO quantization?
376
+ return B ;
377
+ } ;
338
378
}
339
379
340
- function binempty ( ) {
341
- return new Uint32Array ( 0 ) ;
380
+ // cumulative distribution
381
+ function bin1cp ( E , T , V ) {
382
+ const bin = bin1 ( E , T , V ) ;
383
+ return ( I ) => {
384
+ const B = bin ( I ) ;
385
+ for ( let i = 1 , n = B . length ; i < n ; ++ i ) {
386
+ const C = B [ i - 1 ] ;
387
+ const b = B [ i ] ;
388
+ for ( const j of C ) b . push ( j ) ;
389
+ }
390
+ return B ;
391
+ } ;
392
+ }
393
+
394
+ // complementary cumulative distribution
395
+ function bin1cn ( E , T , V ) {
396
+ const bin = bin1 ( E , T , V ) ;
397
+ return ( I ) => {
398
+ const B = bin ( I ) ;
399
+ for ( let i = B . length - 2 ; i >= 0 ; -- i ) {
400
+ const C = B [ i + 1 ] ;
401
+ const b = B [ i ] ;
402
+ for ( const j of C ) b . push ( j ) ;
403
+ }
404
+ return B ;
405
+ } ;
342
406
}
0 commit comments