@voila.dev/chart 5.0.114 → 5.0.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas/paint-scene.d.ts.map +1 -1
- package/dist/canvas/paint-scene.js +3 -1
- package/dist/canvas/paint-scene.js.map +2 -2
- package/dist/contour/density.d.ts.map +1 -1
- package/dist/contour/density.js +2 -0
- package/dist/contour/density.js.map +2 -2
- package/dist/core/marks/area.d.ts.map +1 -1
- package/dist/core/marks/area.js +27 -11
- package/dist/core/marks/area.js.map +2 -2
- package/dist/core/marks/bar.d.ts.map +1 -1
- package/dist/core/marks/bar.js +2 -0
- package/dist/core/marks/bar.js.map +2 -2
- package/dist/core/marks/line.d.ts.map +1 -1
- package/dist/core/marks/line.js +13 -4
- package/dist/core/marks/line.js.map +2 -2
- package/dist/core/marks/polar.d.ts.map +1 -1
- package/dist/core/marks/polar.js +22 -5
- package/dist/core/marks/polar.js.map +2 -2
- package/dist/core/marks/shared.d.ts +8 -1
- package/dist/core/marks/shared.d.ts.map +1 -1
- package/dist/core/marks/shared.js.map +2 -2
- package/dist/core/marks/stats/difference.d.ts.map +1 -1
- package/dist/core/marks/stats/difference.js +20 -7
- package/dist/core/marks/stats/difference.js.map +2 -2
- package/dist/core/motion/arc-motion.d.ts +9 -0
- package/dist/core/motion/arc-motion.d.ts.map +1 -0
- package/dist/core/motion/arc-motion.js +67 -0
- package/dist/core/motion/arc-motion.js.map +7 -0
- package/dist/core/motion/enter-exit.d.ts +6 -0
- package/dist/core/motion/enter-exit.d.ts.map +1 -0
- package/dist/core/motion/enter-exit.js +20 -0
- package/dist/core/motion/enter-exit.js.map +7 -0
- package/dist/core/motion/leaving.d.ts +12 -0
- package/dist/core/motion/leaving.d.ts.map +1 -0
- package/dist/core/motion/leaving.js +33 -0
- package/dist/core/motion/leaving.js.map +7 -0
- package/dist/core/motion/motion-store.d.ts +4 -1
- package/dist/core/motion/motion-store.d.ts.map +1 -1
- package/dist/core/motion/motion-store.js +251 -100
- package/dist/core/motion/motion-store.js.map +3 -3
- package/dist/core/motion/points-motion.d.ts +6 -0
- package/dist/core/motion/points-motion.d.ts.map +1 -0
- package/dist/core/motion/points-motion.js +140 -0
- package/dist/core/motion/points-motion.js.map +7 -0
- package/dist/core/motion/strings.d.ts +18 -0
- package/dist/core/motion/strings.d.ts.map +1 -0
- package/dist/core/motion/strings.js +34 -0
- package/dist/core/motion/strings.js.map +7 -0
- package/dist/core/types.d.ts +85 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/geo/geo.d.ts.map +1 -1
- package/dist/geo/geo.js +2 -0
- package/dist/geo/geo.js.map +2 -2
- package/dist/react/chart.d.ts.map +1 -1
- package/dist/react/chart.js +6 -2
- package/dist/react/chart.js.map +2 -2
- package/dist/react/svg-scene.d.ts.map +1 -1
- package/dist/react/svg-scene.js +3 -1
- package/dist/react/svg-scene.js.map +2 -2
- package/dist/react/use-animated-scene.d.ts.map +1 -1
- package/dist/react/use-animated-scene.js +14 -2
- package/dist/react/use-animated-scene.js.map +2 -2
- package/dist/voronoi/voronoi.d.ts.map +1 -1
- package/dist/voronoi/voronoi.js +2 -0
- package/dist/voronoi/voronoi.js.map +2 -2
- package/package.json +2 -2
- package/src/canvas/paint-scene.ts +5 -1
- package/src/contour/density.ts +2 -0
- package/src/core/marks/area.ts +28 -13
- package/src/core/marks/bar.ts +2 -0
- package/src/core/marks/line.ts +15 -8
- package/src/core/marks/polar.ts +22 -5
- package/src/core/marks/shared.ts +8 -0
- package/src/core/marks/stats/difference.ts +28 -8
- package/src/core/motion/arc-motion.ts +113 -0
- package/src/core/motion/enter-exit.ts +44 -0
- package/src/core/motion/leaving.ts +55 -0
- package/src/core/motion/motion-store.ts +332 -109
- package/src/core/motion/points-motion.ts +190 -0
- package/src/core/motion/strings.ts +68 -0
- package/src/core/types.ts +98 -0
- package/src/geo/geo.ts +2 -0
- package/src/react/chart.tsx +8 -2
- package/src/react/svg-scene.tsx +5 -1
- package/src/react/use-animated-scene.ts +18 -2
- package/src/voronoi/voronoi.ts +2 -0
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { type Generator, spring, tween } from "@voila.dev/motion";
|
|
2
|
+
import { enterFrom, exitTo } from "#/core/motion/enter-exit.ts";
|
|
3
|
+
import {
|
|
4
|
+
type LeavingGuide,
|
|
5
|
+
leavingGuides,
|
|
6
|
+
withLeaving,
|
|
7
|
+
} from "#/core/motion/leaving.ts";
|
|
8
|
+
import {
|
|
9
|
+
STRING_CHANNELS,
|
|
10
|
+
type StringChannel,
|
|
11
|
+
stringMixer,
|
|
12
|
+
stringOf,
|
|
13
|
+
withStrings,
|
|
14
|
+
} from "#/core/motion/strings.ts";
|
|
2
15
|
import { type ChartTiming, staggerDelay } from "#/core/motion/timing.ts";
|
|
3
|
-
import {
|
|
4
|
-
|
|
16
|
+
import type {
|
|
17
|
+
ChartScene,
|
|
18
|
+
GeometryMotion,
|
|
19
|
+
GeometryPlan,
|
|
20
|
+
SceneNode,
|
|
21
|
+
} from "#/core/types.ts";
|
|
5
22
|
|
|
6
23
|
/**
|
|
7
24
|
* Every moving number of a chart, as springs keyed by node and channel. A
|
|
@@ -9,10 +26,13 @@ import type { ChartScene, SceneNode } from "#/core/types.ts";
|
|
|
9
26
|
* the speed it has, so an update landing mid-flight bends the motion instead
|
|
10
27
|
* of kinking it. Pure and clocked from outside (milliseconds), so the SVG and
|
|
11
28
|
* Canvas renderers move identically and tests step it by hand. Hit-testing
|
|
12
|
-
* and focus never read it: they read the scene it moves towards
|
|
29
|
+
* and focus never read it: they read the scene it moves towards, which never
|
|
30
|
+
* holds the marks collapsing or the guides fading on their way out.
|
|
13
31
|
*/
|
|
14
32
|
export interface MotionStore {
|
|
15
33
|
retarget(scene: ChartScene, now: number): void;
|
|
34
|
+
/** The first render: lines that `enter: "draw"` trace themselves in. False when none does. */
|
|
35
|
+
introduce(now: number): boolean;
|
|
16
36
|
/** Jumps to `scene`: a resize, or reduced motion. */
|
|
17
37
|
snap(scene: ChartScene): void;
|
|
18
38
|
frame(now: number): ChartScene;
|
|
@@ -25,14 +45,26 @@ interface Motion {
|
|
|
25
45
|
readonly start: number;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
readonly
|
|
48
|
+
/** A path or a colour: a progress from 0 to 1, and the mix it reads. */
|
|
49
|
+
interface StringMotion extends Motion {
|
|
50
|
+
readonly mix: (progress: number) => string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A guide fading out where it stood. */
|
|
54
|
+
interface GuideMotion extends Motion {
|
|
55
|
+
readonly guide: LeavingGuide;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A data mark collapsing on its way out: drawn after its old sibling until its springs settle. */
|
|
59
|
+
interface Leaving {
|
|
60
|
+
readonly node: SceneNode;
|
|
61
|
+
readonly after: string | undefined;
|
|
62
|
+
readonly siblings: readonly SceneNode[];
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
type Numeric = Record<string, number>;
|
|
34
66
|
|
|
35
|
-
const
|
|
67
|
+
const FIELDS: Record<SceneNode["kind"], readonly string[]> = {
|
|
36
68
|
rect: ["x", "y", "width", "height"],
|
|
37
69
|
circle: ["cx", "cy", "r"],
|
|
38
70
|
line: ["x1", "y1", "x2", "y2"],
|
|
@@ -42,20 +74,31 @@ const CHANNELS: Record<SceneNode["kind"], readonly string[]> = {
|
|
|
42
74
|
};
|
|
43
75
|
|
|
44
76
|
const OPACITY = "opacity";
|
|
77
|
+
const DRAWN = "drawn";
|
|
78
|
+
const SEPARATOR = "\u0000";
|
|
45
79
|
|
|
46
|
-
function
|
|
80
|
+
function walk(
|
|
47
81
|
nodes: readonly SceneNode[],
|
|
48
|
-
|
|
82
|
+
visit: (node: SceneNode, siblings: readonly SceneNode[]) => void,
|
|
49
83
|
) {
|
|
50
84
|
for (const node of nodes) {
|
|
51
|
-
|
|
52
|
-
if (node.kind === "group")
|
|
85
|
+
visit(node, nodes);
|
|
86
|
+
if (node.kind === "group") walk(node.children, visit);
|
|
53
87
|
}
|
|
54
|
-
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function index(nodes: readonly SceneNode[]) {
|
|
91
|
+
const byKey = new Map<string, SceneNode>();
|
|
92
|
+
const siblings = new Map<string, readonly SceneNode[]>();
|
|
93
|
+
walk(nodes, (node, list) => {
|
|
94
|
+
byKey.set(node.key, node);
|
|
95
|
+
siblings.set(node.key, list);
|
|
96
|
+
});
|
|
97
|
+
return { byKey, siblings };
|
|
55
98
|
}
|
|
56
99
|
|
|
57
100
|
function channelKey(node: string, channel: string): string {
|
|
58
|
-
return `${node}
|
|
101
|
+
return `${node}${SEPARATOR}${channel}`;
|
|
59
102
|
}
|
|
60
103
|
|
|
61
104
|
function elapsed(motion: Motion, now: number): number {
|
|
@@ -66,13 +109,23 @@ function done(motion: Motion, now: number): boolean {
|
|
|
66
109
|
return elapsed(motion, now) >= motion.generator.duration;
|
|
67
110
|
}
|
|
68
111
|
|
|
112
|
+
function clamp(value: number): number {
|
|
113
|
+
return Math.min(1, Math.max(0, value));
|
|
114
|
+
}
|
|
115
|
+
|
|
69
116
|
export function createMotionStore(
|
|
70
117
|
initial: ChartScene,
|
|
71
118
|
timing: ChartTiming,
|
|
72
119
|
): MotionStore {
|
|
73
120
|
let target = initial;
|
|
74
121
|
const numbers = new Map<string, Motion>();
|
|
75
|
-
const
|
|
122
|
+
const strings = new Map<string, StringMotion>();
|
|
123
|
+
const guides = new Map<string, GuideMotion>();
|
|
124
|
+
const shapes = new Map<
|
|
125
|
+
string,
|
|
126
|
+
{ readonly plan: GeometryPlan; readonly motion: GeometryMotion }
|
|
127
|
+
>();
|
|
128
|
+
const leaving = new Map<string, Leaving>();
|
|
76
129
|
|
|
77
130
|
function generator(from: number, to: number, velocity: number): Generator {
|
|
78
131
|
return timing.type === "spring"
|
|
@@ -90,137 +143,307 @@ export function createMotionStore(
|
|
|
90
143
|
});
|
|
91
144
|
}
|
|
92
145
|
|
|
93
|
-
function
|
|
94
|
-
const motion =
|
|
95
|
-
|
|
96
|
-
return tweenPath(
|
|
97
|
-
motion.from,
|
|
98
|
-
motion.to,
|
|
99
|
-
motion.generator.at(elapsed(motion, now)).value,
|
|
100
|
-
);
|
|
146
|
+
function value(node: string, channel: string, fallback: number, now: number) {
|
|
147
|
+
const motion = numbers.get(channelKey(node, channel));
|
|
148
|
+
return motion ? motion.generator.at(elapsed(motion, now)).value : fallback;
|
|
101
149
|
}
|
|
102
150
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
151
|
+
/** Springs one channel from `from` to `to`, at the speed it already has. */
|
|
152
|
+
function spring1(
|
|
153
|
+
node: string,
|
|
154
|
+
channel: string,
|
|
155
|
+
from: number,
|
|
156
|
+
to: number,
|
|
106
157
|
start: number,
|
|
107
158
|
now: number,
|
|
108
159
|
) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
160
|
+
const key = channelKey(node, channel);
|
|
161
|
+
const running = numbers.get(key);
|
|
162
|
+
const velocity = running
|
|
163
|
+
? running.generator.at(elapsed(running, now)).velocity
|
|
164
|
+
: 0;
|
|
165
|
+
numbers.delete(key);
|
|
166
|
+
if (from === to && velocity === 0) return;
|
|
167
|
+
numbers.set(key, { generator: generator(from, to, velocity), start });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Mixes one string channel from what is painted to what `to` asks for. */
|
|
171
|
+
function mixString(
|
|
172
|
+
from: SceneNode,
|
|
173
|
+
to: SceneNode,
|
|
174
|
+
channel: StringChannel,
|
|
175
|
+
start: number,
|
|
176
|
+
) {
|
|
177
|
+
const key = channelKey(to.key, channel);
|
|
178
|
+
strings.delete(key);
|
|
179
|
+
const was = stringOf(from, channel);
|
|
180
|
+
const goal = stringOf(to, channel);
|
|
181
|
+
if (was === undefined || goal === undefined || was === goal) return;
|
|
182
|
+
strings.set(key, {
|
|
183
|
+
generator: generator(0, 1, 0),
|
|
184
|
+
start,
|
|
185
|
+
mix: stringMixer(to, channel, was, goal),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function drop(node: string) {
|
|
190
|
+
for (const motions of [numbers, strings]) {
|
|
191
|
+
for (const key of motions.keys()) {
|
|
192
|
+
if (key.startsWith(node + SEPARATOR)) motions.delete(key);
|
|
193
|
+
}
|
|
122
194
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
195
|
+
shapes.delete(node);
|
|
196
|
+
leaving.delete(node);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** `from` is the node as painted now (or the shape it enters from), `to` where it goes. */
|
|
200
|
+
function move(from: SceneNode, to: SceneNode, start: number, now: number) {
|
|
201
|
+
const key = to.key;
|
|
202
|
+
for (const field of FIELDS[to.kind]) {
|
|
203
|
+
const was = (from as unknown as Numeric)[field] as number;
|
|
204
|
+
spring1(
|
|
205
|
+
key,
|
|
206
|
+
field,
|
|
207
|
+
was,
|
|
208
|
+
(to as unknown as Numeric)[field] as number,
|
|
209
|
+
start,
|
|
127
210
|
now,
|
|
128
211
|
);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
212
|
+
}
|
|
213
|
+
mixString(from, to, "fill", start);
|
|
214
|
+
mixString(from, to, "stroke", start);
|
|
215
|
+
if (to.kind !== "path" || from.kind !== "path") return;
|
|
216
|
+
const previous = shapes.get(key);
|
|
217
|
+
shapes.delete(key);
|
|
218
|
+
strings.delete(channelKey(key, "d"));
|
|
219
|
+
const motion = to.motion;
|
|
220
|
+
const plan =
|
|
221
|
+
motion && from.geometry && to.geometry
|
|
222
|
+
? motion.plan(from.geometry, to.geometry)
|
|
223
|
+
: undefined;
|
|
224
|
+
if (!plan) {
|
|
225
|
+
// No meaning to move: the node's own morph, else the numbers of `d`.
|
|
226
|
+
mixString(from, to, "d", start);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
for (const channel of previous?.plan.targets.keys() ?? []) {
|
|
230
|
+
if (!plan.targets.has(channel)) {
|
|
231
|
+
numbers.delete(channelKey(key, `g${channel}`));
|
|
137
232
|
}
|
|
138
233
|
}
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const from = fade ? fade.generator.at(elapsed(fade, now)).value : 0;
|
|
143
|
-
numbers.set(fading, { generator: generator(from, 1, 0), start });
|
|
234
|
+
for (const [channel, goal] of plan.targets) {
|
|
235
|
+
const was = plan.starts.get(channel) ?? goal;
|
|
236
|
+
spring1(key, `g${channel}`, was, goal, start, now);
|
|
144
237
|
}
|
|
238
|
+
shapes.set(key, { plan, motion: motion as GeometryMotion });
|
|
145
239
|
}
|
|
146
240
|
|
|
147
|
-
function
|
|
241
|
+
function paintNode(node: SceneNode, now: number): SceneNode {
|
|
148
242
|
if (node.kind === "group") {
|
|
149
|
-
return {
|
|
150
|
-
...node,
|
|
151
|
-
children: node.children.map((child) => paint(child, now)),
|
|
152
|
-
};
|
|
243
|
+
return { ...node, children: paintList(node.children, node.key, now) };
|
|
153
244
|
}
|
|
245
|
+
const key = node.key;
|
|
154
246
|
const moved: Numeric = {};
|
|
155
|
-
for (const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
247
|
+
for (const field of FIELDS[node.kind]) {
|
|
248
|
+
moved[field] = value(
|
|
249
|
+
key,
|
|
250
|
+
field,
|
|
251
|
+
(node as unknown as Numeric)[field] as number,
|
|
252
|
+
now,
|
|
253
|
+
);
|
|
159
254
|
}
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
255
|
+
let next = { ...node, ...moved } as Exclude<SceneNode, { kind: "group" }>;
|
|
256
|
+
const shape = shapes.get(key);
|
|
257
|
+
if (next.kind === "path" && shape) {
|
|
258
|
+
const geometry = shape.plan.build((channel) =>
|
|
259
|
+
value(key, `g${channel}`, shape.plan.targets.get(channel) ?? 0, now),
|
|
260
|
+
);
|
|
261
|
+
next = { ...next, geometry, d: shape.motion.draw(geometry) };
|
|
262
|
+
}
|
|
263
|
+
const mixed: Partial<Record<StringChannel, string>> = {};
|
|
264
|
+
for (const channel of STRING_CHANNELS) {
|
|
265
|
+
const motion = strings.get(channelKey(key, channel));
|
|
266
|
+
if (motion) {
|
|
267
|
+
mixed[channel] = motion.mix(
|
|
268
|
+
motion.generator.at(elapsed(motion, now)).value,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
next = withStrings(next, mixed) as typeof next;
|
|
273
|
+
const opacity = value(key, OPACITY, 1, now);
|
|
274
|
+
const drawn = value(key, DRAWN, 1, now);
|
|
275
|
+
if (opacity === 1 && drawn === 1) return next;
|
|
276
|
+
const geometry = next.kind === "path" ? next.geometry : undefined;
|
|
277
|
+
const length =
|
|
278
|
+
geometry && next.kind === "path" ? next.motion?.length?.(geometry) : 0;
|
|
170
279
|
return {
|
|
171
|
-
...
|
|
280
|
+
...next,
|
|
172
281
|
paint: {
|
|
173
|
-
...
|
|
174
|
-
opacity: (
|
|
282
|
+
...next.paint,
|
|
283
|
+
opacity: (next.paint.opacity ?? 1) * clamp(opacity),
|
|
284
|
+
...(drawn < 1 && geometry
|
|
285
|
+
? {
|
|
286
|
+
drawn: {
|
|
287
|
+
fraction: clamp(drawn),
|
|
288
|
+
length: length ?? 0,
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
: {}),
|
|
175
292
|
},
|
|
176
293
|
} as SceneNode;
|
|
177
294
|
}
|
|
178
295
|
|
|
296
|
+
/**
|
|
297
|
+
* The painted list, with the marks still collapsing after the sibling they
|
|
298
|
+
* followed, and the guides still fading back at their places.
|
|
299
|
+
*/
|
|
300
|
+
function paintList(
|
|
301
|
+
list: readonly SceneNode[],
|
|
302
|
+
parent: string | null,
|
|
303
|
+
now: number,
|
|
304
|
+
): SceneNode[] {
|
|
305
|
+
const out: SceneNode[] = [];
|
|
306
|
+
function place(after: string | undefined) {
|
|
307
|
+
for (const [key, exit] of leaving) {
|
|
308
|
+
if (exit.after !== after) continue;
|
|
309
|
+
out.push(paintNode(exit.node, now));
|
|
310
|
+
place(key);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (parent === null) place(undefined);
|
|
314
|
+
for (const node of list) {
|
|
315
|
+
out.push(paintNode(node, now));
|
|
316
|
+
place(node.key);
|
|
317
|
+
}
|
|
318
|
+
if (guides.size === 0) return out;
|
|
319
|
+
const fading = [...guides.values()].map(
|
|
320
|
+
(motion) =>
|
|
321
|
+
[
|
|
322
|
+
motion.guide,
|
|
323
|
+
Math.max(0, motion.generator.at(elapsed(motion, now)).value),
|
|
324
|
+
] as const,
|
|
325
|
+
);
|
|
326
|
+
return withLeaving(out, parent, fading);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function prune(now: number) {
|
|
330
|
+
for (const motions of [numbers, strings, guides]) {
|
|
331
|
+
for (const [key, motion] of motions) {
|
|
332
|
+
if (done(motion, now)) motions.delete(key);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const moving = new Set(
|
|
336
|
+
[...numbers.keys(), ...strings.keys()].map(
|
|
337
|
+
(key) => key.split(SEPARATOR)[0],
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
for (const key of shapes.keys()) {
|
|
341
|
+
if (!moving.has(key)) shapes.delete(key);
|
|
342
|
+
}
|
|
343
|
+
for (const key of leaving.keys()) {
|
|
344
|
+
if (!moving.has(key)) leaving.delete(key);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
179
348
|
return {
|
|
180
349
|
retarget(scene, now) {
|
|
181
|
-
const before =
|
|
182
|
-
const after =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
350
|
+
const before = index(target.nodes);
|
|
351
|
+
const after = index(scene.nodes);
|
|
352
|
+
function painted(key: string): SceneNode | undefined {
|
|
353
|
+
const node =
|
|
354
|
+
before.byKey.get(key) ??
|
|
355
|
+
leaving.get(key)?.node ??
|
|
356
|
+
guides.get(key)?.guide.node;
|
|
357
|
+
return node && paintNode(node, now);
|
|
358
|
+
}
|
|
359
|
+
// Read before the exits below drop what the guides were showing.
|
|
360
|
+
for (const guide of leavingGuides(target.nodes, (key) =>
|
|
361
|
+
after.byKey.has(key),
|
|
362
|
+
)) {
|
|
363
|
+
guides.set(guide.node.key, {
|
|
364
|
+
generator: generator(value(guide.node.key, OPACITY, 1, now), 0, 0),
|
|
365
|
+
start: now,
|
|
366
|
+
guide,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
for (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {
|
|
370
|
+
if (after.byKey.has(key)) continue;
|
|
371
|
+
const base = (before.byKey.get(key) ??
|
|
372
|
+
leaving.get(key)?.node) as SceneNode;
|
|
373
|
+
const siblings =
|
|
374
|
+
before.siblings.get(key) ?? leaving.get(key)?.siblings ?? [];
|
|
375
|
+
const goal = exitTo(base, siblings, after.byKey);
|
|
376
|
+
const from = painted(key);
|
|
377
|
+
if (!goal || !from) {
|
|
378
|
+
drop(key);
|
|
379
|
+
continue;
|
|
193
380
|
}
|
|
381
|
+
move(from, goal, now, now);
|
|
382
|
+
const at = siblings.findIndex((node) => node.key === key);
|
|
383
|
+
leaving.set(key, {
|
|
384
|
+
node: goal,
|
|
385
|
+
after: siblings[at - 1]?.key,
|
|
386
|
+
siblings,
|
|
387
|
+
});
|
|
194
388
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
201
|
-
|
|
389
|
+
const marks = [...after.byKey.values()].filter(
|
|
390
|
+
(node) => node.role === "mark",
|
|
391
|
+
);
|
|
392
|
+
const order = new Map(marks.map((node, at) => [node.key, at]));
|
|
393
|
+
for (const node of after.byKey.values()) {
|
|
394
|
+
if (node.kind === "group") continue;
|
|
395
|
+
const start =
|
|
396
|
+
now +
|
|
397
|
+
staggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;
|
|
398
|
+
const old = painted(node.key);
|
|
399
|
+
leaving.delete(node.key);
|
|
400
|
+
const from =
|
|
401
|
+
old ??
|
|
402
|
+
enterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);
|
|
403
|
+
if (from) move(from, node, start, now);
|
|
404
|
+
const fading = numbers.get(channelKey(node.key, OPACITY));
|
|
405
|
+
const returning = guides.get(node.key);
|
|
406
|
+
if ((!from && node.enter !== "none") || fading || returning) {
|
|
407
|
+
const shown = returning
|
|
408
|
+
? Math.max(0, returning.generator.at(elapsed(returning, now)).value)
|
|
409
|
+
: fading
|
|
410
|
+
? value(node.key, OPACITY, 1, now)
|
|
411
|
+
: 0;
|
|
412
|
+
spring1(node.key, OPACITY, shown, 1, start, now);
|
|
413
|
+
}
|
|
202
414
|
}
|
|
415
|
+
for (const key of after.byKey.keys()) guides.delete(key);
|
|
203
416
|
target = scene;
|
|
204
417
|
},
|
|
418
|
+
introduce(now) {
|
|
419
|
+
let any = false;
|
|
420
|
+
walk(target.nodes, (node) => {
|
|
421
|
+
if (node.kind !== "path" || node.enter !== "draw") return;
|
|
422
|
+
if (node.geometry?.kind !== "points" || node.paint.strokeDasharray) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
spring1(node.key, DRAWN, 0, 1, now, now);
|
|
426
|
+
any = true;
|
|
427
|
+
});
|
|
428
|
+
return any;
|
|
429
|
+
},
|
|
205
430
|
snap(scene) {
|
|
206
|
-
numbers
|
|
207
|
-
|
|
431
|
+
for (const motions of [numbers, strings, guides, shapes, leaving]) {
|
|
432
|
+
motions.clear();
|
|
433
|
+
}
|
|
208
434
|
target = scene;
|
|
209
435
|
},
|
|
210
436
|
frame(now) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
for (const [key, motion] of paths) {
|
|
215
|
-
if (done(motion, now)) paths.delete(key);
|
|
216
|
-
}
|
|
217
|
-
if (numbers.size === 0 && paths.size === 0) return target;
|
|
218
|
-
return { ...target, nodes: target.nodes.map((node) => paint(node, now)) };
|
|
437
|
+
prune(now);
|
|
438
|
+
if (numbers.size + strings.size + guides.size === 0) return target;
|
|
439
|
+
return { ...target, nodes: paintList(target.nodes, null, now) };
|
|
219
440
|
},
|
|
220
441
|
settled(now) {
|
|
221
|
-
return [
|
|
222
|
-
|
|
223
|
-
|
|
442
|
+
return [
|
|
443
|
+
...numbers.values(),
|
|
444
|
+
...strings.values(),
|
|
445
|
+
...guides.values(),
|
|
446
|
+
].every((motion) => done(motion, now));
|
|
224
447
|
},
|
|
225
448
|
};
|
|
226
449
|
}
|