1
- import { cross , difference , groups , InternMap , select } from "d3" ;
1
+ import { bisectLeft , cross , difference , groups , InternMap , select } 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" ;
@@ -124,6 +124,9 @@ export function plot(options = {}) {
124
124
125
125
autoScaleLabels ( channelsByScale , scaleDescriptors , axes , dimensions , options ) ;
126
126
127
+ // Aggregate and sort time channels.
128
+ const times = aggregateTimes ( stateByMark ) ;
129
+
127
130
// Compute value objects, applying scales as needed.
128
131
for ( const state of stateByMark . values ( ) ) {
129
132
state . values = valueObject ( state . channels , scales ) ;
@@ -213,11 +216,31 @@ export function plot(options = {}) {
213
216
}
214
217
} ) ;
215
218
} else {
219
+ const timeMarks = [ ] ;
216
220
for ( const [ mark , { channels, values, facets} ] of stateByMark ) {
217
221
const facet = facets ? mark . filter ( facets [ 0 ] , channels , values ) : null ;
218
- const node = mark . render ( facet , scales , values , dimensions , context ) ;
222
+ const index = channels . time ? [ ] : facet ;
223
+ const node = mark . render ( index , scales , values , dimensions , context ) ;
224
+ if ( channels . time ) timeMarks . push ( { mark, node} ) ;
219
225
if ( node != null ) svg . appendChild ( node ) ;
220
226
}
227
+ if ( timeMarks . length ) {
228
+ let timeIndex = - 1 ;
229
+ requestAnimationFrame ( function tick ( ) {
230
+ if ( ++ timeIndex >= times . length ) return ;
231
+ const time = times [ timeIndex ] ;
232
+ for ( const timeMark of timeMarks ) {
233
+ const { mark, node} = timeMark ;
234
+ const { channels, values, facets} = stateByMark . get ( mark ) ;
235
+ const facet = facets ? mark . filter ( facets [ 0 ] , channels , values ) : null ;
236
+ const index = facet . filter ( i => channels . time . value [ i ] <= time ) ;
237
+ const timeNode = mark . render ( index , scales , values , dimensions , context ) ;
238
+ node . replaceWith ( timeNode ) ;
239
+ timeMark . node = timeNode ;
240
+ }
241
+ requestAnimationFrame ( tick ) ;
242
+ } ) ;
243
+ }
221
244
}
222
245
223
246
// Wrap the plot in a figure with a caption, if desired.
@@ -257,7 +280,7 @@ export function plot(options = {}) {
257
280
258
281
export class Mark {
259
282
constructor ( data , channels = [ ] , options = { } , defaults ) {
260
- const { facet = "auto" , sort, dx, dy, clip, channels : extraChannels } = options ;
283
+ const { facet = "auto" , sort, time , dx, dy, clip, channels : extraChannels } = options ;
261
284
const names = new Set ( ) ;
262
285
this . data = data ;
263
286
this . sort = isDomainSort ( sort ) ? sort : null ;
@@ -266,6 +289,7 @@ export class Mark {
266
289
this . facet = facet == null || facet === false ? null : keyword ( facet === true ? "include" : facet , "facet" , [ "auto" , "include" , "exclude" ] ) ;
267
290
if ( extraChannels !== undefined ) channels = [ ...channels , ...extraChannels . filter ( e => ! channels . some ( c => c . name === e . name ) ) ] ;
268
291
if ( defaults !== undefined ) channels = [ ...channels , ...styles ( this , options , defaults ) ] ;
292
+ if ( time != null ) channels = [ ...channels , { name : "time" , value : time } ] ;
269
293
this . channels = Object . fromEntries ( channels . filter ( channel => {
270
294
const { name, value, optional} = channel ;
271
295
if ( value == null ) {
@@ -375,6 +399,21 @@ function addScaleChannels(channelsByScale, stateByMark, filter = yes) {
375
399
return channelsByScale ;
376
400
}
377
401
402
+ function aggregateTimes ( stateByMark ) {
403
+ const times = [ ] ;
404
+ for ( const { channels : { time} } of stateByMark . values ( ) ) {
405
+ if ( time ) {
406
+ for ( let t of time . value ) {
407
+ if ( t == null || isNaN ( t = + t ) ) continue ;
408
+ const i = bisectLeft ( times , t ) ;
409
+ if ( times [ i ] === t ) continue ;
410
+ times . splice ( i , 0 , t ) ;
411
+ }
412
+ }
413
+ }
414
+ return times ;
415
+ }
416
+
378
417
// Derives a copy of the specified axis with the label disabled.
379
418
function nolabel ( axis ) {
380
419
return axis === undefined || axis . label === undefined
0 commit comments