Skip to content

Commit 6005af8

Browse files
committed
facets closer to working
1 parent c086ee4 commit 6005af8

File tree

1 file changed

+68
-23
lines changed

1 file changed

+68
-23
lines changed

src/plot.js

+68-23
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,49 @@ export function plot(options = {}) {
7979
timeMarks.set(mark, valueof(mark.data, mark.timeChannel.time.value));
8080
}
8181
}
82-
const timeChannels = Array.from(timeMarks, ([,times]) => ({value: times}));
82+
const timeChannels = Array.from(timeMarks, ([, times]) => ({value: times}));
8383
const timeDomain = inferDomain(timeChannels);
8484
const times = aggregateTimes(timeChannels);
85+
const timesIndex = new Map(times.map((d,i) => [d,i]));
8586

8687
// Initialize the marks’ state.
8788
for (const mark of marks) {
8889
if (stateByMark.has(mark)) throw new Error("duplicate mark; each mark must be unique");
8990

90-
// TODO: augment the facets with time, for time-aware marks
91-
const markFacets = facetsIndex === undefined ? undefined
91+
let markFacets = facetsIndex === undefined ? undefined
9292
: mark.facet === "auto" ? mark.data === facet.data ? facetsIndex : undefined
9393
: mark.facet === "include" ? facetsIndex
9494
: mark.facet === "exclude" ? facetsExclude || (facetsExclude = facetsIndex.map(f => Uint32Array.from(difference(facetIndex, f))))
9595
: 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);
97110
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+
98125
stateByMark.set(mark, {data, facets, channels});
99126
}
100127

@@ -144,6 +171,8 @@ export function plot(options = {}) {
144171
state.values = valueObject(state.channels, scales);
145172
}
146173

174+
const animateMarks = [];
175+
147176
const {width, height} = dimensions;
148177

149178
const svg = create("svg", context)
@@ -224,19 +253,41 @@ export function plot(options = {}) {
224253
for (const [mark, {channels, values, facets}] of stateByMark) {
225254
const facet = facets ? mark.filter(facets[j] ?? facets[0], channels, values) : null;
226255
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+
}
228268
}
229269
});
230270
} else {
231271
for (const [mark, {channels, values, facets}] of stateByMark) {
232272
const facet = facets ? mark.filter(facets[0], channels, values) : null;
233273
const index = channels.time ? [] : facet;
234274
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+
}
236287
}
237288
}
238289

239-
if (timeMarks.size > 0) {
290+
if (animateMarks.length > 0) {
240291
// TODO There needs to be an option to avoid interpolation and just play
241292
// the distinct times, as given, in ascending order, as keyframes. And
242293
// there needs to be an option to control the delay, duration, iterations,
@@ -245,26 +296,23 @@ export function plot(options = {}) {
245296
const delay = 0; // TODO configurable; delay initial rendering
246297
const duration = 5000; // TODO configurable
247298
const startTime = performance.now() + delay;
248-
console.warn(timeMarks);
249-
250-
if (false) requestAnimationFrame(function tick() {
299+
requestAnimationFrame(function tick() {
251300
const t = Math.max(0, Math.min(1, (performance.now() - startTime) / duration));
252301
const currentTime = interpolateTime(t);
253302
const i0 = bisectLeft(times, currentTime);
254303
const time0 = times[i0 - 1];
255304
const time1 = times[i0];
256305
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();
259309
const {values} = stateByMark.get(mark);
260-
const {time: T} = values;
261310
let timeNode;
262311
if (isFinite(timet)) {
263312
const I0 = facet.filter(i => T[i] === time0); // preceding keyframe
264313
const I1 = facet.filter(i => T[i] === time1); // following keyframe
265314
const n = I0.length; // TODO enter, exit, key
266315
const Ii = I0.map((_, i) => i + facet.length); // TODO optimize
267-
268316
// TODO This is interpolating the already-scaled values, but we
269317
// probably want to interpolate in data space instead and then
270318
// re-apply the scales. I’m not sure what to do for ordinal data,
@@ -278,16 +326,13 @@ export function plot(options = {}) {
278326
// default with the dot mark) breaks consistent ordering! TODO If
279327
// the time filter is not “eq” (strict equals) here, then we’ll need
280328
// to combine the interpolated data with the filtered data.
329+
for (let i = 0; i < n; ++i) {
330+
interp.time[Ii[i]] = currentTime;
331+
}
281332
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);
291336
}
292337
}
293338

0 commit comments

Comments
 (0)