@voila.dev/chart 6.0.120 → 6.0.121
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/core/motion/motion-store.d.ts.map +1 -1
- package/dist/core/motion/motion-store.js +28 -42
- package/dist/core/motion/motion-store.js.map +2 -2
- package/package.json +2 -2
- package/src/core/motion/motion-store.ts +48 -50
- package/dist/core/motion/leaving.d.ts +0 -12
- package/dist/core/motion/leaving.d.ts.map +0 -1
- package/dist/core/motion/leaving.js +0 -26
- package/dist/core/motion/leaving.js.map +0 -7
- package/src/core/motion/leaving.ts +0 -49
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion-store.d.ts","sourceRoot":"","sources":["../../../src/core/motion/motion-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"motion-store.d.ts","sourceRoot":"","sources":["../../../src/core/motion/motion-store.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EACX,UAAU,EAIV,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,8FAA8F;IAC9F,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,qDAAqD;IACrD,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;IAC/B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAiGD,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,WAAW,GACjB,WAAW,CA4Qb"}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { enterFrom, exitTo } from "./enter-exit.js";
|
|
2
|
-
import {
|
|
3
|
-
leavingGuides,
|
|
4
|
-
withLeaving
|
|
5
|
-
} from "./leaving.js";
|
|
6
2
|
import {
|
|
7
3
|
STRING_CHANNELS,
|
|
8
4
|
stringMixer,
|
|
@@ -21,20 +17,28 @@ const FIELDS = {
|
|
|
21
17
|
const OPACITY = "opacity";
|
|
22
18
|
const DRAWN = "drawn";
|
|
23
19
|
const SEPARATOR = "\0";
|
|
24
|
-
|
|
20
|
+
const GUIDE_ROLES = /* @__PURE__ */ new Set(["grid", "axis"]);
|
|
21
|
+
function first(parent) {
|
|
22
|
+
return parent === null ? void 0 : parent + SEPARATOR;
|
|
23
|
+
}
|
|
24
|
+
function walk(nodes, visit, parent = null) {
|
|
25
|
+
let after = first(parent);
|
|
25
26
|
for (const node of nodes) {
|
|
26
|
-
visit(node, nodes);
|
|
27
|
-
if (node.kind === "group") walk(node.children, visit);
|
|
27
|
+
visit(node, nodes, after);
|
|
28
|
+
if (node.kind === "group") walk(node.children, visit, node.key);
|
|
29
|
+
after = node.key;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
function index(nodes) {
|
|
31
33
|
const byKey = /* @__PURE__ */ new Map();
|
|
32
34
|
const siblings = /* @__PURE__ */ new Map();
|
|
33
|
-
|
|
35
|
+
const afters = /* @__PURE__ */ new Map();
|
|
36
|
+
walk(nodes, (node, list, after) => {
|
|
34
37
|
byKey.set(node.key, node);
|
|
35
38
|
siblings.set(node.key, list);
|
|
39
|
+
afters.set(node.key, after);
|
|
36
40
|
});
|
|
37
|
-
return { byKey, siblings };
|
|
41
|
+
return { byKey, siblings, afters };
|
|
38
42
|
}
|
|
39
43
|
function channelKey(node, channel) {
|
|
40
44
|
return `${node}${SEPARATOR}${channel}`;
|
|
@@ -54,7 +58,6 @@ function clamp(value) {
|
|
|
54
58
|
function createMotionStore(initial, timing) {
|
|
55
59
|
let target = initial;
|
|
56
60
|
const channels = /* @__PURE__ */ new Map();
|
|
57
|
-
const guides = /* @__PURE__ */ new Map();
|
|
58
61
|
const shapes = /* @__PURE__ */ new Map();
|
|
59
62
|
const leaving = /* @__PURE__ */ new Map();
|
|
60
63
|
function value(node, channel, fallback, now) {
|
|
@@ -178,18 +181,12 @@ function createMotionStore(initial, timing) {
|
|
|
178
181
|
place(key);
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
|
-
|
|
184
|
+
place(first(parent));
|
|
182
185
|
for (const node of list) {
|
|
183
186
|
out.push(paintNode(node, now));
|
|
184
187
|
place(node.key);
|
|
185
188
|
}
|
|
186
|
-
|
|
187
|
-
return withLeaving(
|
|
188
|
-
out,
|
|
189
|
-
parent,
|
|
190
|
-
guides.values(),
|
|
191
|
-
(node) => paintNode(node, now)
|
|
192
|
-
);
|
|
189
|
+
return out;
|
|
193
190
|
}
|
|
194
191
|
function prune(now) {
|
|
195
192
|
for (const [key, motion] of channels) {
|
|
@@ -198,7 +195,7 @@ function createMotionStore(initial, timing) {
|
|
|
198
195
|
const moving = new Set(
|
|
199
196
|
[...channels.keys()].map((key) => key.split(SEPARATOR)[0])
|
|
200
197
|
);
|
|
201
|
-
for (const byNode of [shapes, leaving
|
|
198
|
+
for (const byNode of [shapes, leaving]) {
|
|
202
199
|
for (const key of byNode.keys()) {
|
|
203
200
|
if (!moving.has(key)) byNode.delete(key);
|
|
204
201
|
}
|
|
@@ -209,40 +206,31 @@ function createMotionStore(initial, timing) {
|
|
|
209
206
|
const before = index(target.nodes);
|
|
210
207
|
const after = index(scene.nodes);
|
|
211
208
|
function painted(key) {
|
|
212
|
-
const node = before.byKey.get(key) ?? leaving.get(key)?.node
|
|
209
|
+
const node = before.byKey.get(key) ?? leaving.get(key)?.node;
|
|
213
210
|
return node && paintNode(node, now);
|
|
214
211
|
}
|
|
215
|
-
const fadingGuides = leavingGuides(
|
|
216
|
-
target.nodes,
|
|
217
|
-
(key) => after.byKey.has(key)
|
|
218
|
-
).map(
|
|
219
|
-
(guide) => [guide, value(guide.node.key, OPACITY, 1, now)]
|
|
220
|
-
);
|
|
221
212
|
for (const key of /* @__PURE__ */ new Set([...before.byKey.keys(), ...leaving.keys()])) {
|
|
222
213
|
if (after.byKey.has(key)) continue;
|
|
223
214
|
const base = before.byKey.get(key) ?? leaving.get(key)?.node;
|
|
224
|
-
const
|
|
225
|
-
const
|
|
215
|
+
const exit = leaving.get(key);
|
|
216
|
+
const siblings = exit?.siblings ?? before.siblings.get(key) ?? [];
|
|
217
|
+
const guide = base.kind !== "group" && GUIDE_ROLES.has(base.role ?? "");
|
|
218
|
+
const goal = exitTo(base, siblings, after.byKey) ?? (guide && base);
|
|
226
219
|
const from = painted(key);
|
|
227
220
|
if (!goal || !from) {
|
|
228
221
|
drop(key);
|
|
229
222
|
continue;
|
|
230
223
|
}
|
|
231
224
|
move(from, goal, now, now);
|
|
232
|
-
|
|
225
|
+
if (guide) {
|
|
226
|
+
spring1(key, OPACITY, value(key, OPACITY, 1, now), 0, now, now);
|
|
227
|
+
}
|
|
233
228
|
leaving.set(key, {
|
|
234
229
|
node: goal,
|
|
235
|
-
after:
|
|
230
|
+
after: exit ? exit.after : before.afters.get(key),
|
|
236
231
|
siblings
|
|
237
232
|
});
|
|
238
233
|
}
|
|
239
|
-
for (const [guide, shown] of fadingGuides) {
|
|
240
|
-
guides.set(guide.node.key, guide);
|
|
241
|
-
channels.set(channelKey(guide.node.key, OPACITY), {
|
|
242
|
-
generator: timing.motion(shown, 0, 0),
|
|
243
|
-
start: now
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
234
|
const marks = [...after.byKey.values()].filter(
|
|
247
235
|
(node) => node.role === "mark"
|
|
248
236
|
);
|
|
@@ -251,18 +239,16 @@ function createMotionStore(initial, timing) {
|
|
|
251
239
|
if (node.kind === "group") continue;
|
|
252
240
|
const start = now + staggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1e3;
|
|
253
241
|
const old = painted(node.key);
|
|
254
|
-
leaving.delete(node.key);
|
|
242
|
+
const returning = leaving.delete(node.key);
|
|
255
243
|
const from = old ?? enterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);
|
|
256
244
|
if (from) move(from, node, start, now);
|
|
257
245
|
const fading = channels.get(channelKey(node.key, OPACITY));
|
|
258
246
|
if (!from && node.enter !== "none" || fading) {
|
|
259
247
|
const shown = fading ? Math.max(0, sample(fading, now).value) : 0;
|
|
260
|
-
if (
|
|
261
|
-
channels.delete(channelKey(node.key, OPACITY));
|
|
248
|
+
if (returning) channels.delete(channelKey(node.key, OPACITY));
|
|
262
249
|
spring1(node.key, OPACITY, shown, 1, start, now);
|
|
263
250
|
}
|
|
264
251
|
}
|
|
265
|
-
for (const key of after.byKey.keys()) guides.delete(key);
|
|
266
252
|
target = scene;
|
|
267
253
|
},
|
|
268
254
|
introduce(now) {
|
|
@@ -278,7 +264,7 @@ function createMotionStore(initial, timing) {
|
|
|
278
264
|
return any;
|
|
279
265
|
},
|
|
280
266
|
snap(scene) {
|
|
281
|
-
for (const motions of [channels,
|
|
267
|
+
for (const motions of [channels, shapes, leaving]) {
|
|
282
268
|
motions.clear();
|
|
283
269
|
}
|
|
284
270
|
target = scene;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/motion/motion-store.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Generator } from \"@voila.dev/motion\";\nimport { enterFrom, exitTo } from \"#/core/motion/enter-exit.ts\";\nimport {\n\ttype LeavingGuide,\n\tleavingGuides,\n\twithLeaving,\n} from \"#/core/motion/leaving.ts\";\nimport {\n\tSTRING_CHANNELS,\n\ttype StringChannel,\n\tstringMixer,\n\tstringOf,\n\twithString,\n} from \"#/core/motion/strings.ts\";\nimport { type ChartTiming, staggerDelay } from \"#/core/motion/timing.ts\";\nimport type {\n\tChartScene,\n\tGeometryMotion,\n\tGeometryPlan,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * Every moving number of a chart, as springs keyed by node and channel. A\n * new scene retargets them: each channel starts from where it is painted, at\n * the speed it has, so an update landing mid-flight bends the motion instead\n * of kinking it. Pure and clocked from outside (milliseconds), so the SVG and\n * Canvas renderers move identically and tests step it by hand. Hit-testing\n * and focus never read it: they read the scene it moves towards, which never\n * holds the marks collapsing or the guides fading on their way out.\n */\nexport interface MotionStore {\n\tretarget(scene: ChartScene, now: number): void;\n\t/** The first render: lines that `enter: \"draw\"` trace themselves in. False when none does. */\n\tintroduce(now: number): boolean;\n\t/** Jumps to `scene`: a resize, or reduced motion. */\n\tsnap(scene: ChartScene): void;\n\tframe(now: number): ChartScene;\n\tsettled(now: number): boolean;\n}\n\ninterface Motion {\n\treadonly generator: Generator;\n\t/** Milliseconds, the delay included. */\n\treadonly start: number;\n\t/** A path or a colour: the generator runs a progress from 0 to 1, and this reads it. */\n\treadonly mix?: (progress: number) => string;\n}\n\n/** A data mark collapsing on its way out: drawn after its old sibling until its springs settle. */\ninterface Leaving {\n\treadonly node: SceneNode;\n\treadonly after: string | undefined;\n\treadonly siblings: readonly SceneNode[];\n}\n\ntype Numeric = Record<string, number>;\n\nconst FIELDS: Record<SceneNode[\"kind\"], readonly string[]> = {\n\trect: [\"x\", \"y\", \"width\", \"height\"],\n\tcircle: [\"cx\", \"cy\", \"r\"],\n\tline: [\"x1\", \"y1\", \"x2\", \"y2\"],\n\ttext: [\"x\", \"y\"],\n\tpath: [],\n\tgroup: [],\n};\n\nconst OPACITY = \"opacity\";\nconst DRAWN = \"drawn\";\nconst SEPARATOR = \"\\u0000\";\n\nfunction walk(\n\tnodes: readonly SceneNode[],\n\tvisit: (node: SceneNode, siblings: readonly SceneNode[]) => void,\n) {\n\tfor (const node of nodes) {\n\t\tvisit(node, nodes);\n\t\tif (node.kind === \"group\") walk(node.children, visit);\n\t}\n}\n\nfunction index(nodes: readonly SceneNode[]) {\n\tconst byKey = new Map<string, SceneNode>();\n\tconst siblings = new Map<string, readonly SceneNode[]>();\n\twalk(nodes, (node, list) => {\n\t\tbyKey.set(node.key, node);\n\t\tsiblings.set(node.key, list);\n\t});\n\treturn { byKey, siblings };\n}\n\nfunction channelKey(node: string, channel: string): string {\n\treturn `${node}${SEPARATOR}${channel}`;\n}\n\nfunction elapsed(motion: Motion, now: number): number {\n\treturn Math.max(0, (now - motion.start) / 1000);\n}\n\nfunction sample(motion: Motion, now: number) {\n\treturn motion.generator.at(elapsed(motion, now));\n}\n\nfunction done(motion: Motion, now: number): boolean {\n\treturn elapsed(motion, now) >= motion.generator.duration;\n}\n\nfunction clamp(value: number): number {\n\treturn Math.min(1, Math.max(0, value));\n}\n\nexport function createMotionStore(\n\tinitial: ChartScene,\n\ttiming: ChartTiming,\n): MotionStore {\n\tlet target = initial;\n\tconst channels = new Map<string, Motion>();\n\t/** Fading out on their opacity channel. */\n\tconst guides = new Map<string, LeavingGuide>();\n\tconst shapes = new Map<\n\t\tstring,\n\t\t{ readonly plan: GeometryPlan; readonly motion: GeometryMotion }\n\t>();\n\tconst leaving = new Map<string, Leaving>();\n\n\tfunction value(node: string, channel: string, fallback: number, now: number) {\n\t\tconst motion = channels.get(channelKey(node, channel));\n\t\treturn motion ? sample(motion, now).value : fallback;\n\t}\n\n\t/** Springs one channel from `from` to `to`, at the speed it already has. */\n\tfunction spring1(\n\t\tnode: string,\n\t\tchannel: string,\n\t\tfrom: number,\n\t\tto: number,\n\t\tstart: number,\n\t\tnow: number,\n\t) {\n\t\tconst key = channelKey(node, channel);\n\t\tconst running = channels.get(key);\n\t\tconst velocity = running ? sample(running, now).velocity : 0;\n\t\tchannels.delete(key);\n\t\tif (from === to && velocity === 0) return;\n\t\tchannels.set(key, { generator: timing.motion(from, to, velocity), start });\n\t}\n\n\t/** Mixes one string channel from what is painted to what `to` asks for. */\n\tfunction mixString(\n\t\tfrom: SceneNode,\n\t\tto: SceneNode,\n\t\tchannel: StringChannel,\n\t\tstart: number,\n\t) {\n\t\tconst key = channelKey(to.key, channel);\n\t\tchannels.delete(key);\n\t\tconst was = stringOf(from, channel);\n\t\tconst goal = stringOf(to, channel);\n\t\tif (was === undefined || goal === undefined || was === goal) return;\n\t\tchannels.set(key, {\n\t\t\tgenerator: timing.motion(0, 1, 0),\n\t\t\tstart,\n\t\t\tmix: stringMixer(to, channel, was, goal),\n\t\t});\n\t}\n\n\tfunction drop(node: string) {\n\t\tfor (const key of channels.keys()) {\n\t\t\tif (key.startsWith(node + SEPARATOR)) channels.delete(key);\n\t\t}\n\t\tshapes.delete(node);\n\t\tleaving.delete(node);\n\t}\n\n\t/** `from` is the node as painted now (or the shape it enters from), `to` where it goes. */\n\tfunction move(from: SceneNode, to: SceneNode, start: number, now: number) {\n\t\tconst key = to.key;\n\t\tfor (const field of FIELDS[to.kind]) {\n\t\t\tconst was = (from as unknown as Numeric)[field] as number;\n\t\t\tspring1(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\twas,\n\t\t\t\t(to as unknown as Numeric)[field] as number,\n\t\t\t\tstart,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tmixString(from, to, \"fill\", start);\n\t\tmixString(from, to, \"stroke\", start);\n\t\tif (to.kind !== \"path\" || from.kind !== \"path\") return;\n\t\tconst previous = shapes.get(key);\n\t\tshapes.delete(key);\n\t\tchannels.delete(channelKey(key, \"d\"));\n\t\tconst motion = to.motion;\n\t\tconst plan =\n\t\t\tmotion && from.geometry && to.geometry\n\t\t\t\t? motion.plan(from.geometry, to.geometry)\n\t\t\t\t: undefined;\n\t\tif (!plan) {\n\t\t\t// No meaning to move: the node's own morph, else the numbers of `d`.\n\t\t\tmixString(from, to, \"d\", start);\n\t\t\treturn;\n\t\t}\n\t\tfor (const channel of previous?.plan.targets.keys() ?? []) {\n\t\t\tif (!plan.targets.has(channel)) {\n\t\t\t\tchannels.delete(channelKey(key, `g${channel}`));\n\t\t\t}\n\t\t}\n\t\tfor (const [channel, goal] of plan.targets) {\n\t\t\tconst was = plan.starts.get(channel) ?? goal;\n\t\t\tspring1(key, `g${channel}`, was, goal, start, now);\n\t\t}\n\t\tshapes.set(key, { plan, motion: motion as GeometryMotion });\n\t}\n\n\tfunction paintNode(node: SceneNode, now: number): SceneNode {\n\t\tif (node.kind === \"group\") {\n\t\t\treturn { ...node, children: paintList(node.children, node.key, now) };\n\t\t}\n\t\tconst key = node.key;\n\t\tconst moved: Numeric = {};\n\t\tfor (const field of FIELDS[node.kind]) {\n\t\t\tmoved[field] = value(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\t(node as unknown as Numeric)[field] as number,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tlet next = { ...node, ...moved } as Exclude<SceneNode, { kind: \"group\" }>;\n\t\tconst shape = shapes.get(key);\n\t\tif (next.kind === \"path\" && shape) {\n\t\t\tconst geometry = shape.plan.build((channel) =>\n\t\t\t\tvalue(key, `g${channel}`, shape.plan.targets.get(channel) ?? 0, now),\n\t\t\t);\n\t\t\tnext = { ...next, geometry, d: shape.motion.draw(geometry) };\n\t\t}\n\t\tfor (const channel of STRING_CHANNELS) {\n\t\t\tconst motion = channels.get(channelKey(key, channel));\n\t\t\tif (motion?.mix) {\n\t\t\t\tnext = withString(next, channel, motion.mix(sample(motion, now).value));\n\t\t\t}\n\t\t}\n\t\tconst opacity = value(key, OPACITY, 1, now);\n\t\tconst drawn = value(key, DRAWN, 1, now);\n\t\tif (opacity === 1 && drawn === 1) return next;\n\t\treturn {\n\t\t\t...next,\n\t\t\tpaint: {\n\t\t\t\t...next.paint,\n\t\t\t\topacity: (next.paint.opacity ?? 1) * clamp(opacity),\n\t\t\t\t...(drawn < 1 &&\n\t\t\t\t\tnext.kind === \"path\" &&\n\t\t\t\t\tnext.geometry && {\n\t\t\t\t\t\tdrawn: {\n\t\t\t\t\t\t\tfraction: clamp(drawn),\n\t\t\t\t\t\t\tlength: next.motion?.length?.(next.geometry) ?? 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t} as SceneNode;\n\t}\n\n\t/**\n\t * The painted list, with the marks still collapsing after the sibling they\n\t * followed, and the guides still fading back at their places.\n\t */\n\tfunction paintList(\n\t\tlist: readonly SceneNode[],\n\t\tparent: string | null,\n\t\tnow: number,\n\t): SceneNode[] {\n\t\tconst out: SceneNode[] = [];\n\t\tfunction place(after: string | undefined) {\n\t\t\tfor (const [key, exit] of leaving) {\n\t\t\t\tif (exit.after !== after) continue;\n\t\t\t\tout.push(paintNode(exit.node, now));\n\t\t\t\tplace(key);\n\t\t\t}\n\t\t}\n\t\tif (parent === null) place(undefined);\n\t\tfor (const node of list) {\n\t\t\tout.push(paintNode(node, now));\n\t\t\tplace(node.key);\n\t\t}\n\t\tif (guides.size === 0) return out;\n\t\treturn withLeaving(out, parent, guides.values(), (node) =>\n\t\t\tpaintNode(node, now),\n\t\t);\n\t}\n\n\tfunction prune(now: number) {\n\t\tfor (const [key, motion] of channels) {\n\t\t\tif (done(motion, now)) channels.delete(key);\n\t\t}\n\t\tconst moving = new Set(\n\t\t\t[...channels.keys()].map((key) => key.split(SEPARATOR)[0]),\n\t\t);\n\t\tfor (const byNode of [shapes, leaving, guides]) {\n\t\t\tfor (const key of byNode.keys()) {\n\t\t\t\tif (!moving.has(key)) byNode.delete(key);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tretarget(scene, now) {\n\t\t\tconst before = index(target.nodes);\n\t\t\tconst after = index(scene.nodes);\n\t\t\tfunction painted(key: string): SceneNode | undefined {\n\t\t\t\tconst node =\n\t\t\t\t\tbefore.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node ??\n\t\t\t\t\tguides.get(key)?.node;\n\t\t\t\treturn node && paintNode(node, now);\n\t\t\t}\n\t\t\t// Read before the exits below drop what the guides were showing.\n\t\t\tconst fadingGuides = leavingGuides(target.nodes, (key) =>\n\t\t\t\tafter.byKey.has(key),\n\t\t\t).map(\n\t\t\t\t(guide) => [guide, value(guide.node.key, OPACITY, 1, now)] as const,\n\t\t\t);\n\t\t\tfor (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {\n\t\t\t\tif (after.byKey.has(key)) continue;\n\t\t\t\tconst base = (before.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node) as SceneNode;\n\t\t\t\tconst siblings =\n\t\t\t\t\tbefore.siblings.get(key) ?? leaving.get(key)?.siblings ?? [];\n\t\t\t\tconst goal = exitTo(base, siblings, after.byKey);\n\t\t\t\tconst from = painted(key);\n\t\t\t\tif (!goal || !from) {\n\t\t\t\t\tdrop(key);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmove(from, goal, now, now);\n\t\t\t\tconst at = siblings.findIndex((node) => node.key === key);\n\t\t\t\tleaving.set(key, {\n\t\t\t\t\tnode: goal,\n\t\t\t\t\tafter: siblings[at - 1]?.key,\n\t\t\t\t\tsiblings,\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const [guide, shown] of fadingGuides) {\n\t\t\t\tguides.set(guide.node.key, guide);\n\t\t\t\tchannels.set(channelKey(guide.node.key, OPACITY), {\n\t\t\t\t\tgenerator: timing.motion(shown, 0, 0),\n\t\t\t\t\tstart: now,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst marks = [...after.byKey.values()].filter(\n\t\t\t\t(node) => node.role === \"mark\",\n\t\t\t);\n\t\t\tconst order = new Map(marks.map((node, at) => [node.key, at]));\n\t\t\tfor (const node of after.byKey.values()) {\n\t\t\t\tif (node.kind === \"group\") continue;\n\t\t\t\tconst start =\n\t\t\t\t\tnow +\n\t\t\t\t\tstaggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;\n\t\t\t\tconst old = painted(node.key);\n\t\t\t\tleaving.delete(node.key);\n\t\t\t\tconst from =\n\t\t\t\t\told ??\n\t\t\t\t\tenterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);\n\t\t\t\tif (from) move(from, node, start, now);\n\t\t\t\tconst fading = channels.get(channelKey(node.key, OPACITY));\n\t\t\t\tif ((!from && node.enter !== \"none\") || fading) {\n\t\t\t\t\tconst shown = fading ? Math.max(0, sample(fading, now).value) : 0;\n\t\t\t\t\t// A guide coming back starts from rest: its fade-out speed points the wrong way.\n\t\t\t\t\tif (guides.has(node.key))\n\t\t\t\t\t\tchannels.delete(channelKey(node.key, OPACITY));\n\t\t\t\t\tspring1(node.key, OPACITY, shown, 1, start, now);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const key of after.byKey.keys()) guides.delete(key);\n\t\t\ttarget = scene;\n\t\t},\n\t\tintroduce(now) {\n\t\t\tlet any = false;\n\t\t\twalk(target.nodes, (node) => {\n\t\t\t\tif (node.kind !== \"path\" || node.enter !== \"draw\") return;\n\t\t\t\tif (node.geometry?.kind !== \"points\" || node.paint.strokeDasharray) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tspring1(node.key, DRAWN, 0, 1, now, now);\n\t\t\t\tany = true;\n\t\t\t});\n\t\t\treturn any;\n\t\t},\n\t\tsnap(scene) {\n\t\t\tfor (const motions of [channels, guides, shapes, leaving]) {\n\t\t\t\tmotions.clear();\n\t\t\t}\n\t\t\ttarget = scene;\n\t\t},\n\t\tframe(now) {\n\t\t\tprune(now);\n\t\t\tif (channels.size === 0) return target;\n\t\t\treturn { ...target, nodes: paintList(target.nodes, null, now) };\n\t\t},\n\t\tsettled(now) {\n\t\t\treturn [...channels.values()].every((motion) => done(motion, now));\n\t\t},\n\t};\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,WAAW,cAAc;AAClC;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { Generator } from \"@voila.dev/motion\";\nimport { enterFrom, exitTo } from \"#/core/motion/enter-exit.ts\";\nimport {\n\tSTRING_CHANNELS,\n\ttype StringChannel,\n\tstringMixer,\n\tstringOf,\n\twithString,\n} from \"#/core/motion/strings.ts\";\nimport { type ChartTiming, staggerDelay } from \"#/core/motion/timing.ts\";\nimport type {\n\tChartScene,\n\tGeometryMotion,\n\tGeometryPlan,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * Every moving number of a chart, as springs keyed by node and channel. A\n * new scene retargets them: each channel starts from where it is painted, at\n * the speed it has, so an update landing mid-flight bends the motion instead\n * of kinking it. Pure and clocked from outside (milliseconds), so the SVG and\n * Canvas renderers move identically and tests step it by hand. Hit-testing\n * and focus never read it: they read the scene it moves towards, which never\n * holds the marks collapsing or the guides fading on their way out.\n */\nexport interface MotionStore {\n\tretarget(scene: ChartScene, now: number): void;\n\t/** The first render: lines that `enter: \"draw\"` trace themselves in. False when none does. */\n\tintroduce(now: number): boolean;\n\t/** Jumps to `scene`: a resize, or reduced motion. */\n\tsnap(scene: ChartScene): void;\n\tframe(now: number): ChartScene;\n\tsettled(now: number): boolean;\n}\n\ninterface Motion {\n\treadonly generator: Generator;\n\t/** Milliseconds, the delay included. */\n\treadonly start: number;\n\t/** A path or a colour: the generator runs a progress from 0 to 1, and this reads it. */\n\treadonly mix?: (progress: number) => string;\n}\n\n/**\n * A node on its way out, drawn after its old sibling until its springs\n * settle: a data mark collapsing, or a guide fading where it stood.\n */\ninterface Leaving {\n\treadonly node: SceneNode;\n\t/** Its old previous sibling's key, else its group's `first` marker. */\n\treadonly after: string | undefined;\n\treadonly siblings: readonly SceneNode[];\n}\n\ntype Numeric = Record<string, number>;\n\nconst FIELDS: Record<SceneNode[\"kind\"], readonly string[]> = {\n\trect: [\"x\", \"y\", \"width\", \"height\"],\n\tcircle: [\"cx\", \"cy\", \"r\"],\n\tline: [\"x1\", \"y1\", \"x2\", \"y2\"],\n\ttext: [\"x\", \"y\"],\n\tpath: [],\n\tgroup: [],\n};\n\nconst OPACITY = \"opacity\";\nconst DRAWN = \"drawn\";\nconst SEPARATOR = \"\\u0000\";\n/**\n * A tick label or a grid line vanishing at once reads as a glitch, so they\n * fade out. Data marks never linger: a bar the data no longer has would show\n * a value that isn't true.\n */\nconst GUIDE_ROLES = new Set([\"grid\", \"axis\"]);\n\n/** Where the leaving first children of the group `parent` are drawn. */\nfunction first(parent: string | null): string | undefined {\n\treturn parent === null ? undefined : parent + SEPARATOR;\n}\n\n/** Visits every node with its list and the key it is drawn after when it leaves (see `Leaving`). */\nfunction walk(\n\tnodes: readonly SceneNode[],\n\tvisit: (\n\t\tnode: SceneNode,\n\t\tsiblings: readonly SceneNode[],\n\t\tafter: string | undefined,\n\t) => void,\n\tparent: string | null = null,\n) {\n\tlet after = first(parent);\n\tfor (const node of nodes) {\n\t\tvisit(node, nodes, after);\n\t\tif (node.kind === \"group\") walk(node.children, visit, node.key);\n\t\tafter = node.key;\n\t}\n}\n\nfunction index(nodes: readonly SceneNode[]) {\n\tconst byKey = new Map<string, SceneNode>();\n\tconst siblings = new Map<string, readonly SceneNode[]>();\n\tconst afters = new Map<string, string | undefined>();\n\twalk(nodes, (node, list, after) => {\n\t\tbyKey.set(node.key, node);\n\t\tsiblings.set(node.key, list);\n\t\tafters.set(node.key, after);\n\t});\n\treturn { byKey, siblings, afters };\n}\n\nfunction channelKey(node: string, channel: string): string {\n\treturn `${node}${SEPARATOR}${channel}`;\n}\n\nfunction elapsed(motion: Motion, now: number): number {\n\treturn Math.max(0, (now - motion.start) / 1000);\n}\n\nfunction sample(motion: Motion, now: number) {\n\treturn motion.generator.at(elapsed(motion, now));\n}\n\nfunction done(motion: Motion, now: number): boolean {\n\treturn elapsed(motion, now) >= motion.generator.duration;\n}\n\nfunction clamp(value: number): number {\n\treturn Math.min(1, Math.max(0, value));\n}\n\nexport function createMotionStore(\n\tinitial: ChartScene,\n\ttiming: ChartTiming,\n): MotionStore {\n\tlet target = initial;\n\tconst channels = new Map<string, Motion>();\n\tconst shapes = new Map<\n\t\tstring,\n\t\t{ readonly plan: GeometryPlan; readonly motion: GeometryMotion }\n\t>();\n\tconst leaving = new Map<string, Leaving>();\n\n\tfunction value(node: string, channel: string, fallback: number, now: number) {\n\t\tconst motion = channels.get(channelKey(node, channel));\n\t\treturn motion ? sample(motion, now).value : fallback;\n\t}\n\n\t/** Springs one channel from `from` to `to`, at the speed it already has. */\n\tfunction spring1(\n\t\tnode: string,\n\t\tchannel: string,\n\t\tfrom: number,\n\t\tto: number,\n\t\tstart: number,\n\t\tnow: number,\n\t) {\n\t\tconst key = channelKey(node, channel);\n\t\tconst running = channels.get(key);\n\t\tconst velocity = running ? sample(running, now).velocity : 0;\n\t\tchannels.delete(key);\n\t\tif (from === to && velocity === 0) return;\n\t\tchannels.set(key, { generator: timing.motion(from, to, velocity), start });\n\t}\n\n\t/** Mixes one string channel from what is painted to what `to` asks for. */\n\tfunction mixString(\n\t\tfrom: SceneNode,\n\t\tto: SceneNode,\n\t\tchannel: StringChannel,\n\t\tstart: number,\n\t) {\n\t\tconst key = channelKey(to.key, channel);\n\t\tchannels.delete(key);\n\t\tconst was = stringOf(from, channel);\n\t\tconst goal = stringOf(to, channel);\n\t\tif (was === undefined || goal === undefined || was === goal) return;\n\t\tchannels.set(key, {\n\t\t\tgenerator: timing.motion(0, 1, 0),\n\t\t\tstart,\n\t\t\tmix: stringMixer(to, channel, was, goal),\n\t\t});\n\t}\n\n\tfunction drop(node: string) {\n\t\tfor (const key of channels.keys()) {\n\t\t\tif (key.startsWith(node + SEPARATOR)) channels.delete(key);\n\t\t}\n\t\tshapes.delete(node);\n\t\tleaving.delete(node);\n\t}\n\n\t/** `from` is the node as painted now (or the shape it enters from), `to` where it goes. */\n\tfunction move(from: SceneNode, to: SceneNode, start: number, now: number) {\n\t\tconst key = to.key;\n\t\tfor (const field of FIELDS[to.kind]) {\n\t\t\tconst was = (from as unknown as Numeric)[field] as number;\n\t\t\tspring1(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\twas,\n\t\t\t\t(to as unknown as Numeric)[field] as number,\n\t\t\t\tstart,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tmixString(from, to, \"fill\", start);\n\t\tmixString(from, to, \"stroke\", start);\n\t\tif (to.kind !== \"path\" || from.kind !== \"path\") return;\n\t\tconst previous = shapes.get(key);\n\t\tshapes.delete(key);\n\t\tchannels.delete(channelKey(key, \"d\"));\n\t\tconst motion = to.motion;\n\t\tconst plan =\n\t\t\tmotion && from.geometry && to.geometry\n\t\t\t\t? motion.plan(from.geometry, to.geometry)\n\t\t\t\t: undefined;\n\t\tif (!plan) {\n\t\t\t// No meaning to move: the node's own morph, else the numbers of `d`.\n\t\t\tmixString(from, to, \"d\", start);\n\t\t\treturn;\n\t\t}\n\t\tfor (const channel of previous?.plan.targets.keys() ?? []) {\n\t\t\tif (!plan.targets.has(channel)) {\n\t\t\t\tchannels.delete(channelKey(key, `g${channel}`));\n\t\t\t}\n\t\t}\n\t\tfor (const [channel, goal] of plan.targets) {\n\t\t\tconst was = plan.starts.get(channel) ?? goal;\n\t\t\tspring1(key, `g${channel}`, was, goal, start, now);\n\t\t}\n\t\tshapes.set(key, { plan, motion: motion as GeometryMotion });\n\t}\n\n\tfunction paintNode(node: SceneNode, now: number): SceneNode {\n\t\tif (node.kind === \"group\") {\n\t\t\treturn { ...node, children: paintList(node.children, node.key, now) };\n\t\t}\n\t\tconst key = node.key;\n\t\tconst moved: Numeric = {};\n\t\tfor (const field of FIELDS[node.kind]) {\n\t\t\tmoved[field] = value(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\t(node as unknown as Numeric)[field] as number,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tlet next = { ...node, ...moved } as Exclude<SceneNode, { kind: \"group\" }>;\n\t\tconst shape = shapes.get(key);\n\t\tif (next.kind === \"path\" && shape) {\n\t\t\tconst geometry = shape.plan.build((channel) =>\n\t\t\t\tvalue(key, `g${channel}`, shape.plan.targets.get(channel) ?? 0, now),\n\t\t\t);\n\t\t\tnext = { ...next, geometry, d: shape.motion.draw(geometry) };\n\t\t}\n\t\tfor (const channel of STRING_CHANNELS) {\n\t\t\tconst motion = channels.get(channelKey(key, channel));\n\t\t\tif (motion?.mix) {\n\t\t\t\tnext = withString(next, channel, motion.mix(sample(motion, now).value));\n\t\t\t}\n\t\t}\n\t\tconst opacity = value(key, OPACITY, 1, now);\n\t\tconst drawn = value(key, DRAWN, 1, now);\n\t\tif (opacity === 1 && drawn === 1) return next;\n\t\treturn {\n\t\t\t...next,\n\t\t\tpaint: {\n\t\t\t\t...next.paint,\n\t\t\t\topacity: (next.paint.opacity ?? 1) * clamp(opacity),\n\t\t\t\t...(drawn < 1 &&\n\t\t\t\t\tnext.kind === \"path\" &&\n\t\t\t\t\tnext.geometry && {\n\t\t\t\t\t\tdrawn: {\n\t\t\t\t\t\t\tfraction: clamp(drawn),\n\t\t\t\t\t\t\tlength: next.motion?.length?.(next.geometry) ?? 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t} as SceneNode;\n\t}\n\n\t/** The painted list, with the nodes still leaving after the sibling they followed. */\n\tfunction paintList(\n\t\tlist: readonly SceneNode[],\n\t\tparent: string | null,\n\t\tnow: number,\n\t): SceneNode[] {\n\t\tconst out: SceneNode[] = [];\n\t\tfunction place(after: string | undefined) {\n\t\t\tfor (const [key, exit] of leaving) {\n\t\t\t\tif (exit.after !== after) continue;\n\t\t\t\tout.push(paintNode(exit.node, now));\n\t\t\t\tplace(key);\n\t\t\t}\n\t\t}\n\t\tplace(first(parent));\n\t\tfor (const node of list) {\n\t\t\tout.push(paintNode(node, now));\n\t\t\tplace(node.key);\n\t\t}\n\t\treturn out;\n\t}\n\n\tfunction prune(now: number) {\n\t\tfor (const [key, motion] of channels) {\n\t\t\tif (done(motion, now)) channels.delete(key);\n\t\t}\n\t\tconst moving = new Set(\n\t\t\t[...channels.keys()].map((key) => key.split(SEPARATOR)[0]),\n\t\t);\n\t\tfor (const byNode of [shapes, leaving]) {\n\t\t\tfor (const key of byNode.keys()) {\n\t\t\t\tif (!moving.has(key)) byNode.delete(key);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tretarget(scene, now) {\n\t\t\tconst before = index(target.nodes);\n\t\t\tconst after = index(scene.nodes);\n\t\t\tfunction painted(key: string): SceneNode | undefined {\n\t\t\t\tconst node = before.byKey.get(key) ?? leaving.get(key)?.node;\n\t\t\t\treturn node && paintNode(node, now);\n\t\t\t}\n\t\t\tfor (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {\n\t\t\t\tif (after.byKey.has(key)) continue;\n\t\t\t\tconst base = (before.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node) as SceneNode;\n\t\t\t\tconst exit = leaving.get(key);\n\t\t\t\tconst siblings = exit?.siblings ?? before.siblings.get(key) ?? [];\n\t\t\t\t// A whole guide group leaving goes at once, its lines with it.\n\t\t\t\tconst guide = base.kind !== \"group\" && GUIDE_ROLES.has(base.role ?? \"\");\n\t\t\t\tconst goal = exitTo(base, siblings, after.byKey) ?? (guide && base);\n\t\t\t\tconst from = painted(key);\n\t\t\t\tif (!goal || !from) {\n\t\t\t\t\tdrop(key);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmove(from, goal, now, now);\n\t\t\t\tif (guide) {\n\t\t\t\t\tspring1(key, OPACITY, value(key, OPACITY, 1, now), 0, now, now);\n\t\t\t\t}\n\t\t\t\tleaving.set(key, {\n\t\t\t\t\tnode: goal,\n\t\t\t\t\tafter: exit ? exit.after : before.afters.get(key),\n\t\t\t\t\tsiblings,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst marks = [...after.byKey.values()].filter(\n\t\t\t\t(node) => node.role === \"mark\",\n\t\t\t);\n\t\t\tconst order = new Map(marks.map((node, at) => [node.key, at]));\n\t\t\tfor (const node of after.byKey.values()) {\n\t\t\t\tif (node.kind === \"group\") continue;\n\t\t\t\tconst start =\n\t\t\t\t\tnow +\n\t\t\t\t\tstaggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;\n\t\t\t\tconst old = painted(node.key);\n\t\t\t\tconst returning = leaving.delete(node.key);\n\t\t\t\tconst from =\n\t\t\t\t\told ??\n\t\t\t\t\tenterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);\n\t\t\t\tif (from) move(from, node, start, now);\n\t\t\t\tconst fading = channels.get(channelKey(node.key, OPACITY));\n\t\t\t\tif ((!from && node.enter !== \"none\") || fading) {\n\t\t\t\t\tconst shown = fading ? Math.max(0, sample(fading, now).value) : 0;\n\t\t\t\t\t// A guide coming back starts from rest: its fade-out speed points the wrong way.\n\t\t\t\t\tif (returning) channels.delete(channelKey(node.key, OPACITY));\n\t\t\t\t\tspring1(node.key, OPACITY, shown, 1, start, now);\n\t\t\t\t}\n\t\t\t}\n\t\t\ttarget = scene;\n\t\t},\n\t\tintroduce(now) {\n\t\t\tlet any = false;\n\t\t\twalk(target.nodes, (node) => {\n\t\t\t\tif (node.kind !== \"path\" || node.enter !== \"draw\") return;\n\t\t\t\tif (node.geometry?.kind !== \"points\" || node.paint.strokeDasharray) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tspring1(node.key, DRAWN, 0, 1, now, now);\n\t\t\t\tany = true;\n\t\t\t});\n\t\t\treturn any;\n\t\t},\n\t\tsnap(scene) {\n\t\t\tfor (const motions of [channels, shapes, leaving]) {\n\t\t\t\tmotions.clear();\n\t\t\t}\n\t\t\ttarget = scene;\n\t\t},\n\t\tframe(now) {\n\t\t\tprune(now);\n\t\t\tif (channels.size === 0) return target;\n\t\t\treturn { ...target, nodes: paintList(target.nodes, null, now) };\n\t\t},\n\t\tsettled(now) {\n\t\t\treturn [...channels.values()].every((motion) => done(motion, now));\n\t\t},\n\t};\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voila.dev/chart",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.121",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Charts as data: a definition of marks, channels and scales, drawn to SVG or Canvas, keyboard and screen-reader accessible. The core has no dependencies; the heavy layouts (geo, hierarchy, sankey…) sit behind their own subpaths.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"sideEffects": false,
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@voila.dev/motion": "6.0.
|
|
67
|
+
"@voila.dev/motion": "6.0.121",
|
|
68
68
|
"d3-contour": "4.0.2",
|
|
69
69
|
"d3-delaunay": "6.0.4",
|
|
70
70
|
"d3-force": "3.0.0",
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { Generator } from "@voila.dev/motion";
|
|
2
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
3
|
import {
|
|
9
4
|
STRING_CHANNELS,
|
|
10
5
|
type StringChannel,
|
|
@@ -47,9 +42,13 @@ interface Motion {
|
|
|
47
42
|
readonly mix?: (progress: number) => string;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* A node on its way out, drawn after its old sibling until its springs
|
|
47
|
+
* settle: a data mark collapsing, or a guide fading where it stood.
|
|
48
|
+
*/
|
|
51
49
|
interface Leaving {
|
|
52
50
|
readonly node: SceneNode;
|
|
51
|
+
/** Its old previous sibling's key, else its group's `first` marker. */
|
|
53
52
|
readonly after: string | undefined;
|
|
54
53
|
readonly siblings: readonly SceneNode[];
|
|
55
54
|
}
|
|
@@ -68,25 +67,46 @@ const FIELDS: Record<SceneNode["kind"], readonly string[]> = {
|
|
|
68
67
|
const OPACITY = "opacity";
|
|
69
68
|
const DRAWN = "drawn";
|
|
70
69
|
const SEPARATOR = "\u0000";
|
|
70
|
+
/**
|
|
71
|
+
* A tick label or a grid line vanishing at once reads as a glitch, so they
|
|
72
|
+
* fade out. Data marks never linger: a bar the data no longer has would show
|
|
73
|
+
* a value that isn't true.
|
|
74
|
+
*/
|
|
75
|
+
const GUIDE_ROLES = new Set(["grid", "axis"]);
|
|
76
|
+
|
|
77
|
+
/** Where the leaving first children of the group `parent` are drawn. */
|
|
78
|
+
function first(parent: string | null): string | undefined {
|
|
79
|
+
return parent === null ? undefined : parent + SEPARATOR;
|
|
80
|
+
}
|
|
71
81
|
|
|
82
|
+
/** Visits every node with its list and the key it is drawn after when it leaves (see `Leaving`). */
|
|
72
83
|
function walk(
|
|
73
84
|
nodes: readonly SceneNode[],
|
|
74
|
-
visit: (
|
|
85
|
+
visit: (
|
|
86
|
+
node: SceneNode,
|
|
87
|
+
siblings: readonly SceneNode[],
|
|
88
|
+
after: string | undefined,
|
|
89
|
+
) => void,
|
|
90
|
+
parent: string | null = null,
|
|
75
91
|
) {
|
|
92
|
+
let after = first(parent);
|
|
76
93
|
for (const node of nodes) {
|
|
77
|
-
visit(node, nodes);
|
|
78
|
-
if (node.kind === "group") walk(node.children, visit);
|
|
94
|
+
visit(node, nodes, after);
|
|
95
|
+
if (node.kind === "group") walk(node.children, visit, node.key);
|
|
96
|
+
after = node.key;
|
|
79
97
|
}
|
|
80
98
|
}
|
|
81
99
|
|
|
82
100
|
function index(nodes: readonly SceneNode[]) {
|
|
83
101
|
const byKey = new Map<string, SceneNode>();
|
|
84
102
|
const siblings = new Map<string, readonly SceneNode[]>();
|
|
85
|
-
|
|
103
|
+
const afters = new Map<string, string | undefined>();
|
|
104
|
+
walk(nodes, (node, list, after) => {
|
|
86
105
|
byKey.set(node.key, node);
|
|
87
106
|
siblings.set(node.key, list);
|
|
107
|
+
afters.set(node.key, after);
|
|
88
108
|
});
|
|
89
|
-
return { byKey, siblings };
|
|
109
|
+
return { byKey, siblings, afters };
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
function channelKey(node: string, channel: string): string {
|
|
@@ -115,8 +135,6 @@ export function createMotionStore(
|
|
|
115
135
|
): MotionStore {
|
|
116
136
|
let target = initial;
|
|
117
137
|
const channels = new Map<string, Motion>();
|
|
118
|
-
/** Fading out on their opacity channel. */
|
|
119
|
-
const guides = new Map<string, LeavingGuide>();
|
|
120
138
|
const shapes = new Map<
|
|
121
139
|
string,
|
|
122
140
|
{ readonly plan: GeometryPlan; readonly motion: GeometryMotion }
|
|
@@ -262,10 +280,7 @@ export function createMotionStore(
|
|
|
262
280
|
} as SceneNode;
|
|
263
281
|
}
|
|
264
282
|
|
|
265
|
-
/**
|
|
266
|
-
* The painted list, with the marks still collapsing after the sibling they
|
|
267
|
-
* followed, and the guides still fading back at their places.
|
|
268
|
-
*/
|
|
283
|
+
/** The painted list, with the nodes still leaving after the sibling they followed. */
|
|
269
284
|
function paintList(
|
|
270
285
|
list: readonly SceneNode[],
|
|
271
286
|
parent: string | null,
|
|
@@ -279,15 +294,12 @@ export function createMotionStore(
|
|
|
279
294
|
place(key);
|
|
280
295
|
}
|
|
281
296
|
}
|
|
282
|
-
|
|
297
|
+
place(first(parent));
|
|
283
298
|
for (const node of list) {
|
|
284
299
|
out.push(paintNode(node, now));
|
|
285
300
|
place(node.key);
|
|
286
301
|
}
|
|
287
|
-
|
|
288
|
-
return withLeaving(out, parent, guides.values(), (node) =>
|
|
289
|
-
paintNode(node, now),
|
|
290
|
-
);
|
|
302
|
+
return out;
|
|
291
303
|
}
|
|
292
304
|
|
|
293
305
|
function prune(now: number) {
|
|
@@ -297,7 +309,7 @@ export function createMotionStore(
|
|
|
297
309
|
const moving = new Set(
|
|
298
310
|
[...channels.keys()].map((key) => key.split(SEPARATOR)[0]),
|
|
299
311
|
);
|
|
300
|
-
for (const byNode of [shapes, leaving
|
|
312
|
+
for (const byNode of [shapes, leaving]) {
|
|
301
313
|
for (const key of byNode.keys()) {
|
|
302
314
|
if (!moving.has(key)) byNode.delete(key);
|
|
303
315
|
}
|
|
@@ -309,45 +321,33 @@ export function createMotionStore(
|
|
|
309
321
|
const before = index(target.nodes);
|
|
310
322
|
const after = index(scene.nodes);
|
|
311
323
|
function painted(key: string): SceneNode | undefined {
|
|
312
|
-
const node =
|
|
313
|
-
before.byKey.get(key) ??
|
|
314
|
-
leaving.get(key)?.node ??
|
|
315
|
-
guides.get(key)?.node;
|
|
324
|
+
const node = before.byKey.get(key) ?? leaving.get(key)?.node;
|
|
316
325
|
return node && paintNode(node, now);
|
|
317
326
|
}
|
|
318
|
-
// Read before the exits below drop what the guides were showing.
|
|
319
|
-
const fadingGuides = leavingGuides(target.nodes, (key) =>
|
|
320
|
-
after.byKey.has(key),
|
|
321
|
-
).map(
|
|
322
|
-
(guide) => [guide, value(guide.node.key, OPACITY, 1, now)] as const,
|
|
323
|
-
);
|
|
324
327
|
for (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {
|
|
325
328
|
if (after.byKey.has(key)) continue;
|
|
326
329
|
const base = (before.byKey.get(key) ??
|
|
327
330
|
leaving.get(key)?.node) as SceneNode;
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
+
const exit = leaving.get(key);
|
|
332
|
+
const siblings = exit?.siblings ?? before.siblings.get(key) ?? [];
|
|
333
|
+
// A whole guide group leaving goes at once, its lines with it.
|
|
334
|
+
const guide = base.kind !== "group" && GUIDE_ROLES.has(base.role ?? "");
|
|
335
|
+
const goal = exitTo(base, siblings, after.byKey) ?? (guide && base);
|
|
331
336
|
const from = painted(key);
|
|
332
337
|
if (!goal || !from) {
|
|
333
338
|
drop(key);
|
|
334
339
|
continue;
|
|
335
340
|
}
|
|
336
341
|
move(from, goal, now, now);
|
|
337
|
-
|
|
342
|
+
if (guide) {
|
|
343
|
+
spring1(key, OPACITY, value(key, OPACITY, 1, now), 0, now, now);
|
|
344
|
+
}
|
|
338
345
|
leaving.set(key, {
|
|
339
346
|
node: goal,
|
|
340
|
-
after:
|
|
347
|
+
after: exit ? exit.after : before.afters.get(key),
|
|
341
348
|
siblings,
|
|
342
349
|
});
|
|
343
350
|
}
|
|
344
|
-
for (const [guide, shown] of fadingGuides) {
|
|
345
|
-
guides.set(guide.node.key, guide);
|
|
346
|
-
channels.set(channelKey(guide.node.key, OPACITY), {
|
|
347
|
-
generator: timing.motion(shown, 0, 0),
|
|
348
|
-
start: now,
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
351
|
const marks = [...after.byKey.values()].filter(
|
|
352
352
|
(node) => node.role === "mark",
|
|
353
353
|
);
|
|
@@ -358,7 +358,7 @@ export function createMotionStore(
|
|
|
358
358
|
now +
|
|
359
359
|
staggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;
|
|
360
360
|
const old = painted(node.key);
|
|
361
|
-
leaving.delete(node.key);
|
|
361
|
+
const returning = leaving.delete(node.key);
|
|
362
362
|
const from =
|
|
363
363
|
old ??
|
|
364
364
|
enterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);
|
|
@@ -367,12 +367,10 @@ export function createMotionStore(
|
|
|
367
367
|
if ((!from && node.enter !== "none") || fading) {
|
|
368
368
|
const shown = fading ? Math.max(0, sample(fading, now).value) : 0;
|
|
369
369
|
// A guide coming back starts from rest: its fade-out speed points the wrong way.
|
|
370
|
-
if (
|
|
371
|
-
channels.delete(channelKey(node.key, OPACITY));
|
|
370
|
+
if (returning) channels.delete(channelKey(node.key, OPACITY));
|
|
372
371
|
spring1(node.key, OPACITY, shown, 1, start, now);
|
|
373
372
|
}
|
|
374
373
|
}
|
|
375
|
-
for (const key of after.byKey.keys()) guides.delete(key);
|
|
376
374
|
target = scene;
|
|
377
375
|
},
|
|
378
376
|
introduce(now) {
|
|
@@ -388,7 +386,7 @@ export function createMotionStore(
|
|
|
388
386
|
return any;
|
|
389
387
|
},
|
|
390
388
|
snap(scene) {
|
|
391
|
-
for (const motions of [channels,
|
|
389
|
+
for (const motions of [channels, shapes, leaving]) {
|
|
392
390
|
motions.clear();
|
|
393
391
|
}
|
|
394
392
|
target = scene;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { SceneNode } from "../types.js";
|
|
2
|
-
export interface LeavingGuide {
|
|
3
|
-
readonly node: SceneNode;
|
|
4
|
-
/** The group it sat in, or null at the root. */
|
|
5
|
-
readonly parent: string | null;
|
|
6
|
-
readonly index: number;
|
|
7
|
-
}
|
|
8
|
-
/** The guides of `nodes` whose keys `stays` no longer has. */
|
|
9
|
-
export declare function leavingGuides(nodes: readonly SceneNode[], stays: (key: string) => boolean, parent?: string | null, into?: LeavingGuide[]): LeavingGuide[];
|
|
10
|
-
/** `children` with the leaving guides of `parent` back at their places, as `paint` draws them. */
|
|
11
|
-
export declare function withLeaving(children: readonly SceneNode[], parent: string | null, leaving: Iterable<LeavingGuide>, paint: (node: SceneNode) => SceneNode): SceneNode[];
|
|
12
|
-
//# sourceMappingURL=leaving.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"leaving.d.ts","sourceRoot":"","sources":["../../../src/core/motion/leaving.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AASjD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAC5B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,EAC/B,MAAM,GAAE,MAAM,GAAG,IAAW,EAC5B,IAAI,GAAE,YAAY,EAAO,GACvB,YAAY,EAAE,CAWhB;AAED,kGAAkG;AAClG,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,SAAS,SAAS,EAAE,EAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,SAAS,GACnC,SAAS,EAAE,CAOb"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const GUIDE_ROLES = /* @__PURE__ */ new Set(["grid", "axis"]);
|
|
2
|
-
function leavingGuides(nodes, stays, parent = null, into = []) {
|
|
3
|
-
for (const [index, node] of nodes.entries()) {
|
|
4
|
-
if (node.kind === "group") {
|
|
5
|
-
leavingGuides(node.children, stays, node.key, into);
|
|
6
|
-
continue;
|
|
7
|
-
}
|
|
8
|
-
if (!stays(node.key) && GUIDE_ROLES.has(node.role ?? "")) {
|
|
9
|
-
into.push({ node, parent, index });
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
return into;
|
|
13
|
-
}
|
|
14
|
-
function withLeaving(children, parent, leaving, paint) {
|
|
15
|
-
const placed = [...children];
|
|
16
|
-
for (const guide of leaving) {
|
|
17
|
-
if (guide.parent !== parent) continue;
|
|
18
|
-
placed.splice(Math.min(guide.index, placed.length), 0, paint(guide.node));
|
|
19
|
-
}
|
|
20
|
-
return placed;
|
|
21
|
-
}
|
|
22
|
-
export {
|
|
23
|
-
leavingGuides,
|
|
24
|
-
withLeaving
|
|
25
|
-
};
|
|
26
|
-
//# sourceMappingURL=leaving.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/core/motion/leaving.ts"],
|
|
4
|
-
"sourcesContent": ["import type { SceneNode } from \"#/core/types.ts\";\n\n/**\n * Guides that left the scene still fade out where they stood: a tick label\n * or a grid line vanishing at once reads as a glitch. Data marks never\n * linger \u2014 a bar the data no longer has would show a value that isn't true.\n */\nconst GUIDE_ROLES = new Set([\"grid\", \"axis\"]);\n\nexport interface LeavingGuide {\n\treadonly node: SceneNode;\n\t/** The group it sat in, or null at the root. */\n\treadonly parent: string | null;\n\treadonly index: number;\n}\n\n/** The guides of `nodes` whose keys `stays` no longer has. */\nexport function leavingGuides(\n\tnodes: readonly SceneNode[],\n\tstays: (key: string) => boolean,\n\tparent: string | null = null,\n\tinto: LeavingGuide[] = [],\n): LeavingGuide[] {\n\tfor (const [index, node] of nodes.entries()) {\n\t\tif (node.kind === \"group\") {\n\t\t\tleavingGuides(node.children, stays, node.key, into);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!stays(node.key) && GUIDE_ROLES.has(node.role ?? \"\")) {\n\t\t\tinto.push({ node, parent, index });\n\t\t}\n\t}\n\treturn into;\n}\n\n/** `children` with the leaving guides of `parent` back at their places, as `paint` draws them. */\nexport function withLeaving(\n\tchildren: readonly SceneNode[],\n\tparent: string | null,\n\tleaving: Iterable<LeavingGuide>,\n\tpaint: (node: SceneNode) => SceneNode,\n): SceneNode[] {\n\tconst placed = [...children];\n\tfor (const guide of leaving) {\n\t\tif (guide.parent !== parent) continue;\n\t\tplaced.splice(Math.min(guide.index, placed.length), 0, paint(guide.node));\n\t}\n\treturn placed;\n}\n"],
|
|
5
|
-
"mappings": "AAOA,MAAM,cAAc,oBAAI,IAAI,CAAC,QAAQ,MAAM,CAAC;AAUrC,SAAS,cACf,OACA,OACA,SAAwB,MACxB,OAAuB,CAAC,GACP;AACjB,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG;AAC5C,QAAI,KAAK,SAAS,SAAS;AAC1B,oBAAc,KAAK,UAAU,OAAO,KAAK,KAAK,IAAI;AAClD;AAAA,IACD;AACA,QAAI,CAAC,MAAM,KAAK,GAAG,KAAK,YAAY,IAAI,KAAK,QAAQ,EAAE,GAAG;AACzD,WAAK,KAAK,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,IAClC;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,YACf,UACA,QACA,SACA,OACc;AACd,QAAM,SAAS,CAAC,GAAG,QAAQ;AAC3B,aAAW,SAAS,SAAS;AAC5B,QAAI,MAAM,WAAW,OAAQ;AAC7B,WAAO,OAAO,KAAK,IAAI,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG,MAAM,MAAM,IAAI,CAAC;AAAA,EACzE;AACA,SAAO;AACR;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { SceneNode } from "#/core/types.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Guides that left the scene still fade out where they stood: a tick label
|
|
5
|
-
* or a grid line vanishing at once reads as a glitch. Data marks never
|
|
6
|
-
* linger — a bar the data no longer has would show a value that isn't true.
|
|
7
|
-
*/
|
|
8
|
-
const GUIDE_ROLES = new Set(["grid", "axis"]);
|
|
9
|
-
|
|
10
|
-
export interface LeavingGuide {
|
|
11
|
-
readonly node: SceneNode;
|
|
12
|
-
/** The group it sat in, or null at the root. */
|
|
13
|
-
readonly parent: string | null;
|
|
14
|
-
readonly index: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** The guides of `nodes` whose keys `stays` no longer has. */
|
|
18
|
-
export function leavingGuides(
|
|
19
|
-
nodes: readonly SceneNode[],
|
|
20
|
-
stays: (key: string) => boolean,
|
|
21
|
-
parent: string | null = null,
|
|
22
|
-
into: LeavingGuide[] = [],
|
|
23
|
-
): LeavingGuide[] {
|
|
24
|
-
for (const [index, node] of nodes.entries()) {
|
|
25
|
-
if (node.kind === "group") {
|
|
26
|
-
leavingGuides(node.children, stays, node.key, into);
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (!stays(node.key) && GUIDE_ROLES.has(node.role ?? "")) {
|
|
30
|
-
into.push({ node, parent, index });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return into;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** `children` with the leaving guides of `parent` back at their places, as `paint` draws them. */
|
|
37
|
-
export function withLeaving(
|
|
38
|
-
children: readonly SceneNode[],
|
|
39
|
-
parent: string | null,
|
|
40
|
-
leaving: Iterable<LeavingGuide>,
|
|
41
|
-
paint: (node: SceneNode) => SceneNode,
|
|
42
|
-
): SceneNode[] {
|
|
43
|
-
const placed = [...children];
|
|
44
|
-
for (const guide of leaving) {
|
|
45
|
-
if (guide.parent !== parent) continue;
|
|
46
|
-
placed.splice(Math.min(guide.index, placed.length), 0, paint(guide.node));
|
|
47
|
-
}
|
|
48
|
-
return placed;
|
|
49
|
-
}
|