@opendata-ai/openchart-vanilla 7.7.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
- renderLegend
5
- } from "./chunk-PSMDJEXK.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;
@@ -3370,7 +3374,7 @@ function createGraph(container, spec, options) {
3370
3374
  }
3371
3375
  chromeEl = document.createElement("div");
3372
3376
  chromeEl.className = "oc-graph-chrome";
3373
- renderChrome4();
3377
+ renderChrome3();
3374
3378
  wrapper.appendChild(chromeEl);
3375
3379
  canvas = document.createElement("canvas");
3376
3380
  canvas.className = "oc-graph-canvas";
@@ -3382,7 +3386,7 @@ function createGraph(container, spec, options) {
3382
3386
  if (options?.legend !== false) {
3383
3387
  legendEl = document.createElement("div");
3384
3388
  legendEl.className = "oc-graph-legend";
3385
- renderLegend3();
3389
+ renderLegend2();
3386
3390
  wrapper.appendChild(legendEl);
3387
3391
  }
3388
3392
  container.appendChild(wrapper);
@@ -3390,7 +3394,7 @@ function createGraph(container, spec, options) {
3390
3394
  renderer = new GraphCanvasRenderer(canvas);
3391
3395
  renderer.resize(width, canvasHeight);
3392
3396
  }
3393
- function renderChrome4() {
3397
+ function renderChrome3() {
3394
3398
  if (!chromeEl) return;
3395
3399
  let html = "";
3396
3400
  if (compilation.chrome.title) {
@@ -3406,7 +3410,7 @@ function createGraph(container, spec, options) {
3406
3410
  chromeEl.style.display = "";
3407
3411
  }
3408
3412
  }
3409
- function renderLegend3() {
3413
+ function renderLegend2() {
3410
3414
  if (!legendEl) return;
3411
3415
  const entries = "entries" in compilation.legend ? compilation.legend.entries : [];
3412
3416
  if (entries.length === 0) {
@@ -3704,8 +3708,8 @@ function createGraph(container, spec, options) {
3704
3708
  compilation = compile();
3705
3709
  adjacencyMap = buildAdjacencyMap(compilation.edges);
3706
3710
  buildDataMaps();
3707
- renderChrome4();
3708
- renderLegend3();
3711
+ renderChrome3();
3712
+ renderLegend2();
3709
3713
  initSimulation();
3710
3714
  initInteraction();
3711
3715
  hoveredNodeId = null;
@@ -3739,8 +3743,8 @@ function createGraph(container, spec, options) {
3739
3743
  };
3740
3744
  });
3741
3745
  spatialIndex.rebuild(positionedNodes);
3742
- renderChrome4();
3743
- renderLegend3();
3746
+ renderChrome3();
3747
+ renderLegend2();
3744
3748
  needsRender = true;
3745
3749
  scheduleRender();
3746
3750
  }
@@ -4335,7 +4339,7 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
4335
4339
  };
4336
4340
  }
4337
4341
  function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4338
- const SVG_NS4 = "http://www.w3.org/2000/svg";
4342
+ const SVG_NS3 = "http://www.w3.org/2000/svg";
4339
4343
  const cleanups = [];
4340
4344
  const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
4341
4345
  for (const el of annotationGroups) {
@@ -4374,7 +4378,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4374
4378
  const createdHandles = [];
4375
4379
  for (const ep of endpoints) {
4376
4380
  if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
4377
- const handleEl = document.createElementNS(SVG_NS4, "circle");
4381
+ const handleEl = document.createElementNS(SVG_NS3, "circle");
4378
4382
  handleEl.setAttribute("class", "oc-connector-handle");
4379
4383
  handleEl.setAttribute("data-endpoint", ep.name);
4380
4384
  handleEl.setAttribute("cx", String(ep.cx));
@@ -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));
@@ -6335,7 +8053,7 @@ function renderBrand(parent, layout) {
6335
8053
  a2.appendChild(text);
6336
8054
  parent.appendChild(a2);
6337
8055
  }
6338
- function renderLegend2(parent, legendLayout) {
8056
+ function renderLegend(parent, legendLayout) {
6339
8057
  if (!("entries" in legendLayout) || legendLayout.entries.length === 0) return;
6340
8058
  const legend = legendLayout;
6341
8059
  const g = createSVGElement2("g");
@@ -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");
@@ -6582,7 +8301,7 @@ function renderSankeySVG(layout, animation) {
6582
8301
  renderLinks(svg, layout.links, nodePositions, animation);
6583
8302
  renderNodes(svg, layout.nodes, animation);
6584
8303
  renderLabels(svg, layout.nodes, layout.measureText);
6585
- renderLegend2(svg, layout.legend);
8304
+ renderLegend(svg, layout.legend);
6586
8305
  renderChrome2(svg, layout);
6587
8306
  if (layout.watermark) {
6588
8307
  renderBrand(svg, layout);
@@ -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);
@@ -7792,349 +9511,6 @@ function createTable(container, spec, options) {
7792
9511
 
7793
9512
  // src/tilemap-mount.ts
7794
9513
  import { compileTileMap } from "@opendata-ai/openchart-engine";
7795
-
7796
- // src/tilemap-renderer.ts
7797
- import { textAscent as textAscent3 } from "@opendata-ai/openchart-core";
7798
- var SVG_NS3 = "http://www.w3.org/2000/svg";
7799
- var XLINK_NS3 = "http://www.w3.org/1999/xlink";
7800
- var BRAND_URL4 = "https://tryopendata.ai";
7801
- var EASE_VAR_MAP3 = {
7802
- smooth: "var(--oc-ease-smooth)",
7803
- snappy: "var(--oc-ease-snappy)"
7804
- };
7805
- var gradientIdCounter = 0;
7806
- function createSVGElement3(tag) {
7807
- return document.createElementNS(SVG_NS3, tag);
7808
- }
7809
- function setAttrs3(el, attrs) {
7810
- for (const [key, value] of Object.entries(attrs)) {
7811
- el.setAttribute(key, String(value));
7812
- }
7813
- }
7814
- function renderChrome3(parent, layout) {
7815
- const g = createSVGElement3("g");
7816
- g.setAttribute("class", "oc-chrome");
7817
- const { chrome } = layout;
7818
- const bottomOffset = layout.area.y + layout.area.height;
7819
- if (chrome.title) {
7820
- const text = createSVGElement3("text");
7821
- setAttrs3(text, {
7822
- x: chrome.title.x,
7823
- y: chrome.title.y + textAscent3(chrome.title.style.fontSize)
7824
- });
7825
- text.setAttribute("class", "oc-title");
7826
- text.setAttribute("font-family", chrome.title.style.fontFamily);
7827
- text.setAttribute("font-size", String(chrome.title.style.fontSize));
7828
- text.setAttribute("font-weight", String(chrome.title.style.fontWeight));
7829
- text.style.setProperty("fill", chrome.title.style.fill);
7830
- text.textContent = chrome.title.text;
7831
- g.appendChild(text);
7832
- }
7833
- if (chrome.subtitle) {
7834
- const text = createSVGElement3("text");
7835
- setAttrs3(text, {
7836
- x: chrome.subtitle.x,
7837
- y: chrome.subtitle.y + textAscent3(chrome.subtitle.style.fontSize)
7838
- });
7839
- text.setAttribute("class", "oc-subtitle");
7840
- text.setAttribute("font-family", chrome.subtitle.style.fontFamily);
7841
- text.setAttribute("font-size", String(chrome.subtitle.style.fontSize));
7842
- text.setAttribute("font-weight", String(chrome.subtitle.style.fontWeight));
7843
- text.style.setProperty(
7844
- "fill",
7845
- chrome.subtitle.style.fill
7846
- );
7847
- text.textContent = chrome.subtitle.text;
7848
- g.appendChild(text);
7849
- }
7850
- if (chrome.source) {
7851
- const text = createSVGElement3("text");
7852
- setAttrs3(text, {
7853
- x: chrome.source.x,
7854
- y: bottomOffset + chrome.source.y + textAscent3(chrome.source.style.fontSize)
7855
- });
7856
- text.setAttribute("class", "oc-source");
7857
- text.setAttribute("font-family", chrome.source.style.fontFamily);
7858
- text.setAttribute("font-size", String(chrome.source.style.fontSize));
7859
- text.setAttribute("font-weight", String(chrome.source.style.fontWeight));
7860
- text.style.setProperty(
7861
- "fill",
7862
- chrome.source.style.fill
7863
- );
7864
- text.textContent = chrome.source.text;
7865
- g.appendChild(text);
7866
- }
7867
- if (chrome.byline) {
7868
- const text = createSVGElement3("text");
7869
- setAttrs3(text, {
7870
- x: chrome.byline.x,
7871
- y: bottomOffset + chrome.byline.y + textAscent3(chrome.byline.style.fontSize)
7872
- });
7873
- text.setAttribute("class", "oc-byline");
7874
- text.setAttribute("font-family", chrome.byline.style.fontFamily);
7875
- text.setAttribute("font-size", String(chrome.byline.style.fontSize));
7876
- text.setAttribute("font-weight", String(chrome.byline.style.fontWeight));
7877
- text.style.setProperty(
7878
- "fill",
7879
- chrome.byline.style.fill
7880
- );
7881
- text.textContent = chrome.byline.text;
7882
- g.appendChild(text);
7883
- }
7884
- if (chrome.footer) {
7885
- const text = createSVGElement3("text");
7886
- setAttrs3(text, {
7887
- x: chrome.footer.x,
7888
- y: bottomOffset + chrome.footer.y + textAscent3(chrome.footer.style.fontSize)
7889
- });
7890
- text.setAttribute("class", "oc-footer");
7891
- text.setAttribute("font-family", chrome.footer.style.fontFamily);
7892
- text.setAttribute("font-size", String(chrome.footer.style.fontSize));
7893
- text.setAttribute("font-weight", String(chrome.footer.style.fontWeight));
7894
- text.style.setProperty(
7895
- "fill",
7896
- chrome.footer.style.fill
7897
- );
7898
- text.textContent = chrome.footer.text;
7899
- g.appendChild(text);
7900
- }
7901
- parent.appendChild(g);
7902
- }
7903
- function renderWatermark2(parent, layout) {
7904
- if (layout.width < 480) return;
7905
- const { width, height } = layout;
7906
- const { theme } = layout;
7907
- const padding = theme.spacing.padding;
7908
- const rightEdge = width - padding;
7909
- const bottomEdge = height - padding;
7910
- const fill = theme.colors.axis;
7911
- const a2 = createSVGElement3("a");
7912
- a2.setAttribute("href", BRAND_URL4);
7913
- a2.setAttributeNS(XLINK_NS3, "xlink:href", BRAND_URL4);
7914
- a2.setAttribute("target", "_blank");
7915
- a2.setAttribute("rel", "noopener");
7916
- a2.setAttribute("class", "oc-chrome-ref");
7917
- const text = createSVGElement3("text");
7918
- setAttrs3(text, {
7919
- x: rightEdge,
7920
- y: bottomEdge,
7921
- "dominant-baseline": "alphabetic",
7922
- "text-anchor": "end",
7923
- "font-family": theme.fonts.family,
7924
- "font-size": 12,
7925
- "fill-opacity": 0.55
7926
- });
7927
- text.style.setProperty("fill", fill);
7928
- const trySpan = createSVGElement3("tspan");
7929
- setAttrs3(trySpan, { "font-weight": 500 });
7930
- trySpan.textContent = "try";
7931
- const openDataSpan = createSVGElement3("tspan");
7932
- setAttrs3(openDataSpan, { "font-weight": 600, "font-size": 16 });
7933
- openDataSpan.textContent = "OpenData";
7934
- const aiSpan = createSVGElement3("tspan");
7935
- setAttrs3(aiSpan, { "font-weight": 500 });
7936
- aiSpan.textContent = ".ai";
7937
- text.appendChild(trySpan);
7938
- text.appendChild(openDataSpan);
7939
- text.appendChild(aiSpan);
7940
- a2.appendChild(text);
7941
- parent.appendChild(a2);
7942
- }
7943
- function renderTiles(parent, tiles, animation) {
7944
- const g = createSVGElement3("g");
7945
- g.setAttribute("class", "oc-tilemap-tiles");
7946
- g.setAttribute("role", "list");
7947
- const tileDelays = [];
7948
- if (animation?.enabled) {
7949
- const baseStagger = 800 / Math.max(tiles.length, 1);
7950
- let seed = 17;
7951
- for (let i = 0; i < tiles.length; i++) {
7952
- const idx = tiles[i].animationIndex ?? i;
7953
- seed = seed * 1103515245 + 12345 & 2147483647;
7954
- const jitter = (seed % 1e3 / 1e3 - 0.5) * 0.8;
7955
- tileDelays.push(Math.max(0, Math.round(idx * baseStagger * (1 + jitter))));
7956
- }
7957
- }
7958
- for (let i = 0; i < tiles.length; i++) {
7959
- const tile = tiles[i];
7960
- const tileGroup = createSVGElement3("g");
7961
- tileGroup.setAttribute("class", "oc-tilemap-tile");
7962
- tileGroup.setAttribute("data-state", tile.stateCode);
7963
- tileGroup.setAttribute("role", "listitem");
7964
- if (tile.aria?.label) {
7965
- tileGroup.setAttribute("aria-label", tile.aria.label);
7966
- }
7967
- if (animation?.enabled) {
7968
- const idx = tile.animationIndex ?? i;
7969
- tileGroup.setAttribute("data-animation-index", String(idx));
7970
- tileGroup.style.setProperty(
7971
- "--oc-mark-index",
7972
- String(idx)
7973
- );
7974
- tileGroup.style.setProperty(
7975
- "--oc-tile-delay",
7976
- `${tileDelays[i]}ms`
7977
- );
7978
- }
7979
- const rect = createSVGElement3("rect");
7980
- setAttrs3(rect, {
7981
- x: tile.x,
7982
- y: tile.y,
7983
- width: tile.size,
7984
- height: tile.size,
7985
- rx: tile.cornerRadius,
7986
- fill: tile.fill,
7987
- "fill-opacity": tile.fillOpacity ?? 1,
7988
- stroke: tile.stroke,
7989
- "stroke-width": tile.strokeWidth
7990
- });
7991
- tileGroup.appendChild(rect);
7992
- const codeLabel = createSVGElement3("text");
7993
- setAttrs3(codeLabel, {
7994
- x: tile.label.x,
7995
- y: tile.label.y,
7996
- "text-anchor": "middle",
7997
- "dominant-baseline": "central",
7998
- "font-family": tile.label.style.fontFamily,
7999
- "font-size": tile.label.style.fontSize,
8000
- "font-weight": tile.label.style.fontWeight
8001
- });
8002
- codeLabel.style.setProperty(
8003
- "fill",
8004
- tile.label.style.fill
8005
- );
8006
- codeLabel.textContent = tile.label.text;
8007
- tileGroup.appendChild(codeLabel);
8008
- if (tile.valueLabel.visible && tile.valueLabel.text) {
8009
- const valueLabel = createSVGElement3("text");
8010
- setAttrs3(valueLabel, {
8011
- x: tile.valueLabel.x,
8012
- y: tile.valueLabel.y,
8013
- "text-anchor": "middle",
8014
- "dominant-baseline": "central",
8015
- "font-family": tile.valueLabel.style.fontFamily,
8016
- "font-size": tile.valueLabel.style.fontSize,
8017
- "font-weight": tile.valueLabel.style.fontWeight
8018
- });
8019
- valueLabel.style.setProperty(
8020
- "fill",
8021
- tile.valueLabel.style.fill
8022
- );
8023
- valueLabel.textContent = tile.valueLabel.text;
8024
- tileGroup.appendChild(valueLabel);
8025
- }
8026
- g.appendChild(tileGroup);
8027
- }
8028
- parent.appendChild(g);
8029
- }
8030
- function renderGradientLegend(parent, layout) {
8031
- if (!layout.gradientLegend) return;
8032
- const { gradientLegend } = layout;
8033
- const g = createSVGElement3("g");
8034
- g.setAttribute("class", "oc-tilemap-legend");
8035
- const defs = parent.querySelector("defs") || createSVGElement3("defs");
8036
- const exists = parent.querySelector("defs");
8037
- if (!exists) {
8038
- parent.insertBefore(defs, parent.firstChild);
8039
- }
8040
- const gradientId = `oc-tilemap-legend-gradient-${gradientIdCounter++}`;
8041
- const grad = createSVGElement3("linearGradient");
8042
- grad.id = gradientId;
8043
- grad.setAttribute("x1", "0%");
8044
- grad.setAttribute("y1", "0%");
8045
- grad.setAttribute("x2", "100%");
8046
- grad.setAttribute("y2", "0%");
8047
- for (const stop of gradientLegend.colorStops) {
8048
- const s = createSVGElement3("stop");
8049
- const attrs = {
8050
- offset: `${stop.offset * 100}%`,
8051
- "stop-color": stop.color
8052
- };
8053
- if (stop.opacity !== void 0) {
8054
- attrs["stop-opacity"] = stop.opacity;
8055
- }
8056
- setAttrs3(s, attrs);
8057
- grad.appendChild(s);
8058
- }
8059
- defs.appendChild(grad);
8060
- const barHeight = gradientLegend.bounds.height;
8061
- const bar = createSVGElement3("rect");
8062
- setAttrs3(bar, {
8063
- x: gradientLegend.bounds.x,
8064
- y: gradientLegend.bounds.y,
8065
- width: gradientLegend.bounds.width,
8066
- height: barHeight,
8067
- rx: barHeight / 2,
8068
- fill: `url(#${gradientId})`
8069
- });
8070
- g.appendChild(bar);
8071
- const minText = createSVGElement3("text");
8072
- setAttrs3(minText, {
8073
- x: gradientLegend.bounds.x,
8074
- y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
8075
- "text-anchor": "start",
8076
- "font-family": gradientLegend.labelStyle.fontFamily,
8077
- "font-size": gradientLegend.labelStyle.fontSize,
8078
- "font-weight": gradientLegend.labelStyle.fontWeight
8079
- });
8080
- minText.style.setProperty(
8081
- "fill",
8082
- gradientLegend.labelStyle.fill
8083
- );
8084
- minText.textContent = gradientLegend.minLabel;
8085
- g.appendChild(minText);
8086
- const maxText = createSVGElement3("text");
8087
- setAttrs3(maxText, {
8088
- x: gradientLegend.bounds.x + gradientLegend.bounds.width,
8089
- y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
8090
- "text-anchor": "end",
8091
- "font-family": gradientLegend.labelStyle.fontFamily,
8092
- "font-size": gradientLegend.labelStyle.fontSize,
8093
- "font-weight": gradientLegend.labelStyle.fontWeight
8094
- });
8095
- maxText.style.setProperty(
8096
- "fill",
8097
- gradientLegend.labelStyle.fill
8098
- );
8099
- maxText.textContent = gradientLegend.maxLabel;
8100
- g.appendChild(maxText);
8101
- parent.appendChild(g);
8102
- }
8103
- function renderTileMapSVG(layout, opts) {
8104
- const { width, height, tiles, a11y, watermark, animation } = layout;
8105
- const animate = opts?.animate && animation?.enabled;
8106
- const svg = createSVGElement3("svg");
8107
- svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
8108
- svg.setAttribute("role", "img");
8109
- if (a11y.altText) {
8110
- svg.setAttribute("aria-label", a11y.altText);
8111
- }
8112
- const classes = animate ? "oc-tilemap oc-animate" : "oc-tilemap";
8113
- svg.setAttribute("class", classes);
8114
- if (animate && animation) {
8115
- const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));
8116
- svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
8117
- svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
8118
- svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
8119
- const easeVar = EASE_VAR_MAP3[animation.ease] || EASE_VAR_MAP3.smooth;
8120
- svg.style.setProperty("--oc-animation-ease", easeVar);
8121
- }
8122
- const defs = createSVGElement3("defs");
8123
- svg.appendChild(defs);
8124
- renderChrome3(svg, layout);
8125
- renderTiles(svg, tiles, animate ? animation : void 0);
8126
- if (layout.categoricalLegend) {
8127
- renderLegend(svg, layout.categoricalLegend);
8128
- } else {
8129
- renderGradientLegend(svg, layout);
8130
- }
8131
- if (watermark) {
8132
- renderWatermark2(svg, layout);
8133
- }
8134
- return svg;
8135
- }
8136
-
8137
- // src/tilemap-mount.ts
8138
9514
  function resolveDarkMode6(mode) {
8139
9515
  if (mode === "force") return true;
8140
9516
  if (mode === "off" || mode === void 0) return false;
@@ -8265,7 +9641,7 @@ function createTileMap(container, spec, options) {
8265
9641
  tooltipManager = createTooltipManager(container);
8266
9642
  }
8267
9643
  }
8268
- if (animate && currentLayout.animation?.enabled) {
9644
+ if (animate && currentLayout.animation?.enter) {
8269
9645
  animationCleanup = setupAnimationCleanup(newSvg, () => {
8270
9646
  if (pendingResize && !destroyed) {
8271
9647
  pendingResize = false;
@@ -8386,6 +9762,7 @@ export {
8386
9762
  exportSVG,
8387
9763
  exportSVGWithFonts,
8388
9764
  observeResize,
9765
+ rectPathWithCorners,
8389
9766
  registerMarkRenderer,
8390
9767
  renderBarCell,
8391
9768
  renderCategoryCell,