@opendata-ai/openchart-vanilla 7.8.0 → 7.9.0
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/{chunk-KYRQV6KR.js → chunk-QRE4FQQM.js} +44 -18
- package/dist/chunk-QRE4FQQM.js.map +1 -0
- package/dist/index.d.ts +26 -19
- package/dist/index.js +1743 -23
- package/dist/index.js.map +1 -1
- package/dist/static.js +1 -1
- package/package.json +3 -3
- package/src/__tests__/animation.test.ts +1 -1
- package/src/__tests__/transition.test.ts +1592 -0
- package/src/barlist-mount.ts +1 -1
- package/src/barlist-renderer.ts +4 -4
- package/src/index.ts +2 -0
- package/src/mount.ts +64 -1
- package/src/renderers/axes.ts +18 -0
- package/src/renderers/marks.ts +21 -7
- package/src/sankey-mount.ts +2 -2
- package/src/sankey-renderer.ts +9 -8
- package/src/svg-renderer.ts +8 -7
- package/src/table-mount.ts +1 -1
- package/src/table-renderer.ts +5 -5
- package/src/tilemap-mount.ts +1 -1
- package/src/tilemap-renderer.ts +6 -6
- package/src/transition.ts +2473 -0
- package/dist/chunk-KYRQV6KR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buildGradientDefs,
|
|
3
|
+
rectPathWithCorners,
|
|
2
4
|
registerMarkRenderer,
|
|
3
5
|
renderChartSVG,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
renderSingleMark,
|
|
7
|
+
renderTileMapSVG,
|
|
8
|
+
resolveMarkFill
|
|
9
|
+
} from "./chunk-QRE4FQQM.js";
|
|
6
10
|
|
|
7
11
|
// src/barlist-mount.ts
|
|
8
12
|
import { compileBarList } from "@opendata-ai/openchart-engine";
|
|
@@ -134,7 +138,7 @@ function renderRows(parent, rows, animation) {
|
|
|
134
138
|
if (row.aria?.label) {
|
|
135
139
|
rowGroup.setAttribute("aria-label", row.aria.label);
|
|
136
140
|
}
|
|
137
|
-
if (animation?.
|
|
141
|
+
if (animation?.enter) {
|
|
138
142
|
rowGroup.setAttribute("data-animation-index", String(row.animationIndex));
|
|
139
143
|
const style = rowGroup.style;
|
|
140
144
|
style.setProperty("--oc-mark-index", String(row.animationIndex));
|
|
@@ -216,7 +220,7 @@ function renderRows(parent, rows, animation) {
|
|
|
216
220
|
}
|
|
217
221
|
function renderBarListSVG(layout, opts) {
|
|
218
222
|
const { width, height, rows, a11y, watermark, animation } = layout;
|
|
219
|
-
const animate = opts?.animate && animation?.
|
|
223
|
+
const animate = opts?.animate && !!animation?.enter;
|
|
220
224
|
const svg = createSVGElement("svg");
|
|
221
225
|
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
222
226
|
svg.setAttribute("role", "list");
|
|
@@ -226,8 +230,8 @@ function renderBarListSVG(layout, opts) {
|
|
|
226
230
|
}
|
|
227
231
|
const classes = animate ? "oc-barlist oc-animate" : "oc-barlist";
|
|
228
232
|
svg.setAttribute("class", classes);
|
|
229
|
-
if (animate && animation) {
|
|
230
|
-
svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
|
|
233
|
+
if (animate && animation?.enter) {
|
|
234
|
+
svg.style.setProperty("--oc-animation-duration", `${animation.enter.duration}ms`);
|
|
231
235
|
svg.style.setProperty("--oc-animation-stagger", "40ms");
|
|
232
236
|
}
|
|
233
237
|
renderChrome(svg, layout);
|
|
@@ -805,7 +809,7 @@ function createBarList(container, spec, options) {
|
|
|
805
809
|
tooltipManager = createTooltipManager(container);
|
|
806
810
|
}
|
|
807
811
|
}
|
|
808
|
-
if (currentLayout.animation?.
|
|
812
|
+
if (currentLayout.animation?.enter) {
|
|
809
813
|
animationCleanup = setupAnimationCleanup(newSvg, () => {
|
|
810
814
|
if (pendingResize && !destroyed) {
|
|
811
815
|
pendingResize = false;
|
|
@@ -5177,6 +5181,1680 @@ function stampThemeProperties(el, theme) {
|
|
|
5177
5181
|
}
|
|
5178
5182
|
}
|
|
5179
5183
|
|
|
5184
|
+
// src/transition.ts
|
|
5185
|
+
import { isGradientDef } from "@opendata-ai/openchart-core";
|
|
5186
|
+
import {
|
|
5187
|
+
buildAreaPath,
|
|
5188
|
+
buildLinePath,
|
|
5189
|
+
EXIT_DEFAULTS,
|
|
5190
|
+
serializeKeyValue
|
|
5191
|
+
} from "@opendata-ai/openchart-engine";
|
|
5192
|
+
function cubicOut(t) {
|
|
5193
|
+
const f = 1 - t;
|
|
5194
|
+
return 1 - f * f * f;
|
|
5195
|
+
}
|
|
5196
|
+
var TRANSITIONABLE_MARKS = /* @__PURE__ */ new Set(["bar", "line", "area", "point"]);
|
|
5197
|
+
function canTransition(args) {
|
|
5198
|
+
const { prevLayout, nextLayout, prevSpec, nextSpec, isFirstRender, entranceInFlight } = args;
|
|
5199
|
+
if (!prevLayout || !prevSpec || isFirstRender) return false;
|
|
5200
|
+
if (!nextLayout.animation?.update) return false;
|
|
5201
|
+
const prev = prevSpec;
|
|
5202
|
+
const next = nextSpec;
|
|
5203
|
+
if (!("mark" in prev) || !("mark" in next)) return false;
|
|
5204
|
+
const prevMark = typeof prev.mark === "string" ? prev.mark : prev.mark?.type;
|
|
5205
|
+
const nextMark = typeof next.mark === "string" ? next.mark : next.mark?.type;
|
|
5206
|
+
if (prevMark !== nextMark) return false;
|
|
5207
|
+
if (!TRANSITIONABLE_MARKS.has(prevMark)) return false;
|
|
5208
|
+
const prevEnc = prev.encoding;
|
|
5209
|
+
const nextEnc = next.encoding;
|
|
5210
|
+
if (!prevEnc || !nextEnc) return false;
|
|
5211
|
+
if (prevEnc.x?.field !== nextEnc.x?.field || prevEnc.x?.type !== nextEnc.x?.type || prevEnc.y?.field !== nextEnc.y?.field || prevEnc.y?.type !== nextEnc.y?.type || prevEnc.color?.field !== nextEnc.color?.field) {
|
|
5212
|
+
return false;
|
|
5213
|
+
}
|
|
5214
|
+
if (nextLayout.display === "sparkline") return false;
|
|
5215
|
+
if (entranceInFlight) return false;
|
|
5216
|
+
if (prevLayout.dimensions.width !== nextLayout.dimensions.width || prevLayout.dimensions.height !== nextLayout.dimensions.height) {
|
|
5217
|
+
return false;
|
|
5218
|
+
}
|
|
5219
|
+
if (nextLayout.marks.length > 500) return false;
|
|
5220
|
+
if (!hasGeometryChanged(prevLayout, nextLayout)) return false;
|
|
5221
|
+
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
5222
|
+
try {
|
|
5223
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
|
|
5224
|
+
} catch {
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
return true;
|
|
5228
|
+
}
|
|
5229
|
+
function hasGeometryChanged(prevLayout, nextLayout) {
|
|
5230
|
+
const prevMarks = prevLayout.marks;
|
|
5231
|
+
const nextMarks = nextLayout.marks;
|
|
5232
|
+
if (prevMarks.length !== nextMarks.length) return true;
|
|
5233
|
+
for (let i = 0; i < prevMarks.length; i++) {
|
|
5234
|
+
const p = prevMarks[i];
|
|
5235
|
+
const n = nextMarks[i];
|
|
5236
|
+
if (p.type !== n.type || p.key !== n.key) return true;
|
|
5237
|
+
if (p.type === "rect" && n.type === "rect") {
|
|
5238
|
+
if (p.x !== n.x || p.y !== n.y || p.width !== n.width || p.height !== n.height) return true;
|
|
5239
|
+
} else if (p.type === "line" && n.type === "line") {
|
|
5240
|
+
if (p.path !== n.path || p.points.length !== n.points.length) return true;
|
|
5241
|
+
for (let j = 0; j < p.points.length; j++) {
|
|
5242
|
+
if (p.points[j].x !== n.points[j].x || p.points[j].y !== n.points[j].y) return true;
|
|
5243
|
+
}
|
|
5244
|
+
} else if (p.type === "area" && n.type === "area") {
|
|
5245
|
+
if (p.path !== n.path) return true;
|
|
5246
|
+
} else if (p.type === "point" && n.type === "point") {
|
|
5247
|
+
if (p.cx !== n.cx || p.cy !== n.cy || p.r !== n.r) return true;
|
|
5248
|
+
} else if (p.type === "rule" && n.type === "rule") {
|
|
5249
|
+
if (p.x1 !== n.x1 || p.y1 !== n.y1 || p.x2 !== n.x2 || p.y2 !== n.y2) return true;
|
|
5250
|
+
} else if (p.type === "tick" && n.type === "tick") {
|
|
5251
|
+
if (p.x !== n.x || p.y !== n.y) return true;
|
|
5252
|
+
} else if (p.type === "textMark" && n.type === "textMark") {
|
|
5253
|
+
if (p.x !== n.x || p.y !== n.y) return true;
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
return false;
|
|
5257
|
+
}
|
|
5258
|
+
function geomFromMark(m2) {
|
|
5259
|
+
return { x: m2.x, y: m2.y, width: m2.width, height: m2.height };
|
|
5260
|
+
}
|
|
5261
|
+
function applyGeomToElement(el, geom, mark) {
|
|
5262
|
+
const shapeEl = el.querySelector("rect, path");
|
|
5263
|
+
if (!shapeEl) return;
|
|
5264
|
+
const sides = mark.cornerRadiusSides;
|
|
5265
|
+
const partialCorners = !!sides && (!sides.tl || !sides.tr || !sides.br || !sides.bl) && !!mark.cornerRadius;
|
|
5266
|
+
if (partialCorners && shapeEl.tagName === "path") {
|
|
5267
|
+
const tempMark = { ...mark, ...geom };
|
|
5268
|
+
shapeEl.setAttribute("d", rectPathWithCorners(tempMark, sides));
|
|
5269
|
+
} else {
|
|
5270
|
+
shapeEl.setAttribute("x", String(geom.x));
|
|
5271
|
+
shapeEl.setAttribute("y", String(geom.y));
|
|
5272
|
+
shapeEl.setAttribute("width", String(geom.width));
|
|
5273
|
+
shapeEl.setAttribute("height", String(geom.height));
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
function lerpGeom(from, to, t) {
|
|
5277
|
+
return {
|
|
5278
|
+
x: from.x + (to.x - from.x) * t,
|
|
5279
|
+
y: from.y + (to.y - from.y) * t,
|
|
5280
|
+
width: from.width + (to.width - from.width) * t,
|
|
5281
|
+
height: from.height + (to.height - from.height) * t
|
|
5282
|
+
};
|
|
5283
|
+
}
|
|
5284
|
+
function lerpPoint(a2, b, t) {
|
|
5285
|
+
return { x: a2.x + (b.x - a2.x) * t, y: a2.y + (b.y - a2.y) * t };
|
|
5286
|
+
}
|
|
5287
|
+
function normalizePointArrays(prevPoints, nextPoints, prevKeys, nextKeys) {
|
|
5288
|
+
const nextKeySet = new Set(nextKeys);
|
|
5289
|
+
const survivors = prevKeys.filter((k) => nextKeySet.has(k));
|
|
5290
|
+
if (survivors.length === 0) {
|
|
5291
|
+
return [prevPoints, nextPoints];
|
|
5292
|
+
}
|
|
5293
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
5294
|
+
for (let i = 0; i < prevKeys.length; i++) {
|
|
5295
|
+
prevByKey.set(prevKeys[i], prevPoints[i]);
|
|
5296
|
+
}
|
|
5297
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
5298
|
+
for (let i = 0; i < nextKeys.length; i++) {
|
|
5299
|
+
nextByKey.set(nextKeys[i], nextPoints[i]);
|
|
5300
|
+
}
|
|
5301
|
+
const merged = mergeKeyOrder(prevKeys, nextKeys);
|
|
5302
|
+
const mergedIndex = /* @__PURE__ */ new Map();
|
|
5303
|
+
for (let i = 0; i < merged.length; i++) {
|
|
5304
|
+
mergedIndex.set(merged[i], i);
|
|
5305
|
+
}
|
|
5306
|
+
const fromPts = [];
|
|
5307
|
+
const toPts = [];
|
|
5308
|
+
for (const key of merged) {
|
|
5309
|
+
const inPrev = prevByKey.has(key);
|
|
5310
|
+
const inNext = nextByKey.has(key);
|
|
5311
|
+
if (inPrev && inNext) {
|
|
5312
|
+
fromPts.push(prevByKey.get(key));
|
|
5313
|
+
toPts.push(nextByKey.get(key));
|
|
5314
|
+
} else if (inNext && !inPrev) {
|
|
5315
|
+
const nextPt = nextByKey.get(key);
|
|
5316
|
+
toPts.push(nextPt);
|
|
5317
|
+
fromPts.push(findInsertionPosition(key, merged, mergedIndex, prevByKey, nextByKey, nextPt));
|
|
5318
|
+
} else if (inPrev && !inNext) {
|
|
5319
|
+
const prevPt = prevByKey.get(key);
|
|
5320
|
+
fromPts.push(prevPt);
|
|
5321
|
+
toPts.push(findRemovalPosition(key, merged, mergedIndex, prevByKey, nextByKey, prevPt));
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5324
|
+
return [fromPts, toPts];
|
|
5325
|
+
}
|
|
5326
|
+
function mergeKeyOrder(prevKeys, nextKeys) {
|
|
5327
|
+
const nextIndex = /* @__PURE__ */ new Map();
|
|
5328
|
+
for (let i = 0; i < nextKeys.length; i++) {
|
|
5329
|
+
if (!nextIndex.has(nextKeys[i])) nextIndex.set(nextKeys[i], i);
|
|
5330
|
+
}
|
|
5331
|
+
const prevIndex = /* @__PURE__ */ new Map();
|
|
5332
|
+
for (let i = 0; i < prevKeys.length; i++) {
|
|
5333
|
+
if (!prevIndex.has(prevKeys[i])) prevIndex.set(prevKeys[i], i);
|
|
5334
|
+
}
|
|
5335
|
+
const result = [];
|
|
5336
|
+
const added = /* @__PURE__ */ new Set();
|
|
5337
|
+
let pi = 0;
|
|
5338
|
+
let ni = 0;
|
|
5339
|
+
while (pi < prevKeys.length && ni < nextKeys.length) {
|
|
5340
|
+
const pk = prevKeys[pi];
|
|
5341
|
+
const nk = nextKeys[ni];
|
|
5342
|
+
if (added.has(pk)) {
|
|
5343
|
+
pi++;
|
|
5344
|
+
continue;
|
|
5345
|
+
}
|
|
5346
|
+
if (added.has(nk)) {
|
|
5347
|
+
ni++;
|
|
5348
|
+
continue;
|
|
5349
|
+
}
|
|
5350
|
+
if (pk === nk) {
|
|
5351
|
+
result.push(pk);
|
|
5352
|
+
added.add(pk);
|
|
5353
|
+
pi++;
|
|
5354
|
+
ni++;
|
|
5355
|
+
} else {
|
|
5356
|
+
const pkInNext = nextIndex.has(pk) && nextIndex.get(pk) >= ni ? nextIndex.get(pk) : -1;
|
|
5357
|
+
const nkInPrev = prevIndex.has(nk) && prevIndex.get(nk) >= pi ? prevIndex.get(nk) : -1;
|
|
5358
|
+
if (pkInNext === -1 && nkInPrev === -1) {
|
|
5359
|
+
result.push(pk);
|
|
5360
|
+
added.add(pk);
|
|
5361
|
+
pi++;
|
|
5362
|
+
} else if (pkInNext === -1) {
|
|
5363
|
+
result.push(pk);
|
|
5364
|
+
added.add(pk);
|
|
5365
|
+
pi++;
|
|
5366
|
+
} else if (nkInPrev === -1) {
|
|
5367
|
+
result.push(nk);
|
|
5368
|
+
added.add(nk);
|
|
5369
|
+
ni++;
|
|
5370
|
+
} else {
|
|
5371
|
+
if (pkInNext <= nkInPrev) {
|
|
5372
|
+
result.push(pk);
|
|
5373
|
+
added.add(pk);
|
|
5374
|
+
pi++;
|
|
5375
|
+
} else {
|
|
5376
|
+
result.push(nk);
|
|
5377
|
+
added.add(nk);
|
|
5378
|
+
ni++;
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
while (pi < prevKeys.length) {
|
|
5384
|
+
if (!added.has(prevKeys[pi])) {
|
|
5385
|
+
result.push(prevKeys[pi]);
|
|
5386
|
+
added.add(prevKeys[pi]);
|
|
5387
|
+
}
|
|
5388
|
+
pi++;
|
|
5389
|
+
}
|
|
5390
|
+
while (ni < nextKeys.length) {
|
|
5391
|
+
if (!added.has(nextKeys[ni])) {
|
|
5392
|
+
result.push(nextKeys[ni]);
|
|
5393
|
+
added.add(nextKeys[ni]);
|
|
5394
|
+
}
|
|
5395
|
+
ni++;
|
|
5396
|
+
}
|
|
5397
|
+
return result;
|
|
5398
|
+
}
|
|
5399
|
+
function findInsertionPosition(key, merged, mergedIndex, prevByKey, nextByKey, nextPt) {
|
|
5400
|
+
const idx = mergedIndex.get(key) ?? -1;
|
|
5401
|
+
let beforeKey = null;
|
|
5402
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
5403
|
+
if (prevByKey.has(merged[i]) && nextByKey.has(merged[i])) {
|
|
5404
|
+
beforeKey = merged[i];
|
|
5405
|
+
break;
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
let afterKey = null;
|
|
5409
|
+
for (let i = idx + 1; i < merged.length; i++) {
|
|
5410
|
+
if (prevByKey.has(merged[i]) && nextByKey.has(merged[i])) {
|
|
5411
|
+
afterKey = merged[i];
|
|
5412
|
+
break;
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
if (beforeKey && afterKey) {
|
|
5416
|
+
const beforeNext = nextByKey.get(beforeKey);
|
|
5417
|
+
const afterNext = nextByKey.get(afterKey);
|
|
5418
|
+
const beforePrev = prevByKey.get(beforeKey);
|
|
5419
|
+
const afterPrev = prevByKey.get(afterKey);
|
|
5420
|
+
const range = afterNext.x - beforeNext.x;
|
|
5421
|
+
const t = range === 0 ? 0.5 : (nextPt.x - beforeNext.x) / range;
|
|
5422
|
+
return lerpPoint(beforePrev, afterPrev, Math.max(0, Math.min(1, t)));
|
|
5423
|
+
}
|
|
5424
|
+
if (beforeKey) {
|
|
5425
|
+
return { ...prevByKey.get(beforeKey) };
|
|
5426
|
+
}
|
|
5427
|
+
if (afterKey) {
|
|
5428
|
+
return { ...prevByKey.get(afterKey) };
|
|
5429
|
+
}
|
|
5430
|
+
return nextPt;
|
|
5431
|
+
}
|
|
5432
|
+
function findRemovalPosition(key, merged, mergedIndex, prevByKey, nextByKey, prevPt) {
|
|
5433
|
+
const idx = mergedIndex.get(key) ?? -1;
|
|
5434
|
+
let beforeKey = null;
|
|
5435
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
5436
|
+
if (prevByKey.has(merged[i]) && nextByKey.has(merged[i])) {
|
|
5437
|
+
beforeKey = merged[i];
|
|
5438
|
+
break;
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
let afterKey = null;
|
|
5442
|
+
for (let i = idx + 1; i < merged.length; i++) {
|
|
5443
|
+
if (prevByKey.has(merged[i]) && nextByKey.has(merged[i])) {
|
|
5444
|
+
afterKey = merged[i];
|
|
5445
|
+
break;
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
if (beforeKey && afterKey) {
|
|
5449
|
+
const beforePrev = prevByKey.get(beforeKey);
|
|
5450
|
+
const afterPrev = prevByKey.get(afterKey);
|
|
5451
|
+
const beforeNext = nextByKey.get(beforeKey);
|
|
5452
|
+
const afterNext = nextByKey.get(afterKey);
|
|
5453
|
+
const range = afterPrev.x - beforePrev.x;
|
|
5454
|
+
const t = range === 0 ? 0.5 : (prevPt.x - beforePrev.x) / range;
|
|
5455
|
+
return lerpPoint(beforeNext, afterNext, Math.max(0, Math.min(1, t)));
|
|
5456
|
+
}
|
|
5457
|
+
if (beforeKey) {
|
|
5458
|
+
return { ...nextByKey.get(beforeKey) };
|
|
5459
|
+
}
|
|
5460
|
+
if (afterKey) {
|
|
5461
|
+
return { ...nextByKey.get(afterKey) };
|
|
5462
|
+
}
|
|
5463
|
+
return prevPt;
|
|
5464
|
+
}
|
|
5465
|
+
function interpolatePoints(fromPts, toPts, t) {
|
|
5466
|
+
const len = Math.min(fromPts.length, toPts.length);
|
|
5467
|
+
const result = new Array(len);
|
|
5468
|
+
for (let i = 0; i < len; i++) {
|
|
5469
|
+
result[i] = lerpPoint(fromPts[i], toPts[i], t);
|
|
5470
|
+
}
|
|
5471
|
+
return result;
|
|
5472
|
+
}
|
|
5473
|
+
function applyLinePath(el, points, interpolate) {
|
|
5474
|
+
const pathEl = el.querySelector("path");
|
|
5475
|
+
if (!pathEl) return;
|
|
5476
|
+
pathEl.setAttribute("d", buildLinePath(points, interpolate));
|
|
5477
|
+
}
|
|
5478
|
+
function applyAreaPaths(el, topPoints, bottomPoints, interpolate, hasStroke) {
|
|
5479
|
+
const paths = el.querySelectorAll("path");
|
|
5480
|
+
if (paths[0]) {
|
|
5481
|
+
paths[0].setAttribute("d", buildAreaPath(topPoints, bottomPoints, interpolate));
|
|
5482
|
+
}
|
|
5483
|
+
if (hasStroke && paths[1]) {
|
|
5484
|
+
paths[1].setAttribute("d", buildLinePath(topPoints, interpolate));
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
function applyFinalLinePath(el, mark) {
|
|
5488
|
+
const pathEl = el.querySelector("path");
|
|
5489
|
+
if (!pathEl || !mark.path) return;
|
|
5490
|
+
pathEl.setAttribute("d", mark.path);
|
|
5491
|
+
}
|
|
5492
|
+
function applyFinalAreaPaths(el, mark) {
|
|
5493
|
+
const paths = el.querySelectorAll("path");
|
|
5494
|
+
if (paths[0]) {
|
|
5495
|
+
paths[0].setAttribute("d", mark.path);
|
|
5496
|
+
}
|
|
5497
|
+
if (paths[1] && mark.topPath) {
|
|
5498
|
+
paths[1].setAttribute("d", mark.topPath);
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
function runTransition(args) {
|
|
5502
|
+
const { svg, prevLayout, nextLayout, animation, onComplete, fromSnapshot } = args;
|
|
5503
|
+
const update = animation.update;
|
|
5504
|
+
const exit = animation.exit ?? { ...EXIT_DEFAULTS };
|
|
5505
|
+
const totalMs = Math.max(update.duration, exit.duration);
|
|
5506
|
+
const enterDelay = 0.4 * update.duration;
|
|
5507
|
+
const enterDuration = update.duration - enterDelay;
|
|
5508
|
+
const marksContainer = svg.querySelector(".oc-marks");
|
|
5509
|
+
if (!marksContainer) {
|
|
5510
|
+
onComplete();
|
|
5511
|
+
return {
|
|
5512
|
+
cancel() {
|
|
5513
|
+
},
|
|
5514
|
+
get running() {
|
|
5515
|
+
return false;
|
|
5516
|
+
},
|
|
5517
|
+
snapshot() {
|
|
5518
|
+
return /* @__PURE__ */ new Map();
|
|
5519
|
+
}
|
|
5520
|
+
};
|
|
5521
|
+
}
|
|
5522
|
+
const tweens = [];
|
|
5523
|
+
const ghosts = [];
|
|
5524
|
+
const defs = svg.querySelector("defs");
|
|
5525
|
+
let ghostGradientMap = /* @__PURE__ */ new Map();
|
|
5526
|
+
if (defs) {
|
|
5527
|
+
const nextKeySet = new Set(nextLayout.marks.filter((m2) => m2.key).map((m2) => m2.key));
|
|
5528
|
+
const exitingMarks = prevLayout.marks.filter(
|
|
5529
|
+
(m2) => m2.key && !nextKeySet.has(m2.key) && "fill" in m2
|
|
5530
|
+
);
|
|
5531
|
+
const gradientMarks = exitingMarks.filter(
|
|
5532
|
+
(m2) => "fill" in m2 && isGradientDef(m2.fill)
|
|
5533
|
+
);
|
|
5534
|
+
if (gradientMarks.length > 0) {
|
|
5535
|
+
ghostGradientMap = buildGradientDefs(gradientMarks, defs);
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
const hasRects = prevLayout.marks.some((m2) => m2.type === "rect") || nextLayout.marks.some((m2) => m2.type === "rect");
|
|
5539
|
+
const hasLines = prevLayout.marks.some((m2) => m2.type === "line") || nextLayout.marks.some((m2) => m2.type === "line");
|
|
5540
|
+
const hasAreas = prevLayout.marks.some((m2) => m2.type === "area") || nextLayout.marks.some((m2) => m2.type === "area");
|
|
5541
|
+
const hasPoints = prevLayout.marks.some((m2) => m2.type === "point") || nextLayout.marks.some((m2) => m2.type === "point");
|
|
5542
|
+
const hasRules = prevLayout.marks.some((m2) => m2.type === "rule") || nextLayout.marks.some((m2) => m2.type === "rule");
|
|
5543
|
+
const hasTicks = prevLayout.marks.some((m2) => m2.type === "tick") || nextLayout.marks.some((m2) => m2.type === "tick");
|
|
5544
|
+
const hasTextMarks = prevLayout.marks.some((m2) => m2.type === "textMark") || nextLayout.marks.some((m2) => m2.type === "textMark");
|
|
5545
|
+
if (hasRects) {
|
|
5546
|
+
buildRectTweens(
|
|
5547
|
+
prevLayout,
|
|
5548
|
+
nextLayout,
|
|
5549
|
+
marksContainer,
|
|
5550
|
+
tweens,
|
|
5551
|
+
ghosts,
|
|
5552
|
+
ghostGradientMap,
|
|
5553
|
+
fromSnapshot
|
|
5554
|
+
);
|
|
5555
|
+
}
|
|
5556
|
+
if (hasLines) {
|
|
5557
|
+
buildLineTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot);
|
|
5558
|
+
}
|
|
5559
|
+
if (hasAreas) {
|
|
5560
|
+
buildAreaTweens(
|
|
5561
|
+
prevLayout,
|
|
5562
|
+
nextLayout,
|
|
5563
|
+
marksContainer,
|
|
5564
|
+
tweens,
|
|
5565
|
+
ghosts,
|
|
5566
|
+
ghostGradientMap,
|
|
5567
|
+
fromSnapshot
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
if (hasPoints) {
|
|
5571
|
+
buildPointTweens(
|
|
5572
|
+
prevLayout,
|
|
5573
|
+
nextLayout,
|
|
5574
|
+
marksContainer,
|
|
5575
|
+
tweens,
|
|
5576
|
+
ghosts,
|
|
5577
|
+
ghostGradientMap,
|
|
5578
|
+
fromSnapshot
|
|
5579
|
+
);
|
|
5580
|
+
}
|
|
5581
|
+
if (hasRules) {
|
|
5582
|
+
buildRuleTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot);
|
|
5583
|
+
}
|
|
5584
|
+
if (hasTicks) {
|
|
5585
|
+
buildTickTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot);
|
|
5586
|
+
}
|
|
5587
|
+
if (hasTextMarks) {
|
|
5588
|
+
buildTextMarkTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot);
|
|
5589
|
+
}
|
|
5590
|
+
buildAxisTweens(svg, prevLayout, nextLayout, tweens, ghosts);
|
|
5591
|
+
const secondaryEls = collectSecondaryElements(svg);
|
|
5592
|
+
const secondaryOriginalOpacity = secondaryEls.map((el) => el.style.opacity ?? "");
|
|
5593
|
+
for (const tw of tweens) {
|
|
5594
|
+
applyTweenState(tw, 0, update, exit, enterDelay, enterDuration);
|
|
5595
|
+
}
|
|
5596
|
+
for (const el of secondaryEls) {
|
|
5597
|
+
el.style.opacity = "0";
|
|
5598
|
+
}
|
|
5599
|
+
let rafId = null;
|
|
5600
|
+
let running = true;
|
|
5601
|
+
let startTime = null;
|
|
5602
|
+
let lastElapsed = 0;
|
|
5603
|
+
const nextLineMarks = nextLayout.marks.filter((m2) => m2.type === "line");
|
|
5604
|
+
const nextAreaMarks = nextLayout.marks.filter((m2) => m2.type === "area");
|
|
5605
|
+
function tick(now2) {
|
|
5606
|
+
if (!running) return;
|
|
5607
|
+
if (startTime === null) startTime = now2;
|
|
5608
|
+
const elapsed = now2 - startTime;
|
|
5609
|
+
lastElapsed = elapsed;
|
|
5610
|
+
const tGlobal = Math.min(elapsed / totalMs, 1);
|
|
5611
|
+
for (const tw of tweens) {
|
|
5612
|
+
applyTweenState(tw, elapsed, update, exit, enterDelay, enterDuration);
|
|
5613
|
+
}
|
|
5614
|
+
applySecondaryOpacity(secondaryEls, elapsed, enterDelay, enterDuration);
|
|
5615
|
+
if (tGlobal >= 1) {
|
|
5616
|
+
finish();
|
|
5617
|
+
} else {
|
|
5618
|
+
rafId = requestAnimationFrame(tick);
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
function finish() {
|
|
5622
|
+
if (!running) return;
|
|
5623
|
+
running = false;
|
|
5624
|
+
rafId = null;
|
|
5625
|
+
snapToFinal();
|
|
5626
|
+
removeGhosts();
|
|
5627
|
+
for (let i = 0; i < secondaryEls.length; i++) {
|
|
5628
|
+
secondaryEls[i].style.opacity = secondaryOriginalOpacity[i];
|
|
5629
|
+
}
|
|
5630
|
+
onComplete();
|
|
5631
|
+
}
|
|
5632
|
+
function snapToFinal() {
|
|
5633
|
+
for (const tw of tweens) {
|
|
5634
|
+
snapTweenToFinal(tw);
|
|
5635
|
+
}
|
|
5636
|
+
for (const mark of nextLineMarks) {
|
|
5637
|
+
if (!mark.key) continue;
|
|
5638
|
+
const el = marksContainer.querySelector(`[data-key="${mark.key}"]`);
|
|
5639
|
+
if (el) applyFinalLinePath(el, mark);
|
|
5640
|
+
}
|
|
5641
|
+
for (const mark of nextAreaMarks) {
|
|
5642
|
+
if (!mark.key) continue;
|
|
5643
|
+
const el = marksContainer.querySelector(`[data-key="${mark.key}"]`);
|
|
5644
|
+
if (el) applyFinalAreaPaths(el, mark);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
function removeGhosts() {
|
|
5648
|
+
for (const ghost of ghosts) {
|
|
5649
|
+
ghost.parentNode?.removeChild(ghost);
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
function captureSnapshot() {
|
|
5653
|
+
const snap = /* @__PURE__ */ new Map();
|
|
5654
|
+
const tUpdate = Math.min(lastElapsed / update.duration, 1);
|
|
5655
|
+
const easedUpdate = cubicOut(tUpdate);
|
|
5656
|
+
for (const tw of tweens) {
|
|
5657
|
+
if (tw.kind !== "update") continue;
|
|
5658
|
+
const key = getKeyForTween(tw);
|
|
5659
|
+
if (!key) continue;
|
|
5660
|
+
switch (tw.tweenType) {
|
|
5661
|
+
case "rect": {
|
|
5662
|
+
const g = lerpGeom(tw.from, tw.to, easedUpdate);
|
|
5663
|
+
snap.set(key, { type: "rect", ...g });
|
|
5664
|
+
break;
|
|
5665
|
+
}
|
|
5666
|
+
case "point": {
|
|
5667
|
+
const cx = tw.fromCx + (tw.toCx - tw.fromCx) * easedUpdate;
|
|
5668
|
+
const cy = tw.fromCy + (tw.toCy - tw.fromCy) * easedUpdate;
|
|
5669
|
+
const entry = { type: "point", cx, cy };
|
|
5670
|
+
if (tw.fromR !== void 0 && tw.toR !== void 0) {
|
|
5671
|
+
entry.r = tw.fromR + (tw.toR - tw.fromR) * easedUpdate;
|
|
5672
|
+
}
|
|
5673
|
+
snap.set(key, entry);
|
|
5674
|
+
break;
|
|
5675
|
+
}
|
|
5676
|
+
case "rule":
|
|
5677
|
+
case "tick": {
|
|
5678
|
+
snap.set(key, {
|
|
5679
|
+
type: tw.tweenType,
|
|
5680
|
+
x1: tw.fromX1 + (tw.toX1 - tw.fromX1) * easedUpdate,
|
|
5681
|
+
y1: tw.fromY1 + (tw.toY1 - tw.fromY1) * easedUpdate,
|
|
5682
|
+
x2: tw.fromX2 + (tw.toX2 - tw.fromX2) * easedUpdate,
|
|
5683
|
+
y2: tw.fromY2 + (tw.toY2 - tw.fromY2) * easedUpdate
|
|
5684
|
+
});
|
|
5685
|
+
break;
|
|
5686
|
+
}
|
|
5687
|
+
case "textMark": {
|
|
5688
|
+
snap.set(key, {
|
|
5689
|
+
type: "textMark",
|
|
5690
|
+
x: tw.fromX + (tw.toX - tw.fromX) * easedUpdate,
|
|
5691
|
+
y: tw.fromY + (tw.toY - tw.fromY) * easedUpdate
|
|
5692
|
+
});
|
|
5693
|
+
break;
|
|
5694
|
+
}
|
|
5695
|
+
case "line": {
|
|
5696
|
+
const pts = interpolatePoints(tw.fromPts, tw.toPts, easedUpdate);
|
|
5697
|
+
const d = buildLinePath(pts, tw.interpolate);
|
|
5698
|
+
snap.set(key, { type: "line", d });
|
|
5699
|
+
break;
|
|
5700
|
+
}
|
|
5701
|
+
case "area": {
|
|
5702
|
+
const top = interpolatePoints(tw.fromTop, tw.toTop, easedUpdate);
|
|
5703
|
+
const bottom = interpolatePoints(tw.fromBottom, tw.toBottom, easedUpdate);
|
|
5704
|
+
const d = buildAreaPath(top, bottom, tw.interpolate);
|
|
5705
|
+
let topD;
|
|
5706
|
+
if (tw.hasStroke) {
|
|
5707
|
+
topD = buildLinePath(top, tw.interpolate);
|
|
5708
|
+
}
|
|
5709
|
+
snap.set(key, { type: "area", d, topD });
|
|
5710
|
+
break;
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
return snap;
|
|
5715
|
+
}
|
|
5716
|
+
rafId = requestAnimationFrame(tick);
|
|
5717
|
+
return {
|
|
5718
|
+
cancel() {
|
|
5719
|
+
if (!running) return;
|
|
5720
|
+
running = false;
|
|
5721
|
+
if (rafId !== null) {
|
|
5722
|
+
cancelAnimationFrame(rafId);
|
|
5723
|
+
rafId = null;
|
|
5724
|
+
}
|
|
5725
|
+
snapToFinal();
|
|
5726
|
+
removeGhosts();
|
|
5727
|
+
for (let i = 0; i < secondaryEls.length; i++) {
|
|
5728
|
+
secondaryEls[i].style.opacity = secondaryOriginalOpacity[i];
|
|
5729
|
+
}
|
|
5730
|
+
},
|
|
5731
|
+
get running() {
|
|
5732
|
+
return running;
|
|
5733
|
+
},
|
|
5734
|
+
snapshot() {
|
|
5735
|
+
return captureSnapshot();
|
|
5736
|
+
}
|
|
5737
|
+
};
|
|
5738
|
+
}
|
|
5739
|
+
function collectSecondaryElements(svg) {
|
|
5740
|
+
const els = [];
|
|
5741
|
+
for (const el of svg.querySelectorAll(".oc-annotation")) {
|
|
5742
|
+
els.push(el);
|
|
5743
|
+
}
|
|
5744
|
+
const epLabels = svg.querySelector(".oc-endpoint-labels");
|
|
5745
|
+
if (epLabels) els.push(epLabels);
|
|
5746
|
+
const markLabels = svg.querySelector(".oc-mark-labels");
|
|
5747
|
+
if (markLabels) els.push(markLabels);
|
|
5748
|
+
return els;
|
|
5749
|
+
}
|
|
5750
|
+
function applySecondaryOpacity(els, elapsed, enterDelay, enterDuration) {
|
|
5751
|
+
if (els.length === 0) return;
|
|
5752
|
+
let t;
|
|
5753
|
+
if (elapsed < enterDelay) {
|
|
5754
|
+
t = 0;
|
|
5755
|
+
} else {
|
|
5756
|
+
t = Math.min((elapsed - enterDelay) / enterDuration, 1);
|
|
5757
|
+
}
|
|
5758
|
+
const opacity = String(cubicOut(t));
|
|
5759
|
+
for (const el of els) {
|
|
5760
|
+
el.style.opacity = opacity;
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
function getKeyForTween(tw) {
|
|
5764
|
+
if ("ghost" in tw && tw.ghost) return null;
|
|
5765
|
+
return tw.el.getAttribute("data-key");
|
|
5766
|
+
}
|
|
5767
|
+
function resolveLineElement(el) {
|
|
5768
|
+
return el.tagName === "line" ? el : el.querySelector("line") ?? el;
|
|
5769
|
+
}
|
|
5770
|
+
function applyTweenState(tw, elapsed, update, exit, enterDelay, enterDuration) {
|
|
5771
|
+
let tLocal;
|
|
5772
|
+
if (tw.kind === "exit") {
|
|
5773
|
+
tLocal = Math.min(elapsed / exit.duration, 1);
|
|
5774
|
+
} else if (tw.kind === "enter") {
|
|
5775
|
+
if (elapsed < enterDelay) {
|
|
5776
|
+
tLocal = 0;
|
|
5777
|
+
} else {
|
|
5778
|
+
tLocal = Math.min((elapsed - enterDelay) / enterDuration, 1);
|
|
5779
|
+
}
|
|
5780
|
+
} else {
|
|
5781
|
+
tLocal = Math.min(elapsed / update.duration, 1);
|
|
5782
|
+
}
|
|
5783
|
+
const eased = cubicOut(tLocal);
|
|
5784
|
+
switch (tw.tweenType) {
|
|
5785
|
+
case "rect": {
|
|
5786
|
+
const geom = lerpGeom(tw.from, tw.to, eased);
|
|
5787
|
+
applyGeomToElement(tw.el, geom, tw.mark);
|
|
5788
|
+
break;
|
|
5789
|
+
}
|
|
5790
|
+
case "line": {
|
|
5791
|
+
const pts = interpolatePoints(tw.fromPts, tw.toPts, eased);
|
|
5792
|
+
applyLinePath(tw.el, pts, tw.interpolate);
|
|
5793
|
+
break;
|
|
5794
|
+
}
|
|
5795
|
+
case "area": {
|
|
5796
|
+
const top = interpolatePoints(tw.fromTop, tw.toTop, eased);
|
|
5797
|
+
const bottom = interpolatePoints(tw.fromBottom, tw.toBottom, eased);
|
|
5798
|
+
applyAreaPaths(tw.el, top, bottom, tw.interpolate, tw.hasStroke);
|
|
5799
|
+
break;
|
|
5800
|
+
}
|
|
5801
|
+
case "point": {
|
|
5802
|
+
const cx = tw.fromCx + (tw.toCx - tw.fromCx) * eased;
|
|
5803
|
+
const cy = tw.fromCy + (tw.toCy - tw.fromCy) * eased;
|
|
5804
|
+
tw.el.setAttribute("cx", String(cx));
|
|
5805
|
+
tw.el.setAttribute("cy", String(cy));
|
|
5806
|
+
if (tw.fromR !== void 0 && tw.toR !== void 0) {
|
|
5807
|
+
const r = tw.fromR + (tw.toR - tw.fromR) * eased;
|
|
5808
|
+
tw.el.setAttribute("r", String(r));
|
|
5809
|
+
}
|
|
5810
|
+
break;
|
|
5811
|
+
}
|
|
5812
|
+
case "rule":
|
|
5813
|
+
case "tick": {
|
|
5814
|
+
const lineEl = resolveLineElement(tw.el);
|
|
5815
|
+
lineEl.setAttribute("x1", String(tw.fromX1 + (tw.toX1 - tw.fromX1) * eased));
|
|
5816
|
+
lineEl.setAttribute("y1", String(tw.fromY1 + (tw.toY1 - tw.fromY1) * eased));
|
|
5817
|
+
lineEl.setAttribute("x2", String(tw.fromX2 + (tw.toX2 - tw.fromX2) * eased));
|
|
5818
|
+
lineEl.setAttribute("y2", String(tw.fromY2 + (tw.toY2 - tw.fromY2) * eased));
|
|
5819
|
+
break;
|
|
5820
|
+
}
|
|
5821
|
+
case "textMark": {
|
|
5822
|
+
const x3 = tw.fromX + (tw.toX - tw.fromX) * eased;
|
|
5823
|
+
const y3 = tw.fromY + (tw.toY - tw.fromY) * eased;
|
|
5824
|
+
tw.el.setAttribute("x", String(x3));
|
|
5825
|
+
tw.el.setAttribute("y", String(y3));
|
|
5826
|
+
break;
|
|
5827
|
+
}
|
|
5828
|
+
case "axisTick": {
|
|
5829
|
+
if (tw.crossfade) {
|
|
5830
|
+
break;
|
|
5831
|
+
}
|
|
5832
|
+
const pos = tw.fromPos + (tw.toPos - tw.fromPos) * eased;
|
|
5833
|
+
if (tw.posAttr === "x") {
|
|
5834
|
+
tw.el.setAttribute("x", String(pos));
|
|
5835
|
+
} else {
|
|
5836
|
+
tw.el.setAttribute("y", String(pos));
|
|
5837
|
+
}
|
|
5838
|
+
break;
|
|
5839
|
+
}
|
|
5840
|
+
case "gridline": {
|
|
5841
|
+
const pos = tw.fromPos + (tw.toPos - tw.fromPos) * eased;
|
|
5842
|
+
if (tw.orientation === "y") {
|
|
5843
|
+
tw.el.setAttribute("y1", String(pos));
|
|
5844
|
+
tw.el.setAttribute("y2", String(pos));
|
|
5845
|
+
} else {
|
|
5846
|
+
tw.el.setAttribute("x1", String(pos));
|
|
5847
|
+
tw.el.setAttribute("x2", String(pos));
|
|
5848
|
+
}
|
|
5849
|
+
break;
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
if (tw.fromOpacity !== void 0 && tw.toOpacity !== void 0) {
|
|
5853
|
+
const opacity = tw.fromOpacity + (tw.toOpacity - tw.fromOpacity) * eased;
|
|
5854
|
+
tw.el.style.opacity = String(opacity);
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
function snapTweenToFinal(tw) {
|
|
5858
|
+
switch (tw.tweenType) {
|
|
5859
|
+
case "rect":
|
|
5860
|
+
applyGeomToElement(tw.el, tw.to, tw.mark);
|
|
5861
|
+
break;
|
|
5862
|
+
case "line":
|
|
5863
|
+
applyLinePath(tw.el, tw.toPts, tw.interpolate);
|
|
5864
|
+
break;
|
|
5865
|
+
case "area":
|
|
5866
|
+
applyAreaPaths(tw.el, tw.toTop, tw.toBottom, tw.interpolate, tw.hasStroke);
|
|
5867
|
+
break;
|
|
5868
|
+
case "point":
|
|
5869
|
+
tw.el.setAttribute("cx", String(tw.toCx));
|
|
5870
|
+
tw.el.setAttribute("cy", String(tw.toCy));
|
|
5871
|
+
if (tw.toR !== void 0) {
|
|
5872
|
+
tw.el.setAttribute("r", String(tw.toR));
|
|
5873
|
+
}
|
|
5874
|
+
break;
|
|
5875
|
+
case "rule":
|
|
5876
|
+
case "tick": {
|
|
5877
|
+
const lineEl = resolveLineElement(tw.el);
|
|
5878
|
+
lineEl.setAttribute("x1", String(tw.toX1));
|
|
5879
|
+
lineEl.setAttribute("y1", String(tw.toY1));
|
|
5880
|
+
lineEl.setAttribute("x2", String(tw.toX2));
|
|
5881
|
+
lineEl.setAttribute("y2", String(tw.toY2));
|
|
5882
|
+
break;
|
|
5883
|
+
}
|
|
5884
|
+
case "textMark":
|
|
5885
|
+
tw.el.setAttribute("x", String(tw.toX));
|
|
5886
|
+
tw.el.setAttribute("y", String(tw.toY));
|
|
5887
|
+
break;
|
|
5888
|
+
case "axisTick":
|
|
5889
|
+
if (!tw.crossfade) {
|
|
5890
|
+
if (tw.posAttr === "x") {
|
|
5891
|
+
tw.el.setAttribute("x", String(tw.toPos));
|
|
5892
|
+
} else {
|
|
5893
|
+
tw.el.setAttribute("y", String(tw.toPos));
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
break;
|
|
5897
|
+
case "gridline":
|
|
5898
|
+
if (tw.orientation === "y") {
|
|
5899
|
+
tw.el.setAttribute("y1", String(tw.toPos));
|
|
5900
|
+
tw.el.setAttribute("y2", String(tw.toPos));
|
|
5901
|
+
} else {
|
|
5902
|
+
tw.el.setAttribute("x1", String(tw.toPos));
|
|
5903
|
+
tw.el.setAttribute("x2", String(tw.toPos));
|
|
5904
|
+
}
|
|
5905
|
+
break;
|
|
5906
|
+
}
|
|
5907
|
+
if (tw.toOpacity !== void 0) {
|
|
5908
|
+
tw.el.style.opacity = String(tw.toOpacity);
|
|
5909
|
+
}
|
|
5910
|
+
if (tw.tweenType === "point" && tw.suppressedOpacity !== void 0) {
|
|
5911
|
+
tw.el.setAttribute("opacity", tw.suppressedOpacity);
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
function resolveGhostGradientFill(mark, ghostGradientMap) {
|
|
5915
|
+
if (!mark.fill || typeof mark.fill === "string" || ghostGradientMap.size === 0) return mark;
|
|
5916
|
+
const resolved = resolveMarkFill(mark.fill, ghostGradientMap);
|
|
5917
|
+
return { ...mark, fill: resolved };
|
|
5918
|
+
}
|
|
5919
|
+
function buildRectTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, ghostGradientMap, fromSnapshot) {
|
|
5920
|
+
const prevRects = prevLayout.marks.filter((m2) => m2.type === "rect");
|
|
5921
|
+
const nextRects = nextLayout.marks.filter((m2) => m2.type === "rect");
|
|
5922
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
5923
|
+
for (const m2 of prevRects) {
|
|
5924
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
5925
|
+
}
|
|
5926
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
5927
|
+
for (const m2 of nextRects) {
|
|
5928
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
5929
|
+
}
|
|
5930
|
+
for (const [key, next] of nextByKey) {
|
|
5931
|
+
const prev = prevByKey.get(key);
|
|
5932
|
+
if (!prev) continue;
|
|
5933
|
+
const el = marksContainer.querySelector(`[data-key="${key}"]`);
|
|
5934
|
+
if (!el) continue;
|
|
5935
|
+
let fromGeom = geomFromMark(prev);
|
|
5936
|
+
const snap = fromSnapshot?.get(key);
|
|
5937
|
+
if (snap && snap.type === "rect") {
|
|
5938
|
+
fromGeom = { x: snap.x, y: snap.y, width: snap.width, height: snap.height };
|
|
5939
|
+
}
|
|
5940
|
+
tweens.push({
|
|
5941
|
+
tweenType: "rect",
|
|
5942
|
+
kind: "update",
|
|
5943
|
+
el,
|
|
5944
|
+
from: fromGeom,
|
|
5945
|
+
to: geomFromMark(next),
|
|
5946
|
+
mark: next
|
|
5947
|
+
});
|
|
5948
|
+
}
|
|
5949
|
+
for (const [key, next] of nextByKey) {
|
|
5950
|
+
if (prevByKey.has(key)) continue;
|
|
5951
|
+
const el = marksContainer.querySelector(`[data-key="${key}"]`);
|
|
5952
|
+
if (!el) continue;
|
|
5953
|
+
let fromGeom;
|
|
5954
|
+
if (next.orient === "horizontal") {
|
|
5955
|
+
fromGeom = { x: next.x, y: next.y, width: 0, height: next.height };
|
|
5956
|
+
} else {
|
|
5957
|
+
fromGeom = { x: next.x, y: next.y + next.height, width: next.width, height: 0 };
|
|
5958
|
+
}
|
|
5959
|
+
tweens.push({
|
|
5960
|
+
tweenType: "rect",
|
|
5961
|
+
kind: "enter",
|
|
5962
|
+
el,
|
|
5963
|
+
from: fromGeom,
|
|
5964
|
+
to: geomFromMark(next),
|
|
5965
|
+
mark: next
|
|
5966
|
+
});
|
|
5967
|
+
}
|
|
5968
|
+
for (const [key, prev] of prevByKey) {
|
|
5969
|
+
if (nextByKey.has(key)) continue;
|
|
5970
|
+
const ghostMark = resolveGhostGradientFill(prev, ghostGradientMap);
|
|
5971
|
+
const ghost = renderSingleMark(ghostMark, 0);
|
|
5972
|
+
if (!ghost) continue;
|
|
5973
|
+
ghost.classList.add("oc-ghost");
|
|
5974
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
5975
|
+
ghost.setAttribute("pointer-events", "none");
|
|
5976
|
+
ghost.removeAttribute("data-key");
|
|
5977
|
+
marksContainer.appendChild(ghost);
|
|
5978
|
+
ghosts.push(ghost);
|
|
5979
|
+
let toGeom;
|
|
5980
|
+
if (prev.orient === "horizontal") {
|
|
5981
|
+
toGeom = { x: prev.x, y: prev.y, width: 0, height: prev.height };
|
|
5982
|
+
} else {
|
|
5983
|
+
toGeom = { x: prev.x, y: prev.y + prev.height, width: prev.width, height: 0 };
|
|
5984
|
+
}
|
|
5985
|
+
tweens.push({
|
|
5986
|
+
tweenType: "rect",
|
|
5987
|
+
kind: "exit",
|
|
5988
|
+
el: ghost,
|
|
5989
|
+
from: geomFromMark(prev),
|
|
5990
|
+
to: toGeom,
|
|
5991
|
+
mark: prev,
|
|
5992
|
+
ghost,
|
|
5993
|
+
fromOpacity: 1,
|
|
5994
|
+
toOpacity: 0
|
|
5995
|
+
});
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
function buildLineTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot) {
|
|
5999
|
+
const prevLines = prevLayout.marks.filter((m2) => m2.type === "line");
|
|
6000
|
+
const nextLines = nextLayout.marks.filter((m2) => m2.type === "line");
|
|
6001
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
6002
|
+
for (const m2 of prevLines) {
|
|
6003
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
6004
|
+
}
|
|
6005
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
6006
|
+
for (const m2 of nextLines) {
|
|
6007
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
6008
|
+
}
|
|
6009
|
+
for (const [key, next] of nextByKey) {
|
|
6010
|
+
const prev = prevByKey.get(key);
|
|
6011
|
+
if (!prev) continue;
|
|
6012
|
+
const el = marksContainer.querySelector(`[data-key="${key}"]`);
|
|
6013
|
+
if (!el) continue;
|
|
6014
|
+
const snap = fromSnapshot?.get(key);
|
|
6015
|
+
if (snap && snap.type === "line") {
|
|
6016
|
+
const ghost = el.cloneNode(true);
|
|
6017
|
+
ghost.classList.add("oc-ghost");
|
|
6018
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6019
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6020
|
+
ghost.removeAttribute("data-key");
|
|
6021
|
+
const ghostPath = ghost.querySelector("path");
|
|
6022
|
+
if (ghostPath) ghostPath.setAttribute("d", snap.d);
|
|
6023
|
+
marksContainer.appendChild(ghost);
|
|
6024
|
+
ghosts.push(ghost);
|
|
6025
|
+
tweens.push({
|
|
6026
|
+
tweenType: "line",
|
|
6027
|
+
kind: "exit",
|
|
6028
|
+
el: ghost,
|
|
6029
|
+
fromPts: next.points,
|
|
6030
|
+
// placeholder, not used for path
|
|
6031
|
+
toPts: next.points,
|
|
6032
|
+
finalPoints: next.points,
|
|
6033
|
+
interpolate: next.interpolate,
|
|
6034
|
+
ghost,
|
|
6035
|
+
fromOpacity: 1,
|
|
6036
|
+
toOpacity: 0
|
|
6037
|
+
});
|
|
6038
|
+
tweens.push({
|
|
6039
|
+
tweenType: "line",
|
|
6040
|
+
kind: "enter",
|
|
6041
|
+
el,
|
|
6042
|
+
fromPts: next.points,
|
|
6043
|
+
toPts: next.points,
|
|
6044
|
+
finalPoints: next.points,
|
|
6045
|
+
interpolate: next.interpolate,
|
|
6046
|
+
fromOpacity: 0,
|
|
6047
|
+
toOpacity: 1
|
|
6048
|
+
});
|
|
6049
|
+
continue;
|
|
6050
|
+
}
|
|
6051
|
+
const prevPKs = prev.pointKeys ?? prev.points.map((_, i) => `${i}`);
|
|
6052
|
+
const nextPKs = next.pointKeys ?? next.points.map((_, i) => `${i}`);
|
|
6053
|
+
const prevKeySet = new Set(prevPKs);
|
|
6054
|
+
const hasSurvivors = nextPKs.some((k) => prevKeySet.has(k));
|
|
6055
|
+
if (!hasSurvivors) {
|
|
6056
|
+
const ghost = renderSingleMark(prev, 0);
|
|
6057
|
+
if (ghost) {
|
|
6058
|
+
ghost.classList.add("oc-ghost");
|
|
6059
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6060
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6061
|
+
ghost.removeAttribute("data-key");
|
|
6062
|
+
marksContainer.appendChild(ghost);
|
|
6063
|
+
ghosts.push(ghost);
|
|
6064
|
+
tweens.push({
|
|
6065
|
+
tweenType: "line",
|
|
6066
|
+
kind: "exit",
|
|
6067
|
+
el: ghost,
|
|
6068
|
+
fromPts: prev.points,
|
|
6069
|
+
toPts: prev.points,
|
|
6070
|
+
finalPoints: prev.points,
|
|
6071
|
+
interpolate: prev.interpolate,
|
|
6072
|
+
ghost,
|
|
6073
|
+
fromOpacity: 1,
|
|
6074
|
+
toOpacity: 0
|
|
6075
|
+
});
|
|
6076
|
+
}
|
|
6077
|
+
tweens.push({
|
|
6078
|
+
tweenType: "line",
|
|
6079
|
+
kind: "enter",
|
|
6080
|
+
el,
|
|
6081
|
+
fromPts: next.points,
|
|
6082
|
+
toPts: next.points,
|
|
6083
|
+
finalPoints: next.points,
|
|
6084
|
+
interpolate: next.interpolate,
|
|
6085
|
+
fromOpacity: 0,
|
|
6086
|
+
toOpacity: 1
|
|
6087
|
+
});
|
|
6088
|
+
} else {
|
|
6089
|
+
const [fromPts, toPts] = normalizePointArrays(prev.points, next.points, prevPKs, nextPKs);
|
|
6090
|
+
tweens.push({
|
|
6091
|
+
tweenType: "line",
|
|
6092
|
+
kind: "update",
|
|
6093
|
+
el,
|
|
6094
|
+
fromPts,
|
|
6095
|
+
toPts,
|
|
6096
|
+
finalPoints: next.points,
|
|
6097
|
+
interpolate: next.interpolate
|
|
6098
|
+
});
|
|
6099
|
+
}
|
|
6100
|
+
}
|
|
6101
|
+
for (const [key, next] of nextByKey) {
|
|
6102
|
+
if (prevByKey.has(key)) continue;
|
|
6103
|
+
const el = marksContainer.querySelector(`[data-key="${key}"]`);
|
|
6104
|
+
if (!el) continue;
|
|
6105
|
+
tweens.push({
|
|
6106
|
+
tweenType: "line",
|
|
6107
|
+
kind: "enter",
|
|
6108
|
+
el,
|
|
6109
|
+
fromPts: next.points,
|
|
6110
|
+
toPts: next.points,
|
|
6111
|
+
finalPoints: next.points,
|
|
6112
|
+
interpolate: next.interpolate,
|
|
6113
|
+
fromOpacity: 0,
|
|
6114
|
+
toOpacity: 1
|
|
6115
|
+
});
|
|
6116
|
+
}
|
|
6117
|
+
for (const [key, prev] of prevByKey) {
|
|
6118
|
+
if (nextByKey.has(key)) continue;
|
|
6119
|
+
const ghost = renderSingleMark(prev, 0);
|
|
6120
|
+
if (!ghost) continue;
|
|
6121
|
+
ghost.classList.add("oc-ghost");
|
|
6122
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6123
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6124
|
+
ghost.removeAttribute("data-key");
|
|
6125
|
+
marksContainer.appendChild(ghost);
|
|
6126
|
+
ghosts.push(ghost);
|
|
6127
|
+
tweens.push({
|
|
6128
|
+
tweenType: "line",
|
|
6129
|
+
kind: "exit",
|
|
6130
|
+
el: ghost,
|
|
6131
|
+
fromPts: prev.points,
|
|
6132
|
+
toPts: prev.points,
|
|
6133
|
+
finalPoints: prev.points,
|
|
6134
|
+
interpolate: prev.interpolate,
|
|
6135
|
+
ghost,
|
|
6136
|
+
fromOpacity: 1,
|
|
6137
|
+
toOpacity: 0
|
|
6138
|
+
});
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
function buildAreaTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, ghostGradientMap, fromSnapshot) {
|
|
6142
|
+
const prevAreas = prevLayout.marks.filter((m2) => m2.type === "area");
|
|
6143
|
+
const nextAreas = nextLayout.marks.filter((m2) => m2.type === "area");
|
|
6144
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
6145
|
+
for (const m2 of prevAreas) {
|
|
6146
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
6147
|
+
}
|
|
6148
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
6149
|
+
for (const m2 of nextAreas) {
|
|
6150
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
6151
|
+
}
|
|
6152
|
+
for (const [key, next] of nextByKey) {
|
|
6153
|
+
const prev = prevByKey.get(key);
|
|
6154
|
+
if (!prev) continue;
|
|
6155
|
+
const el = marksContainer.querySelector(`[data-key="${key}"]`);
|
|
6156
|
+
if (!el) continue;
|
|
6157
|
+
const snap = fromSnapshot?.get(key);
|
|
6158
|
+
if (snap && snap.type === "area") {
|
|
6159
|
+
const ghost = el.cloneNode(true);
|
|
6160
|
+
ghost.classList.add("oc-ghost");
|
|
6161
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6162
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6163
|
+
ghost.removeAttribute("data-key");
|
|
6164
|
+
const ghostPaths = ghost.querySelectorAll("path");
|
|
6165
|
+
if (ghostPaths[0]) ghostPaths[0].setAttribute("d", snap.d);
|
|
6166
|
+
if (ghostPaths[1] && snap.topD) ghostPaths[1].setAttribute("d", snap.topD);
|
|
6167
|
+
marksContainer.appendChild(ghost);
|
|
6168
|
+
ghosts.push(ghost);
|
|
6169
|
+
tweens.push({
|
|
6170
|
+
tweenType: "area",
|
|
6171
|
+
kind: "exit",
|
|
6172
|
+
el: ghost,
|
|
6173
|
+
fromTop: next.topPoints,
|
|
6174
|
+
toTop: next.topPoints,
|
|
6175
|
+
fromBottom: next.bottomPoints,
|
|
6176
|
+
toBottom: next.bottomPoints,
|
|
6177
|
+
finalTopPoints: next.topPoints,
|
|
6178
|
+
finalBottomPoints: next.bottomPoints,
|
|
6179
|
+
interpolate: next.interpolate,
|
|
6180
|
+
hasStroke: !!next.stroke && !!next.topPath,
|
|
6181
|
+
ghost,
|
|
6182
|
+
fromOpacity: 1,
|
|
6183
|
+
toOpacity: 0
|
|
6184
|
+
});
|
|
6185
|
+
tweens.push({
|
|
6186
|
+
tweenType: "area",
|
|
6187
|
+
kind: "enter",
|
|
6188
|
+
el,
|
|
6189
|
+
fromTop: next.topPoints,
|
|
6190
|
+
toTop: next.topPoints,
|
|
6191
|
+
fromBottom: next.bottomPoints,
|
|
6192
|
+
toBottom: next.bottomPoints,
|
|
6193
|
+
finalTopPoints: next.topPoints,
|
|
6194
|
+
finalBottomPoints: next.bottomPoints,
|
|
6195
|
+
interpolate: next.interpolate,
|
|
6196
|
+
hasStroke: !!next.stroke && !!next.topPath,
|
|
6197
|
+
fromOpacity: 0,
|
|
6198
|
+
toOpacity: 1
|
|
6199
|
+
});
|
|
6200
|
+
continue;
|
|
6201
|
+
}
|
|
6202
|
+
const prevPKs = prev.pointKeys ?? prev.topPoints.map((_, i) => `${i}`);
|
|
6203
|
+
const nextPKs = next.pointKeys ?? next.topPoints.map((_, i) => `${i}`);
|
|
6204
|
+
const prevKeySet = new Set(prevPKs);
|
|
6205
|
+
const hasSurvivors = nextPKs.some((k) => prevKeySet.has(k));
|
|
6206
|
+
if (!hasSurvivors) {
|
|
6207
|
+
const ghost = renderSingleMark(prev, 0);
|
|
6208
|
+
if (ghost) {
|
|
6209
|
+
ghost.classList.add("oc-ghost");
|
|
6210
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6211
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6212
|
+
ghost.removeAttribute("data-key");
|
|
6213
|
+
marksContainer.appendChild(ghost);
|
|
6214
|
+
ghosts.push(ghost);
|
|
6215
|
+
tweens.push({
|
|
6216
|
+
tweenType: "area",
|
|
6217
|
+
kind: "exit",
|
|
6218
|
+
el: ghost,
|
|
6219
|
+
fromTop: prev.topPoints,
|
|
6220
|
+
toTop: prev.topPoints,
|
|
6221
|
+
fromBottom: prev.bottomPoints,
|
|
6222
|
+
toBottom: prev.bottomPoints,
|
|
6223
|
+
finalTopPoints: prev.topPoints,
|
|
6224
|
+
finalBottomPoints: prev.bottomPoints,
|
|
6225
|
+
interpolate: prev.interpolate,
|
|
6226
|
+
hasStroke: !!prev.stroke && !!prev.topPath,
|
|
6227
|
+
ghost,
|
|
6228
|
+
fromOpacity: 1,
|
|
6229
|
+
toOpacity: 0
|
|
6230
|
+
});
|
|
6231
|
+
}
|
|
6232
|
+
tweens.push({
|
|
6233
|
+
tweenType: "area",
|
|
6234
|
+
kind: "enter",
|
|
6235
|
+
el,
|
|
6236
|
+
fromTop: next.topPoints,
|
|
6237
|
+
toTop: next.topPoints,
|
|
6238
|
+
fromBottom: next.bottomPoints,
|
|
6239
|
+
toBottom: next.bottomPoints,
|
|
6240
|
+
finalTopPoints: next.topPoints,
|
|
6241
|
+
finalBottomPoints: next.bottomPoints,
|
|
6242
|
+
interpolate: next.interpolate,
|
|
6243
|
+
hasStroke: !!next.stroke && !!next.topPath,
|
|
6244
|
+
fromOpacity: 0,
|
|
6245
|
+
toOpacity: 1
|
|
6246
|
+
});
|
|
6247
|
+
} else {
|
|
6248
|
+
const [fromTop, toTop] = normalizePointArrays(
|
|
6249
|
+
prev.topPoints,
|
|
6250
|
+
next.topPoints,
|
|
6251
|
+
prevPKs,
|
|
6252
|
+
nextPKs
|
|
6253
|
+
);
|
|
6254
|
+
const [fromBottom, toBottom] = normalizePointArrays(
|
|
6255
|
+
prev.bottomPoints,
|
|
6256
|
+
next.bottomPoints,
|
|
6257
|
+
prevPKs,
|
|
6258
|
+
nextPKs
|
|
6259
|
+
);
|
|
6260
|
+
tweens.push({
|
|
6261
|
+
tweenType: "area",
|
|
6262
|
+
kind: "update",
|
|
6263
|
+
el,
|
|
6264
|
+
fromTop,
|
|
6265
|
+
toTop,
|
|
6266
|
+
fromBottom,
|
|
6267
|
+
toBottom,
|
|
6268
|
+
finalTopPoints: next.topPoints,
|
|
6269
|
+
finalBottomPoints: next.bottomPoints,
|
|
6270
|
+
interpolate: next.interpolate,
|
|
6271
|
+
hasStroke: !!next.stroke && !!next.topPath
|
|
6272
|
+
});
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
for (const [key, next] of nextByKey) {
|
|
6276
|
+
if (prevByKey.has(key)) continue;
|
|
6277
|
+
const el = marksContainer.querySelector(`[data-key="${key}"]`);
|
|
6278
|
+
if (!el) continue;
|
|
6279
|
+
tweens.push({
|
|
6280
|
+
tweenType: "area",
|
|
6281
|
+
kind: "enter",
|
|
6282
|
+
el,
|
|
6283
|
+
fromTop: next.topPoints,
|
|
6284
|
+
toTop: next.topPoints,
|
|
6285
|
+
fromBottom: next.bottomPoints,
|
|
6286
|
+
toBottom: next.bottomPoints,
|
|
6287
|
+
finalTopPoints: next.topPoints,
|
|
6288
|
+
finalBottomPoints: next.bottomPoints,
|
|
6289
|
+
interpolate: next.interpolate,
|
|
6290
|
+
hasStroke: !!next.stroke && !!next.topPath,
|
|
6291
|
+
fromOpacity: 0,
|
|
6292
|
+
toOpacity: 1
|
|
6293
|
+
});
|
|
6294
|
+
}
|
|
6295
|
+
for (const [key, prev] of prevByKey) {
|
|
6296
|
+
if (nextByKey.has(key)) continue;
|
|
6297
|
+
const ghostMark = resolveGhostGradientFill(prev, ghostGradientMap);
|
|
6298
|
+
const ghost = renderSingleMark(ghostMark, 0);
|
|
6299
|
+
if (!ghost) continue;
|
|
6300
|
+
ghost.classList.add("oc-ghost");
|
|
6301
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6302
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6303
|
+
ghost.removeAttribute("data-key");
|
|
6304
|
+
marksContainer.appendChild(ghost);
|
|
6305
|
+
ghosts.push(ghost);
|
|
6306
|
+
tweens.push({
|
|
6307
|
+
tweenType: "area",
|
|
6308
|
+
kind: "exit",
|
|
6309
|
+
el: ghost,
|
|
6310
|
+
fromTop: prev.topPoints,
|
|
6311
|
+
toTop: prev.topPoints,
|
|
6312
|
+
fromBottom: prev.bottomPoints,
|
|
6313
|
+
toBottom: prev.bottomPoints,
|
|
6314
|
+
finalTopPoints: prev.topPoints,
|
|
6315
|
+
finalBottomPoints: prev.bottomPoints,
|
|
6316
|
+
interpolate: prev.interpolate,
|
|
6317
|
+
hasStroke: !!prev.stroke && !!prev.topPath,
|
|
6318
|
+
ghost,
|
|
6319
|
+
fromOpacity: 1,
|
|
6320
|
+
toOpacity: 0
|
|
6321
|
+
});
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
function buildPointTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, ghostGradientMap, fromSnapshot) {
|
|
6325
|
+
const prevPoints = prevLayout.marks.filter((m2) => m2.type === "point");
|
|
6326
|
+
const nextPoints = nextLayout.marks.filter((m2) => m2.type === "point");
|
|
6327
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
6328
|
+
for (const m2 of prevPoints) {
|
|
6329
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
6330
|
+
}
|
|
6331
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
6332
|
+
for (const m2 of nextPoints) {
|
|
6333
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
6334
|
+
}
|
|
6335
|
+
for (const [key, next] of nextByKey) {
|
|
6336
|
+
const prev = prevByKey.get(key);
|
|
6337
|
+
if (!prev) continue;
|
|
6338
|
+
const el = marksContainer.querySelector(
|
|
6339
|
+
`circle.oc-mark-point[data-key="${key}"]`
|
|
6340
|
+
);
|
|
6341
|
+
if (!el) continue;
|
|
6342
|
+
const renderedOpacity = el.getAttribute("opacity");
|
|
6343
|
+
const isSuppressed = renderedOpacity === "0";
|
|
6344
|
+
let fromCx = prev.cx;
|
|
6345
|
+
let fromCy = prev.cy;
|
|
6346
|
+
let fromR = prev.r !== next.r ? prev.r : void 0;
|
|
6347
|
+
const snap = fromSnapshot?.get(key);
|
|
6348
|
+
if (snap && snap.type === "point") {
|
|
6349
|
+
fromCx = snap.cx;
|
|
6350
|
+
fromCy = snap.cy;
|
|
6351
|
+
if (snap.r !== void 0 && snap.r !== next.r) {
|
|
6352
|
+
fromR = snap.r;
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
tweens.push({
|
|
6356
|
+
tweenType: "point",
|
|
6357
|
+
kind: "update",
|
|
6358
|
+
el,
|
|
6359
|
+
fromCx,
|
|
6360
|
+
fromCy,
|
|
6361
|
+
toCx: next.cx,
|
|
6362
|
+
toCy: next.cy,
|
|
6363
|
+
fromR,
|
|
6364
|
+
toR: fromR !== void 0 ? next.r : void 0,
|
|
6365
|
+
// Do NOT tween opacity for updated points - preserve renderer state
|
|
6366
|
+
suppressedOpacity: isSuppressed ? "0" : void 0
|
|
6367
|
+
});
|
|
6368
|
+
}
|
|
6369
|
+
for (const [key, next] of nextByKey) {
|
|
6370
|
+
if (prevByKey.has(key)) continue;
|
|
6371
|
+
const el = marksContainer.querySelector(
|
|
6372
|
+
`circle.oc-mark-point[data-key="${key}"]`
|
|
6373
|
+
);
|
|
6374
|
+
if (!el) continue;
|
|
6375
|
+
const renderedOpacity = el.getAttribute("opacity");
|
|
6376
|
+
const targetOpacity = renderedOpacity != null ? Number(renderedOpacity) : 1;
|
|
6377
|
+
tweens.push({
|
|
6378
|
+
tweenType: "point",
|
|
6379
|
+
kind: "enter",
|
|
6380
|
+
el,
|
|
6381
|
+
fromCx: next.cx,
|
|
6382
|
+
fromCy: next.cy,
|
|
6383
|
+
toCx: next.cx,
|
|
6384
|
+
toCy: next.cy,
|
|
6385
|
+
fromOpacity: 0,
|
|
6386
|
+
toOpacity: targetOpacity,
|
|
6387
|
+
suppressedOpacity: renderedOpacity === "0" ? "0" : void 0
|
|
6388
|
+
});
|
|
6389
|
+
}
|
|
6390
|
+
for (const [key, prev] of prevByKey) {
|
|
6391
|
+
if (nextByKey.has(key)) continue;
|
|
6392
|
+
const ghostMark = resolveGhostGradientFill(prev, ghostGradientMap);
|
|
6393
|
+
const ghost = renderSingleMark(ghostMark, 0);
|
|
6394
|
+
if (!ghost) continue;
|
|
6395
|
+
ghost.classList.add("oc-ghost");
|
|
6396
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6397
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6398
|
+
ghost.removeAttribute("data-key");
|
|
6399
|
+
marksContainer.appendChild(ghost);
|
|
6400
|
+
ghosts.push(ghost);
|
|
6401
|
+
tweens.push({
|
|
6402
|
+
tweenType: "point",
|
|
6403
|
+
kind: "exit",
|
|
6404
|
+
el: ghost,
|
|
6405
|
+
fromCx: prev.cx,
|
|
6406
|
+
fromCy: prev.cy,
|
|
6407
|
+
toCx: prev.cx,
|
|
6408
|
+
toCy: prev.cy,
|
|
6409
|
+
ghost,
|
|
6410
|
+
fromOpacity: 1,
|
|
6411
|
+
toOpacity: 0
|
|
6412
|
+
});
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
function buildRuleTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot) {
|
|
6416
|
+
const prevRules = prevLayout.marks.filter((m2) => m2.type === "rule");
|
|
6417
|
+
const nextRules = nextLayout.marks.filter((m2) => m2.type === "rule");
|
|
6418
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
6419
|
+
for (const m2 of prevRules) {
|
|
6420
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
6421
|
+
}
|
|
6422
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
6423
|
+
for (const m2 of nextRules) {
|
|
6424
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
6425
|
+
}
|
|
6426
|
+
for (const [key, next] of nextByKey) {
|
|
6427
|
+
const prev = prevByKey.get(key);
|
|
6428
|
+
if (!prev) continue;
|
|
6429
|
+
const el = marksContainer.querySelector(
|
|
6430
|
+
`.oc-mark-rule[data-key="${key}"]`
|
|
6431
|
+
);
|
|
6432
|
+
if (!el) continue;
|
|
6433
|
+
let fromX1 = prev.x1;
|
|
6434
|
+
let fromY1 = prev.y1;
|
|
6435
|
+
let fromX2 = prev.x2;
|
|
6436
|
+
let fromY2 = prev.y2;
|
|
6437
|
+
const snap = fromSnapshot?.get(key);
|
|
6438
|
+
if (snap && snap.type === "rule") {
|
|
6439
|
+
fromX1 = snap.x1;
|
|
6440
|
+
fromY1 = snap.y1;
|
|
6441
|
+
fromX2 = snap.x2;
|
|
6442
|
+
fromY2 = snap.y2;
|
|
6443
|
+
}
|
|
6444
|
+
tweens.push({
|
|
6445
|
+
tweenType: "rule",
|
|
6446
|
+
kind: "update",
|
|
6447
|
+
el,
|
|
6448
|
+
fromX1,
|
|
6449
|
+
fromY1,
|
|
6450
|
+
fromX2,
|
|
6451
|
+
fromY2,
|
|
6452
|
+
toX1: next.x1,
|
|
6453
|
+
toY1: next.y1,
|
|
6454
|
+
toX2: next.x2,
|
|
6455
|
+
toY2: next.y2
|
|
6456
|
+
});
|
|
6457
|
+
}
|
|
6458
|
+
for (const [key, next] of nextByKey) {
|
|
6459
|
+
if (prevByKey.has(key)) continue;
|
|
6460
|
+
const el = marksContainer.querySelector(
|
|
6461
|
+
`.oc-mark-rule[data-key="${key}"]`
|
|
6462
|
+
);
|
|
6463
|
+
if (!el) continue;
|
|
6464
|
+
tweens.push({
|
|
6465
|
+
tweenType: "rule",
|
|
6466
|
+
kind: "enter",
|
|
6467
|
+
el,
|
|
6468
|
+
fromX1: next.x1,
|
|
6469
|
+
fromY1: next.y1,
|
|
6470
|
+
fromX2: next.x2,
|
|
6471
|
+
fromY2: next.y2,
|
|
6472
|
+
toX1: next.x1,
|
|
6473
|
+
toY1: next.y1,
|
|
6474
|
+
toX2: next.x2,
|
|
6475
|
+
toY2: next.y2,
|
|
6476
|
+
fromOpacity: 0,
|
|
6477
|
+
toOpacity: 1
|
|
6478
|
+
});
|
|
6479
|
+
}
|
|
6480
|
+
for (const [key, prev] of prevByKey) {
|
|
6481
|
+
if (nextByKey.has(key)) continue;
|
|
6482
|
+
const ghost = renderSingleMark(prev, 0);
|
|
6483
|
+
if (!ghost) continue;
|
|
6484
|
+
ghost.classList.add("oc-ghost");
|
|
6485
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6486
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6487
|
+
ghost.removeAttribute("data-key");
|
|
6488
|
+
marksContainer.appendChild(ghost);
|
|
6489
|
+
ghosts.push(ghost);
|
|
6490
|
+
tweens.push({
|
|
6491
|
+
tweenType: "rule",
|
|
6492
|
+
kind: "exit",
|
|
6493
|
+
el: ghost,
|
|
6494
|
+
fromX1: prev.x1,
|
|
6495
|
+
fromY1: prev.y1,
|
|
6496
|
+
fromX2: prev.x2,
|
|
6497
|
+
fromY2: prev.y2,
|
|
6498
|
+
toX1: prev.x1,
|
|
6499
|
+
toY1: prev.y1,
|
|
6500
|
+
toX2: prev.x2,
|
|
6501
|
+
toY2: prev.y2,
|
|
6502
|
+
ghost,
|
|
6503
|
+
fromOpacity: 1,
|
|
6504
|
+
toOpacity: 0
|
|
6505
|
+
});
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
function buildTickTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot) {
|
|
6509
|
+
const prevTicks = prevLayout.marks.filter((m2) => m2.type === "tick");
|
|
6510
|
+
const nextTicks = nextLayout.marks.filter((m2) => m2.type === "tick");
|
|
6511
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
6512
|
+
for (const m2 of prevTicks) {
|
|
6513
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
6514
|
+
}
|
|
6515
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
6516
|
+
for (const m2 of nextTicks) {
|
|
6517
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
6518
|
+
}
|
|
6519
|
+
function tickEndpoints(t) {
|
|
6520
|
+
const half = t.length / 2;
|
|
6521
|
+
if (t.orient === "vertical") {
|
|
6522
|
+
return { x1: t.x, y1: t.y - half, x2: t.x, y2: t.y + half };
|
|
6523
|
+
}
|
|
6524
|
+
return { x1: t.x - half, y1: t.y, x2: t.x + half, y2: t.y };
|
|
6525
|
+
}
|
|
6526
|
+
for (const [key, next] of nextByKey) {
|
|
6527
|
+
const prev = prevByKey.get(key);
|
|
6528
|
+
if (!prev) continue;
|
|
6529
|
+
const el = marksContainer.querySelector(
|
|
6530
|
+
`.oc-mark-tick[data-key="${key}"]`
|
|
6531
|
+
);
|
|
6532
|
+
if (!el) continue;
|
|
6533
|
+
let pEnd = tickEndpoints(prev);
|
|
6534
|
+
const nEnd = tickEndpoints(next);
|
|
6535
|
+
const snap = fromSnapshot?.get(key);
|
|
6536
|
+
if (snap && snap.type === "tick") {
|
|
6537
|
+
pEnd = { x1: snap.x1, y1: snap.y1, x2: snap.x2, y2: snap.y2 };
|
|
6538
|
+
}
|
|
6539
|
+
tweens.push({
|
|
6540
|
+
tweenType: "tick",
|
|
6541
|
+
kind: "update",
|
|
6542
|
+
el,
|
|
6543
|
+
fromX1: pEnd.x1,
|
|
6544
|
+
fromY1: pEnd.y1,
|
|
6545
|
+
fromX2: pEnd.x2,
|
|
6546
|
+
fromY2: pEnd.y2,
|
|
6547
|
+
toX1: nEnd.x1,
|
|
6548
|
+
toY1: nEnd.y1,
|
|
6549
|
+
toX2: nEnd.x2,
|
|
6550
|
+
toY2: nEnd.y2
|
|
6551
|
+
});
|
|
6552
|
+
}
|
|
6553
|
+
for (const [key, next] of nextByKey) {
|
|
6554
|
+
if (prevByKey.has(key)) continue;
|
|
6555
|
+
const el = marksContainer.querySelector(
|
|
6556
|
+
`.oc-mark-tick[data-key="${key}"]`
|
|
6557
|
+
);
|
|
6558
|
+
if (!el) continue;
|
|
6559
|
+
const nEnd = tickEndpoints(next);
|
|
6560
|
+
tweens.push({
|
|
6561
|
+
tweenType: "tick",
|
|
6562
|
+
kind: "enter",
|
|
6563
|
+
el,
|
|
6564
|
+
fromX1: nEnd.x1,
|
|
6565
|
+
fromY1: nEnd.y1,
|
|
6566
|
+
fromX2: nEnd.x2,
|
|
6567
|
+
fromY2: nEnd.y2,
|
|
6568
|
+
toX1: nEnd.x1,
|
|
6569
|
+
toY1: nEnd.y1,
|
|
6570
|
+
toX2: nEnd.x2,
|
|
6571
|
+
toY2: nEnd.y2,
|
|
6572
|
+
fromOpacity: 0,
|
|
6573
|
+
toOpacity: 1
|
|
6574
|
+
});
|
|
6575
|
+
}
|
|
6576
|
+
for (const [key, prev] of prevByKey) {
|
|
6577
|
+
if (nextByKey.has(key)) continue;
|
|
6578
|
+
const ghost = renderSingleMark(prev, 0);
|
|
6579
|
+
if (!ghost) continue;
|
|
6580
|
+
ghost.classList.add("oc-ghost");
|
|
6581
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6582
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6583
|
+
ghost.removeAttribute("data-key");
|
|
6584
|
+
marksContainer.appendChild(ghost);
|
|
6585
|
+
ghosts.push(ghost);
|
|
6586
|
+
const pEnd = tickEndpoints(prev);
|
|
6587
|
+
tweens.push({
|
|
6588
|
+
tweenType: "tick",
|
|
6589
|
+
kind: "exit",
|
|
6590
|
+
el: ghost,
|
|
6591
|
+
fromX1: pEnd.x1,
|
|
6592
|
+
fromY1: pEnd.y1,
|
|
6593
|
+
fromX2: pEnd.x2,
|
|
6594
|
+
fromY2: pEnd.y2,
|
|
6595
|
+
toX1: pEnd.x1,
|
|
6596
|
+
toY1: pEnd.y1,
|
|
6597
|
+
toX2: pEnd.x2,
|
|
6598
|
+
toY2: pEnd.y2,
|
|
6599
|
+
ghost,
|
|
6600
|
+
fromOpacity: 1,
|
|
6601
|
+
toOpacity: 0
|
|
6602
|
+
});
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
function buildTextMarkTweens(prevLayout, nextLayout, marksContainer, tweens, ghosts, fromSnapshot) {
|
|
6606
|
+
const prevTexts = prevLayout.marks.filter((m2) => m2.type === "textMark");
|
|
6607
|
+
const nextTexts = nextLayout.marks.filter((m2) => m2.type === "textMark");
|
|
6608
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
6609
|
+
for (const m2 of prevTexts) {
|
|
6610
|
+
if (m2.key) prevByKey.set(m2.key, m2);
|
|
6611
|
+
}
|
|
6612
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
6613
|
+
for (const m2 of nextTexts) {
|
|
6614
|
+
if (m2.key) nextByKey.set(m2.key, m2);
|
|
6615
|
+
}
|
|
6616
|
+
for (const [key, next] of nextByKey) {
|
|
6617
|
+
const prev = prevByKey.get(key);
|
|
6618
|
+
if (!prev) continue;
|
|
6619
|
+
const el = marksContainer.querySelector(
|
|
6620
|
+
`.oc-mark-text[data-key="${key}"]`
|
|
6621
|
+
);
|
|
6622
|
+
if (!el) continue;
|
|
6623
|
+
let fromX = prev.x;
|
|
6624
|
+
let fromY = prev.y;
|
|
6625
|
+
const snap = fromSnapshot?.get(key);
|
|
6626
|
+
if (snap && snap.type === "textMark") {
|
|
6627
|
+
fromX = snap.x;
|
|
6628
|
+
fromY = snap.y;
|
|
6629
|
+
}
|
|
6630
|
+
tweens.push({
|
|
6631
|
+
tweenType: "textMark",
|
|
6632
|
+
kind: "update",
|
|
6633
|
+
el,
|
|
6634
|
+
fromX,
|
|
6635
|
+
fromY,
|
|
6636
|
+
toX: next.x,
|
|
6637
|
+
toY: next.y
|
|
6638
|
+
});
|
|
6639
|
+
}
|
|
6640
|
+
for (const [key, next] of nextByKey) {
|
|
6641
|
+
if (prevByKey.has(key)) continue;
|
|
6642
|
+
const el = marksContainer.querySelector(
|
|
6643
|
+
`.oc-mark-text[data-key="${key}"]`
|
|
6644
|
+
);
|
|
6645
|
+
if (!el) continue;
|
|
6646
|
+
tweens.push({
|
|
6647
|
+
tweenType: "textMark",
|
|
6648
|
+
kind: "enter",
|
|
6649
|
+
el,
|
|
6650
|
+
fromX: next.x,
|
|
6651
|
+
fromY: next.y,
|
|
6652
|
+
toX: next.x,
|
|
6653
|
+
toY: next.y,
|
|
6654
|
+
fromOpacity: 0,
|
|
6655
|
+
toOpacity: 1
|
|
6656
|
+
});
|
|
6657
|
+
}
|
|
6658
|
+
for (const [key, prev] of prevByKey) {
|
|
6659
|
+
if (nextByKey.has(key)) continue;
|
|
6660
|
+
const ghost = renderSingleMark(prev, 0);
|
|
6661
|
+
if (!ghost) continue;
|
|
6662
|
+
ghost.classList.add("oc-ghost");
|
|
6663
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6664
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6665
|
+
ghost.removeAttribute("data-key");
|
|
6666
|
+
marksContainer.appendChild(ghost);
|
|
6667
|
+
ghosts.push(ghost);
|
|
6668
|
+
tweens.push({
|
|
6669
|
+
tweenType: "textMark",
|
|
6670
|
+
kind: "exit",
|
|
6671
|
+
el: ghost,
|
|
6672
|
+
fromX: prev.x,
|
|
6673
|
+
fromY: prev.y,
|
|
6674
|
+
toX: prev.x,
|
|
6675
|
+
toY: prev.y,
|
|
6676
|
+
ghost,
|
|
6677
|
+
fromOpacity: 1,
|
|
6678
|
+
toOpacity: 0
|
|
6679
|
+
});
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
function buildAxisTweens(svg, prevLayout, nextLayout, tweens, ghosts) {
|
|
6683
|
+
buildSingleAxisTweens(svg, prevLayout.axes.x, nextLayout.axes.x, "x", nextLayout, tweens, ghosts);
|
|
6684
|
+
buildSingleAxisTweens(svg, prevLayout.axes.y, nextLayout.axes.y, "y", nextLayout, tweens, ghosts);
|
|
6685
|
+
}
|
|
6686
|
+
function buildSingleAxisTweens(svg, prevAxis, nextAxis, orientation, layout, tweens, ghosts) {
|
|
6687
|
+
if (!prevAxis || !nextAxis) return;
|
|
6688
|
+
const axisClass = `oc-axis-${orientation}`;
|
|
6689
|
+
const axisGroup = svg.querySelector(`.${axisClass}`);
|
|
6690
|
+
if (!axisGroup) return;
|
|
6691
|
+
const isRotated = !!(nextAxis.tickAngle && Math.abs(nextAxis.tickAngle) > 10);
|
|
6692
|
+
const prevTickMap = /* @__PURE__ */ new Map();
|
|
6693
|
+
for (const tick of prevAxis.ticks) {
|
|
6694
|
+
prevTickMap.set(serializeKeyValue(tick.value), tick.position);
|
|
6695
|
+
}
|
|
6696
|
+
const nextTickMap = /* @__PURE__ */ new Map();
|
|
6697
|
+
for (const tick of nextAxis.ticks) {
|
|
6698
|
+
nextTickMap.set(serializeKeyValue(tick.value), tick.position);
|
|
6699
|
+
}
|
|
6700
|
+
const posAttr = orientation === "x" ? "x" : "y";
|
|
6701
|
+
const tickLabels = axisGroup.querySelectorAll(".oc-axis-tick[data-tick-key]");
|
|
6702
|
+
const matchedNextKeys = /* @__PURE__ */ new Set();
|
|
6703
|
+
for (const labelEl of tickLabels) {
|
|
6704
|
+
const key = labelEl.getAttribute("data-tick-key");
|
|
6705
|
+
if (!key) continue;
|
|
6706
|
+
const nextPos = nextTickMap.get(key);
|
|
6707
|
+
const prevPos = prevTickMap.get(key);
|
|
6708
|
+
if (prevPos !== void 0 && nextPos !== void 0) {
|
|
6709
|
+
matchedNextKeys.add(key);
|
|
6710
|
+
tweens.push({
|
|
6711
|
+
tweenType: "axisTick",
|
|
6712
|
+
kind: "update",
|
|
6713
|
+
el: labelEl,
|
|
6714
|
+
posAttr,
|
|
6715
|
+
fromPos: prevPos,
|
|
6716
|
+
toPos: nextPos,
|
|
6717
|
+
crossfade: isRotated
|
|
6718
|
+
});
|
|
6719
|
+
} else if (prevPos === void 0 && nextPos !== void 0) {
|
|
6720
|
+
matchedNextKeys.add(key);
|
|
6721
|
+
tweens.push({
|
|
6722
|
+
tweenType: "axisTick",
|
|
6723
|
+
kind: "enter",
|
|
6724
|
+
el: labelEl,
|
|
6725
|
+
posAttr,
|
|
6726
|
+
fromPos: nextPos,
|
|
6727
|
+
toPos: nextPos,
|
|
6728
|
+
crossfade: false,
|
|
6729
|
+
fromOpacity: 0,
|
|
6730
|
+
toOpacity: 1
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
for (const [key, prevPos] of prevTickMap) {
|
|
6735
|
+
if (nextTickMap.has(key)) continue;
|
|
6736
|
+
const prevTick = prevAxis.ticks.find((t) => serializeKeyValue(t.value) === key);
|
|
6737
|
+
if (!prevTick) continue;
|
|
6738
|
+
const ghost = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
6739
|
+
ghost.setAttribute("class", "oc-axis-tick oc-ghost");
|
|
6740
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6741
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6742
|
+
ghost.textContent = prevTick.label;
|
|
6743
|
+
const area = layout.area;
|
|
6744
|
+
if (orientation === "x") {
|
|
6745
|
+
ghost.setAttribute("x", String(prevPos));
|
|
6746
|
+
const xLabelPad = nextAxis.labelPadding ?? layout.theme.spacing.xAxisLabelPadding;
|
|
6747
|
+
const fontSize = nextAxis.tickLabelStyle.fontSize;
|
|
6748
|
+
ghost.setAttribute("y", String(area.y + area.height + xLabelPad + fontSize * 0.8));
|
|
6749
|
+
ghost.setAttribute("text-anchor", "middle");
|
|
6750
|
+
} else {
|
|
6751
|
+
const TICK_LABEL_OFFSET = 8;
|
|
6752
|
+
ghost.setAttribute("x", String(area.x - TICK_LABEL_OFFSET));
|
|
6753
|
+
ghost.setAttribute("y", String(prevPos));
|
|
6754
|
+
ghost.setAttribute("text-anchor", "end");
|
|
6755
|
+
ghost.setAttribute("dominant-baseline", "central");
|
|
6756
|
+
}
|
|
6757
|
+
axisGroup.appendChild(ghost);
|
|
6758
|
+
ghosts.push(ghost);
|
|
6759
|
+
tweens.push({
|
|
6760
|
+
tweenType: "axisTick",
|
|
6761
|
+
kind: "exit",
|
|
6762
|
+
el: ghost,
|
|
6763
|
+
posAttr,
|
|
6764
|
+
fromPos: prevPos,
|
|
6765
|
+
toPos: prevPos,
|
|
6766
|
+
crossfade: false,
|
|
6767
|
+
ghost,
|
|
6768
|
+
fromOpacity: 1,
|
|
6769
|
+
toOpacity: 0
|
|
6770
|
+
});
|
|
6771
|
+
}
|
|
6772
|
+
const prevGridMap = /* @__PURE__ */ new Map();
|
|
6773
|
+
for (const gridline of prevAxis.gridlines) {
|
|
6774
|
+
prevGridMap.set(String(gridline.position), gridline.position);
|
|
6775
|
+
}
|
|
6776
|
+
const prevGridByValue = /* @__PURE__ */ new Map();
|
|
6777
|
+
for (const tick of prevAxis.ticks) {
|
|
6778
|
+
const key = serializeKeyValue(tick.value);
|
|
6779
|
+
const matchingGridline = prevAxis.gridlines.find((g) => g.position === tick.position);
|
|
6780
|
+
if (matchingGridline) {
|
|
6781
|
+
prevGridByValue.set(key, matchingGridline.position);
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
const nextGridByValue = /* @__PURE__ */ new Map();
|
|
6785
|
+
for (const tick of nextAxis.ticks) {
|
|
6786
|
+
const key = serializeKeyValue(tick.value);
|
|
6787
|
+
const matchingGridline = nextAxis.gridlines.find((g) => g.position === tick.position);
|
|
6788
|
+
if (matchingGridline) {
|
|
6789
|
+
nextGridByValue.set(key, matchingGridline.position);
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
const gridlines = axisGroup.querySelectorAll(".oc-gridline[data-tick-key]");
|
|
6793
|
+
const matchedGridKeys = /* @__PURE__ */ new Set();
|
|
6794
|
+
for (const glEl of gridlines) {
|
|
6795
|
+
const key = glEl.getAttribute("data-tick-key");
|
|
6796
|
+
if (!key) continue;
|
|
6797
|
+
const nextPos = nextGridByValue.get(key);
|
|
6798
|
+
const prevPos = prevGridByValue.get(key);
|
|
6799
|
+
if (prevPos !== void 0 && nextPos !== void 0) {
|
|
6800
|
+
matchedGridKeys.add(key);
|
|
6801
|
+
tweens.push({
|
|
6802
|
+
tweenType: "gridline",
|
|
6803
|
+
kind: "update",
|
|
6804
|
+
el: glEl,
|
|
6805
|
+
orientation,
|
|
6806
|
+
fromPos: prevPos,
|
|
6807
|
+
toPos: nextPos
|
|
6808
|
+
});
|
|
6809
|
+
} else if (prevPos === void 0 && nextPos !== void 0) {
|
|
6810
|
+
matchedGridKeys.add(key);
|
|
6811
|
+
tweens.push({
|
|
6812
|
+
tweenType: "gridline",
|
|
6813
|
+
kind: "enter",
|
|
6814
|
+
el: glEl,
|
|
6815
|
+
orientation,
|
|
6816
|
+
fromPos: nextPos,
|
|
6817
|
+
toPos: nextPos,
|
|
6818
|
+
fromOpacity: 0,
|
|
6819
|
+
toOpacity: 1
|
|
6820
|
+
});
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
for (const [key, prevPos] of prevGridByValue) {
|
|
6824
|
+
if (nextGridByValue.has(key)) continue;
|
|
6825
|
+
const ghost = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
6826
|
+
ghost.setAttribute("class", "oc-gridline oc-ghost");
|
|
6827
|
+
ghost.setAttribute("aria-hidden", "true");
|
|
6828
|
+
ghost.setAttribute("pointer-events", "none");
|
|
6829
|
+
if (orientation === "y") {
|
|
6830
|
+
const area = layout.area;
|
|
6831
|
+
ghost.setAttribute("x1", String(area.x));
|
|
6832
|
+
ghost.setAttribute("y1", String(prevPos));
|
|
6833
|
+
ghost.setAttribute("x2", String(area.x + area.width));
|
|
6834
|
+
ghost.setAttribute("y2", String(prevPos));
|
|
6835
|
+
} else {
|
|
6836
|
+
const area = layout.area;
|
|
6837
|
+
ghost.setAttribute("x1", String(prevPos));
|
|
6838
|
+
ghost.setAttribute("y1", String(area.y));
|
|
6839
|
+
ghost.setAttribute("x2", String(prevPos));
|
|
6840
|
+
ghost.setAttribute("y2", String(area.y + area.height));
|
|
6841
|
+
}
|
|
6842
|
+
axisGroup.appendChild(ghost);
|
|
6843
|
+
ghosts.push(ghost);
|
|
6844
|
+
tweens.push({
|
|
6845
|
+
tweenType: "gridline",
|
|
6846
|
+
kind: "exit",
|
|
6847
|
+
el: ghost,
|
|
6848
|
+
orientation,
|
|
6849
|
+
fromPos: prevPos,
|
|
6850
|
+
toPos: prevPos,
|
|
6851
|
+
ghost,
|
|
6852
|
+
fromOpacity: 1,
|
|
6853
|
+
toOpacity: 0
|
|
6854
|
+
});
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
|
|
5180
6858
|
// src/mount.ts
|
|
5181
6859
|
function resolveDarkMode3(mode) {
|
|
5182
6860
|
if (mode === "force") return true;
|
|
@@ -5225,6 +6903,8 @@ function createChart(container, spec, options) {
|
|
|
5225
6903
|
let isFirstRender = true;
|
|
5226
6904
|
let cleanupAnimations = null;
|
|
5227
6905
|
let pendingResize = false;
|
|
6906
|
+
let transitionHandle = null;
|
|
6907
|
+
let transitionSnapshot = null;
|
|
5228
6908
|
let fontsReloadPending = false;
|
|
5229
6909
|
let selectedElement = options?.selectedElement ?? null;
|
|
5230
6910
|
let overlayElement = null;
|
|
@@ -5549,6 +7229,10 @@ function createChart(container, spec, options) {
|
|
|
5549
7229
|
pendingRender = true;
|
|
5550
7230
|
return;
|
|
5551
7231
|
}
|
|
7232
|
+
if (transitionHandle) {
|
|
7233
|
+
transitionHandle.cancel();
|
|
7234
|
+
transitionHandle = null;
|
|
7235
|
+
}
|
|
5552
7236
|
if (cleanupAnimations) {
|
|
5553
7237
|
cleanupAnimations();
|
|
5554
7238
|
cleanupAnimations = null;
|
|
@@ -5589,7 +7273,7 @@ function createChart(container, spec, options) {
|
|
|
5589
7273
|
srTable = null;
|
|
5590
7274
|
}
|
|
5591
7275
|
currentLayout = compile();
|
|
5592
|
-
const shouldAnimate = isFirstRender && !!currentLayout.animation?.
|
|
7276
|
+
const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;
|
|
5593
7277
|
const crosshair = !!currentLayout.crosshair;
|
|
5594
7278
|
svgElement = renderChartSVG(currentLayout, container, {
|
|
5595
7279
|
animate: shouldAnimate,
|
|
@@ -5699,6 +7383,12 @@ function createChart(container, spec, options) {
|
|
|
5699
7383
|
}
|
|
5700
7384
|
function update(newSpec, updateOpts) {
|
|
5701
7385
|
if (destroyed) return;
|
|
7386
|
+
const prevSpec = currentSpec;
|
|
7387
|
+
const prevLayout = currentLayout;
|
|
7388
|
+
const entranceWasRunning = cleanupAnimations != null;
|
|
7389
|
+
if (transitionHandle?.running) {
|
|
7390
|
+
transitionSnapshot = transitionHandle.snapshot();
|
|
7391
|
+
}
|
|
5702
7392
|
currentSpec = newSpec;
|
|
5703
7393
|
const nextFont = resolveEffectiveFont();
|
|
5704
7394
|
if (nextFont !== fontFamily) {
|
|
@@ -5711,6 +7401,29 @@ function createChart(container, spec, options) {
|
|
|
5711
7401
|
selectedElement = updateOpts.selectedElement ?? null;
|
|
5712
7402
|
}
|
|
5713
7403
|
render();
|
|
7404
|
+
if (svgElement && canTransition({
|
|
7405
|
+
prevLayout,
|
|
7406
|
+
nextLayout: currentLayout,
|
|
7407
|
+
prevSpec,
|
|
7408
|
+
nextSpec: newSpec,
|
|
7409
|
+
isFirstRender: false,
|
|
7410
|
+
entranceInFlight: entranceWasRunning
|
|
7411
|
+
})) {
|
|
7412
|
+
const snapshot = transitionSnapshot;
|
|
7413
|
+
transitionSnapshot = null;
|
|
7414
|
+
transitionHandle = runTransition({
|
|
7415
|
+
svg: svgElement,
|
|
7416
|
+
prevLayout,
|
|
7417
|
+
nextLayout: currentLayout,
|
|
7418
|
+
animation: currentLayout.animation,
|
|
7419
|
+
fromSnapshot: snapshot ?? void 0,
|
|
7420
|
+
onComplete: () => {
|
|
7421
|
+
transitionHandle = null;
|
|
7422
|
+
}
|
|
7423
|
+
});
|
|
7424
|
+
} else {
|
|
7425
|
+
transitionSnapshot = null;
|
|
7426
|
+
}
|
|
5714
7427
|
}
|
|
5715
7428
|
function resize() {
|
|
5716
7429
|
if (destroyed) return;
|
|
@@ -5765,6 +7478,11 @@ function createChart(container, spec, options) {
|
|
|
5765
7478
|
function destroy() {
|
|
5766
7479
|
if (destroyed) return;
|
|
5767
7480
|
destroyed = true;
|
|
7481
|
+
if (transitionHandle) {
|
|
7482
|
+
transitionHandle.cancel();
|
|
7483
|
+
transitionHandle = null;
|
|
7484
|
+
}
|
|
7485
|
+
transitionSnapshot = null;
|
|
5768
7486
|
if (cleanupAnimations) {
|
|
5769
7487
|
cleanupAnimations();
|
|
5770
7488
|
cleanupAnimations = null;
|
|
@@ -6221,7 +7939,7 @@ function applyTextStyle(el, style) {
|
|
|
6221
7939
|
}
|
|
6222
7940
|
}
|
|
6223
7941
|
function stampAnimationAttrs(el, mark, fallbackIndex, animation) {
|
|
6224
|
-
if (!animation?.
|
|
7942
|
+
if (!animation?.enter) return;
|
|
6225
7943
|
const idx = mark.animationIndex ?? fallbackIndex;
|
|
6226
7944
|
el.setAttribute("data-animation-index", String(idx));
|
|
6227
7945
|
el.style.setProperty("--oc-mark-index", String(idx));
|
|
@@ -6553,16 +8271,17 @@ function renderSankeySVG(layout, animation) {
|
|
|
6553
8271
|
svg.style.height = `${height}px`;
|
|
6554
8272
|
svg.setAttribute("role", layout.a11y.role);
|
|
6555
8273
|
svg.setAttribute("aria-label", layout.a11y.altText);
|
|
6556
|
-
const
|
|
8274
|
+
const enterPhase = animation?.enter;
|
|
8275
|
+
const animate = !!enterPhase;
|
|
6557
8276
|
const classes = animate ? "oc-chart oc-sankey oc-animate" : "oc-chart oc-sankey";
|
|
6558
8277
|
svg.setAttribute("class", classes);
|
|
6559
|
-
if (
|
|
8278
|
+
if (enterPhase) {
|
|
6560
8279
|
const totalMarks = layout.nodes.length + layout.links.length;
|
|
6561
|
-
const stagger = clampStaggerDelay(
|
|
6562
|
-
svg.style.setProperty("--oc-animation-duration", `${
|
|
8280
|
+
const stagger = clampStaggerDelay(enterPhase.staggerDelay, totalMarks);
|
|
8281
|
+
svg.style.setProperty("--oc-animation-duration", `${enterPhase.duration}ms`);
|
|
6563
8282
|
svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
|
|
6564
8283
|
svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
|
|
6565
|
-
const easeVar = EASE_VAR_MAP[
|
|
8284
|
+
const easeVar = EASE_VAR_MAP[enterPhase.ease] || EASE_VAR_MAP.smooth;
|
|
6566
8285
|
svg.style.setProperty("--oc-animation-ease", easeVar);
|
|
6567
8286
|
}
|
|
6568
8287
|
const bg = createSVGElement2("rect");
|
|
@@ -6800,7 +8519,7 @@ function createSankey(container, spec, options) {
|
|
|
6800
8519
|
svgElement.parentNode.removeChild(svgElement);
|
|
6801
8520
|
}
|
|
6802
8521
|
currentLayout = compile();
|
|
6803
|
-
const shouldAnimate = isFirstRender && currentLayout.animation?.
|
|
8522
|
+
const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;
|
|
6804
8523
|
isFirstRender = false;
|
|
6805
8524
|
const animation = shouldAnimate ? currentLayout.animation : void 0;
|
|
6806
8525
|
svgElement = renderSankeySVG(currentLayout, animation);
|
|
@@ -6901,7 +8620,7 @@ function createSankey(container, spec, options) {
|
|
|
6901
8620
|
}
|
|
6902
8621
|
try {
|
|
6903
8622
|
currentLayout = compile();
|
|
6904
|
-
const shouldAnimate = currentLayout.animation?.
|
|
8623
|
+
const shouldAnimate = !!currentLayout.animation?.enter;
|
|
6905
8624
|
isFirstRender = false;
|
|
6906
8625
|
const animation = shouldAnimate ? currentLayout.animation : void 0;
|
|
6907
8626
|
svgElement = renderSankeySVG(currentLayout, animation);
|
|
@@ -7412,14 +9131,14 @@ function renderTable(layout, container, opts) {
|
|
|
7412
9131
|
if (footerRow.childElementCount > 0) {
|
|
7413
9132
|
wrapper.appendChild(footerRow);
|
|
7414
9133
|
}
|
|
7415
|
-
if (opts?.animate && layout.animation?.
|
|
7416
|
-
const
|
|
9134
|
+
if (opts?.animate && layout.animation?.enter) {
|
|
9135
|
+
const enterPhase = layout.animation.enter;
|
|
7417
9136
|
const rowCount = layout.rows.length;
|
|
7418
|
-
const stagger = clampStaggerDelay2(
|
|
9137
|
+
const stagger = clampStaggerDelay2(enterPhase.staggerDelay, rowCount);
|
|
7419
9138
|
const s = wrapper.style;
|
|
7420
|
-
s.setProperty("--oc-animation-duration", `${
|
|
9139
|
+
s.setProperty("--oc-animation-duration", `${enterPhase.duration}ms`);
|
|
7421
9140
|
s.setProperty("--oc-animation-stagger", `${stagger}ms`);
|
|
7422
|
-
s.setProperty("--oc-animation-ease", EASE_VAR_MAP2[
|
|
9141
|
+
s.setProperty("--oc-animation-ease", EASE_VAR_MAP2[enterPhase.ease] || EASE_VAR_MAP2.smooth);
|
|
7423
9142
|
wrapper.classList.add("oc-animate");
|
|
7424
9143
|
}
|
|
7425
9144
|
container.appendChild(wrapper);
|
|
@@ -7549,7 +9268,7 @@ function createTable(container, spec, options) {
|
|
|
7549
9268
|
wrapperElement = null;
|
|
7550
9269
|
}
|
|
7551
9270
|
currentLayout = compile();
|
|
7552
|
-
const shouldAnimate = isFirstRender && !!currentLayout.animation?.
|
|
9271
|
+
const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;
|
|
7553
9272
|
wrapperElement = renderTable(currentLayout, container, { animate: shouldAnimate });
|
|
7554
9273
|
if (shouldAnimate && wrapperElement) {
|
|
7555
9274
|
cleanupAnimations = setupTableAnimationCleanup(wrapperElement);
|
|
@@ -7922,7 +9641,7 @@ function createTileMap(container, spec, options) {
|
|
|
7922
9641
|
tooltipManager = createTooltipManager(container);
|
|
7923
9642
|
}
|
|
7924
9643
|
}
|
|
7925
|
-
if (animate && currentLayout.animation?.
|
|
9644
|
+
if (animate && currentLayout.animation?.enter) {
|
|
7926
9645
|
animationCleanup = setupAnimationCleanup(newSvg, () => {
|
|
7927
9646
|
if (pendingResize && !destroyed) {
|
|
7928
9647
|
pendingResize = false;
|
|
@@ -8043,6 +9762,7 @@ export {
|
|
|
8043
9762
|
exportSVG,
|
|
8044
9763
|
exportSVGWithFonts,
|
|
8045
9764
|
observeResize,
|
|
9765
|
+
rectPathWithCorners,
|
|
8046
9766
|
registerMarkRenderer,
|
|
8047
9767
|
renderBarCell,
|
|
8048
9768
|
renderCategoryCell,
|