@@ -79,22 +79,49 @@ export function plot(options = {}) {
79
79
timeMarks . set ( mark , valueof ( mark . data , mark . timeChannel . time . value ) ) ;
80
80
}
81
81
}
82
- const timeChannels = Array . from ( timeMarks , ( [ , times ] ) => ( { value : times } ) ) ;
82
+ const timeChannels = Array . from ( timeMarks , ( [ , times ] ) => ( { value : times } ) ) ;
83
83
const timeDomain = inferDomain ( timeChannels ) ;
84
84
const times = aggregateTimes ( timeChannels ) ;
85
+ const timesIndex = new Map ( times . map ( ( d , i ) => [ d , i ] ) ) ;
85
86
86
87
// Initialize the marks’ state.
87
88
for ( const mark of marks ) {
88
89
if ( stateByMark . has ( mark ) ) throw new Error ( "duplicate mark; each mark must be unique" ) ;
89
90
90
- // TODO: augment the facets with time, for time-aware marks
91
- const markFacets = facetsIndex === undefined ? undefined
91
+ let markFacets = facetsIndex === undefined ? undefined
92
92
: mark . facet === "auto" ? mark . data === facet . data ? facetsIndex : undefined
93
93
: mark . facet === "include" ? facetsIndex
94
94
: mark . facet === "exclude" ? facetsExclude || ( facetsExclude = facetsIndex . map ( f => Uint32Array . from ( difference ( facetIndex , f ) ) ) )
95
95
: undefined ;
96
- const { data, facets, channels} = mark . initialize ( markFacets , facetChannels ) ;
96
+
97
+ // Split across time facets
98
+ if ( timeMarks . has ( mark ) && times . length > 1 ) {
99
+ const T = timeMarks . get ( mark ) ;
100
+ markFacets = ( markFacets || [ range ( mark . data ) ] ) . flatMap ( facet => {
101
+ const keyFrames = Array . from ( times , ( ) => [ ] ) ;
102
+ for ( const i of facet ) {
103
+ keyFrames [ timesIndex . get ( T [ i ] ) ] . push ( i ) ;
104
+ }
105
+ return keyFrames ;
106
+ } ) ;
107
+ }
108
+
109
+ let { data, facets, channels} = mark . initialize ( markFacets , facetChannels ) ;
97
110
applyScaleTransforms ( channels , options ) ;
111
+
112
+ // Reassemble across time facets
113
+ if ( timeMarks . has ( mark ) && times . length > 1 ) {
114
+ const newFacets = [ ] ;
115
+ const newTimes = [ ] ;
116
+ for ( let k = 0 ; k < facets . length ; ++ k ) {
117
+ const j = Math . floor ( k / times . length ) ;
118
+ newFacets [ j ] = newFacets [ j ] ? newFacets [ j ] . concat ( facets [ k ] ) : facets [ k ] ;
119
+ for ( const i of facets [ k ] ) newTimes [ i ] = times [ k % times . length ] ;
120
+ }
121
+ facets = newFacets ;
122
+ timeMarks . set ( mark , newTimes ) ;
123
+ }
124
+
98
125
stateByMark . set ( mark , { data, facets, channels} ) ;
99
126
}
100
127
@@ -144,6 +171,8 @@ export function plot(options = {}) {
144
171
state . values = valueObject ( state . channels , scales ) ;
145
172
}
146
173
174
+ const animateMarks = [ ] ;
175
+
147
176
const { width, height} = dimensions ;
148
177
149
178
const svg = create ( "svg" , context )
@@ -224,19 +253,41 @@ export function plot(options = {}) {
224
253
for ( const [ mark , { channels, values, facets} ] of stateByMark ) {
225
254
const facet = facets ? mark . filter ( facets [ j ] ?? facets [ 0 ] , channels , values ) : null ;
226
255
const node = mark . render ( facet , scales , values , subdimensions , context ) ;
227
- if ( node != null ) this . appendChild ( node ) ;
256
+ if ( node != null ) {
257
+ this . appendChild ( node ) ;
258
+ if ( timeMarks . has ( mark ) ) {
259
+ animateMarks . push ( {
260
+ mark,
261
+ node,
262
+ facet,
263
+ time : timeMarks . get ( mark ) ,
264
+ interp : Object . fromEntries ( Object . entries ( values ) . map ( ( [ key , value ] ) => [ key , Array . from ( value ) ] ) )
265
+ } ) ;
266
+ }
267
+ }
228
268
}
229
269
} ) ;
230
270
} else {
231
271
for ( const [ mark , { channels, values, facets} ] of stateByMark ) {
232
272
const facet = facets ? mark . filter ( facets [ 0 ] , channels , values ) : null ;
233
273
const index = channels . time ? [ ] : facet ;
234
274
const node = mark . render ( index , scales , values , dimensions , context ) ;
235
- if ( node != null ) svg . appendChild ( node ) ;
275
+ if ( node != null ) {
276
+ svg . appendChild ( node ) ;
277
+ if ( timeMarks . has ( mark ) ) {
278
+ animateMarks . push ( {
279
+ mark,
280
+ node,
281
+ facet,
282
+ time : timeMarks . get ( mark ) ,
283
+ interp : Object . fromEntries ( Object . entries ( values ) . map ( ( [ key , value ] ) => [ key , Array . from ( value ) ] ) )
284
+ } ) ;
285
+ }
286
+ }
236
287
}
237
288
}
238
289
239
- if ( timeMarks . size > 0 ) {
290
+ if ( animateMarks . length > 0 ) {
240
291
// TODO There needs to be an option to avoid interpolation and just play
241
292
// the distinct times, as given, in ascending order, as keyframes. And
242
293
// there needs to be an option to control the delay, duration, iterations,
@@ -245,26 +296,23 @@ export function plot(options = {}) {
245
296
const delay = 0 ; // TODO configurable; delay initial rendering
246
297
const duration = 5000 ; // TODO configurable
247
298
const startTime = performance . now ( ) + delay ;
248
- console . warn ( timeMarks ) ;
249
-
250
- if ( false ) requestAnimationFrame ( function tick ( ) {
299
+ requestAnimationFrame ( function tick ( ) {
251
300
const t = Math . max ( 0 , Math . min ( 1 , ( performance . now ( ) - startTime ) / duration ) ) ;
252
301
const currentTime = interpolateTime ( t ) ;
253
302
const i0 = bisectLeft ( times , currentTime ) ;
254
303
const time0 = times [ i0 - 1 ] ;
255
304
const time1 = times [ i0 ] ;
256
305
const timet = ( currentTime - time0 ) / ( time1 - time0 ) ;
257
- for ( const timeMark of timeMarks ) {
258
- const { mark, facet, interp} = timeMark ;
306
+ for ( const timeMark of animateMarks ) {
307
+ const { mark, facet, time : T , interp} = timeMark ;
308
+ interp . time = T . slice ( ) ;
259
309
const { values} = stateByMark . get ( mark ) ;
260
- const { time : T } = values ;
261
310
let timeNode ;
262
311
if ( isFinite ( timet ) ) {
263
312
const I0 = facet . filter ( i => T [ i ] === time0 ) ; // preceding keyframe
264
313
const I1 = facet . filter ( i => T [ i ] === time1 ) ; // following keyframe
265
314
const n = I0 . length ; // TODO enter, exit, key
266
315
const Ii = I0 . map ( ( _ , i ) => i + facet . length ) ; // TODO optimize
267
-
268
316
// TODO This is interpolating the already-scaled values, but we
269
317
// probably want to interpolate in data space instead and then
270
318
// re-apply the scales. I’m not sure what to do for ordinal data,
@@ -278,16 +326,13 @@ export function plot(options = {}) {
278
326
// default with the dot mark) breaks consistent ordering! TODO If
279
327
// the time filter is not “eq” (strict equals) here, then we’ll need
280
328
// to combine the interpolated data with the filtered data.
329
+ for ( let i = 0 ; i < n ; ++ i ) {
330
+ interp . time [ Ii [ i ] ] = currentTime ;
331
+ }
281
332
for ( const k in values ) {
282
- if ( k === "time" ) {
283
- for ( let i = 0 ; i < n ; ++ i ) {
284
- interp [ k ] [ Ii [ i ] ] = currentTime ;
285
- }
286
- } else {
287
- for ( let i = 0 ; i < n ; ++ i ) {
288
- const past = values [ k ] [ I0 [ i ] ] , future = values [ k ] [ I1 [ i ] ] ;
289
- interp [ k ] [ Ii [ i ] ] = past == future ? past : interpolate ( past , future ) ( timet ) ;
290
- }
333
+ for ( let i = 0 ; i < n ; ++ i ) {
334
+ const past = values [ k ] [ I0 [ i ] ] , future = values [ k ] [ I1 [ i ] ] ;
335
+ interp [ k ] [ Ii [ i ] ] = past == future ? past : interpolate ( past , future ) ( timet ) ;
291
336
}
292
337
}
293
338
0 commit comments