@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/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import {
2
+ buildGradientDefs,
3
+ rectPathWithCorners,
2
4
  registerMarkRenderer,
3
5
  renderChartSVG,
4
- renderTileMapSVG
5
- } from "./chunk-KYRQV6KR.js";
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?.enabled) {
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?.enabled;
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?.enabled) {
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?.enabled;
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?.enabled) return;
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 animate = animation?.enabled;
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 (animation?.enabled) {
8278
+ if (enterPhase) {
6560
8279
  const totalMarks = layout.nodes.length + layout.links.length;
6561
- const stagger = clampStaggerDelay(animation.staggerDelay, totalMarks);
6562
- svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
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[animation.ease] || EASE_VAR_MAP.smooth;
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?.enabled;
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?.enabled;
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?.enabled) {
7416
- const anim = layout.animation;
9134
+ if (opts?.animate && layout.animation?.enter) {
9135
+ const enterPhase = layout.animation.enter;
7417
9136
  const rowCount = layout.rows.length;
7418
- const stagger = clampStaggerDelay2(anim.staggerDelay, rowCount);
9137
+ const stagger = clampStaggerDelay2(enterPhase.staggerDelay, rowCount);
7419
9138
  const s = wrapper.style;
7420
- s.setProperty("--oc-animation-duration", `${anim.duration}ms`);
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[anim.ease] || EASE_VAR_MAP2.smooth);
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?.enabled;
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?.enabled) {
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,