@opendata-ai/openchart-vanilla 8.4.0 → 8.5.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.
Files changed (37) hide show
  1. package/README.md +59 -0
  2. package/dist/chunk-2OJZBMXM.js +377 -0
  3. package/dist/chunk-2OJZBMXM.js.map +1 -0
  4. package/dist/{chunk-45XZOWYC.js → chunk-5KV3ZBBL.js} +58 -437
  5. package/dist/chunk-5KV3ZBBL.js.map +1 -0
  6. package/dist/{chunk-HGRNXLDF.js → chunk-5RX6ZQGZ.js} +10 -1
  7. package/dist/chunk-5RX6ZQGZ.js.map +1 -0
  8. package/dist/{chunk-UOB422LY.js → chunk-6B5OBGIO.js} +2 -2
  9. package/dist/chunk-NG7CVCI2.js +58 -0
  10. package/dist/chunk-NG7CVCI2.js.map +1 -0
  11. package/dist/{chunk-FETYAVTZ.js → chunk-RYVV3YIG.js} +3 -3
  12. package/dist/{chunk-LNPRDSK3.js → chunk-UAK6HWOC.js} +2 -2
  13. package/dist/chunk-WOLX5EZT.js +3577 -0
  14. package/dist/chunk-WOLX5EZT.js.map +1 -0
  15. package/dist/{chunk-YGYMB7KB.js → chunk-XGII4ZRT.js} +55 -30
  16. package/dist/chunk-XGII4ZRT.js.map +1 -0
  17. package/dist/export-gif.js +3 -3
  18. package/dist/graph-3d/index.d.ts +272 -0
  19. package/dist/graph-3d/index.js +1556 -0
  20. package/dist/graph-3d/index.js.map +1 -0
  21. package/dist/index.d.ts +4 -252
  22. package/dist/index.js +27 -3471
  23. package/dist/index.js.map +1 -1
  24. package/dist/renderer-registry-B5tW6yAZ.d.ts +376 -0
  25. package/dist/static.js +4 -3
  26. package/dist/static.js.map +1 -1
  27. package/dist/story/index.js +10 -7
  28. package/dist/story/index.js.map +1 -1
  29. package/dist/styles.css +1 -1
  30. package/dist/styles.css.map +1 -1
  31. package/package.json +26 -5
  32. package/dist/chunk-45XZOWYC.js.map +0 -1
  33. package/dist/chunk-HGRNXLDF.js.map +0 -1
  34. package/dist/chunk-YGYMB7KB.js.map +0 -1
  35. /package/dist/{chunk-UOB422LY.js.map → chunk-6B5OBGIO.js.map} +0 -0
  36. /package/dist/{chunk-FETYAVTZ.js.map → chunk-RYVV3YIG.js.map} +0 -0
  37. /package/dist/{chunk-LNPRDSK3.js.map → chunk-UAK6HWOC.js.map} +0 -0
@@ -4,9 +4,10 @@ import {
4
4
  exportPNG,
5
5
  exportSVG,
6
6
  exportSVGWithFonts
7
- } from "./chunk-UOB422LY.js";
7
+ } from "./chunk-6B5OBGIO.js";
8
8
  import {
9
9
  buildGradientDefs,
10
+ buildPatternDefs,
10
11
  nextSvgId,
11
12
  rectPathWithCorners,
12
13
  renderChartSVG,
@@ -14,161 +15,33 @@ import {
14
15
  renderLegend,
15
16
  renderSingleMark,
16
17
  resolveMarkFill,
18
+ resolvePatternFill,
17
19
  stampAnimationVars
18
- } from "./chunk-YGYMB7KB.js";
20
+ } from "./chunk-XGII4ZRT.js";
19
21
  import {
20
22
  SVG_NS,
21
23
  createSVGElement,
22
24
  setAttrs
23
- } from "./chunk-HGRNXLDF.js";
24
-
25
- // src/resize-observer.ts
26
- var DEBOUNCE_MS = 16;
27
- function observeResize(container, callback) {
28
- let timeoutId = null;
29
- let lastDelivered = null;
30
- const observer = new ResizeObserver((entries) => {
31
- if (timeoutId !== null) {
32
- clearTimeout(timeoutId);
33
- }
34
- timeoutId = setTimeout(() => {
35
- for (const entry of entries) {
36
- const { width, height } = entry.contentRect;
37
- if (lastDelivered && lastDelivered.width === width && lastDelivered.height === height) {
38
- continue;
39
- }
40
- lastDelivered = { width, height };
41
- callback(width, height);
42
- }
43
- timeoutId = null;
44
- }, DEBOUNCE_MS);
45
- });
46
- observer.observe(container);
47
- return () => {
48
- if (timeoutId !== null) {
49
- clearTimeout(timeoutId);
50
- }
51
- observer.disconnect();
52
- };
53
- }
54
-
55
- // src/tooltip.ts
56
- import { computePosition, flip, offset, shift } from "@floating-ui/dom";
57
- var TOOLTIP_OFFSET = 12;
58
- function createTooltipManager(container) {
59
- const tooltip = document.createElement("div");
60
- tooltip.className = "oc-tooltip";
61
- tooltip.setAttribute("role", "tooltip");
62
- container.style.position = container.style.position || "relative";
63
- container.appendChild(tooltip);
64
- let lastContentKey = "";
65
- let currentPositionId = 0;
66
- const handleDocumentTouch = (e) => {
67
- if (!container.contains(e.target)) {
68
- hide();
69
- }
70
- };
71
- document.addEventListener("touchstart", handleDocumentTouch);
72
- const handleDocumentKeydown = (e) => {
73
- if (e.key === "Escape" && tooltip.style.display === "block") {
74
- hide();
75
- }
76
- };
77
- document.addEventListener("keydown", handleDocumentKeydown);
78
- function show(input, x, y, opts) {
79
- if ("element" in input) {
80
- lastContentKey = "";
81
- tooltip.replaceChildren(input.element);
82
- position(x, y, opts);
83
- return;
84
- }
85
- if ("text" in input) {
86
- lastContentKey = "";
87
- tooltip.textContent = input.text;
88
- position(x, y, opts);
89
- return;
90
- }
91
- const content = input;
92
- const emphasisIndex = content.fields.findIndex((f) => f.emphasis);
93
- const contentKey = `${content.title}|${content.fields.length}|${content.fields[0]?.value}|${content.fields[content.fields.length - 1]?.value}|${emphasisIndex}`;
94
- if (contentKey !== lastContentKey) {
95
- lastContentKey = contentKey;
96
- let html = "";
97
- const colorRowCount = content.fields.filter((f) => f.color && f.role !== "total").length;
98
- const perRowSwatches = colorRowCount > 1;
99
- if (content.title) {
100
- const titleColor = perRowSwatches ? void 0 : content.fields.find((f) => f.color)?.color;
101
- html += '<div class="oc-tooltip-header">';
102
- if (titleColor) {
103
- html += `<span class="oc-tooltip-dot" style="background:${esc(titleColor)}"></span>`;
104
- }
105
- html += `<span class="oc-tooltip-title">${esc(content.title)}</span>`;
106
- html += "</div>";
107
- }
108
- if (content.fields.length > 0) {
109
- html += '<div class="oc-tooltip-body">';
110
- for (const field of content.fields) {
111
- let rowClass = "oc-tooltip-row";
112
- if (field.emphasis) rowClass += " oc-tooltip-row--emphasis";
113
- if (field.role === "total") rowClass += " oc-tooltip-row--total";
114
- html += `<div class="${rowClass}">`;
115
- html += '<span class="oc-tooltip-label">';
116
- if (perRowSwatches && field.color) {
117
- html += `<span class="oc-tooltip-row-swatch" style="background:${esc(field.color)}"></span>`;
118
- }
119
- html += `${esc(field.label)}</span>`;
120
- html += `<span class="oc-tooltip-value">${esc(field.value)}</span>`;
121
- html += "</div>";
122
- }
123
- html += "</div>";
124
- }
125
- tooltip.innerHTML = html;
126
- }
127
- position(x, y, opts);
128
- }
129
- function position(x, y, opts) {
130
- tooltip.style.display = "block";
131
- const positionId = ++currentPositionId;
132
- const virtualRef = {
133
- getBoundingClientRect() {
134
- const rect = container.getBoundingClientRect();
135
- return {
136
- x: rect.left + x,
137
- y: rect.top + y,
138
- width: 0,
139
- height: 0,
140
- top: rect.top + y,
141
- left: rect.left + x,
142
- right: rect.left + x,
143
- bottom: rect.top + y
144
- };
145
- }
146
- };
147
- computePosition(virtualRef, tooltip, {
148
- placement: opts?.placement ?? "bottom-start",
149
- middleware: [offset(TOOLTIP_OFFSET), flip(), shift({ padding: 8 })]
150
- }).then(({ x: fx, y: fy }) => {
151
- if (positionId !== currentPositionId) return;
152
- tooltip.style.left = `${fx}px`;
153
- tooltip.style.top = `${fy}px`;
154
- });
155
- }
156
- function hide() {
157
- tooltip.style.display = "none";
158
- lastContentKey = "";
159
- }
160
- function destroy() {
161
- document.removeEventListener("touchstart", handleDocumentTouch);
162
- document.removeEventListener("keydown", handleDocumentKeydown);
163
- if (tooltip.parentNode) {
164
- tooltip.parentNode.removeChild(tooltip);
165
- }
166
- }
167
- return { show, hide, destroy };
168
- }
169
- function esc(str) {
170
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
171
- }
25
+ } from "./chunk-5RX6ZQGZ.js";
26
+ import {
27
+ AnimationScheduler,
28
+ SpatialIndex,
29
+ clamp01,
30
+ createTooltipManager,
31
+ createTween,
32
+ cubicInOut,
33
+ cubicOut,
34
+ lerp,
35
+ linear,
36
+ observeResize,
37
+ prefersReducedMotion,
38
+ resolveDarkMode,
39
+ resolveEase
40
+ } from "./chunk-2OJZBMXM.js";
41
+ import {
42
+ resolvedSurface,
43
+ stampThemeProperties
44
+ } from "./chunk-NG7CVCI2.js";
172
45
 
173
46
  // src/scatter-canvas/renderer.ts
174
47
  var TWO_PI = Math.PI * 2;
@@ -580,20 +453,6 @@ function materializeCanvasModeSVG(layout, opts) {
580
453
  return materializeRasterMarkSVG(layout);
581
454
  }
582
455
 
583
- // src/motion/easing.ts
584
- function linear(t) {
585
- return t;
586
- }
587
- function cubicOut(t) {
588
- const f = 1 - t;
589
- return 1 - f * f * f;
590
- }
591
- function cubicInOut(t) {
592
- return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
593
- }
594
- var clamp01 = (t) => Math.min(1, Math.max(0, t));
595
- var lerp = (a, b, t) => a + (b - a) * t;
596
-
597
456
  // src/text-edit-overlay.ts
598
457
  function getScale(svg) {
599
458
  const viewBox = svg.viewBox?.baseVal;
@@ -625,7 +484,7 @@ function getTextStyles(targetElement, scale) {
625
484
  lineHeight: "1.3"
626
485
  };
627
486
  }
628
- function computePosition2(targetElement, svg, container) {
487
+ function computePosition(targetElement, svg, container) {
629
488
  const bbox = targetElement.getBBox();
630
489
  const scale = getScale(svg);
631
490
  const svgRect = svg.getBoundingClientRect();
@@ -649,7 +508,7 @@ function createTextEditOverlay(config) {
649
508
  targetElement.setAttribute("opacity", "0");
650
509
  const scale = getScale(svg);
651
510
  const styles = getTextStyles(targetElement, scale);
652
- const position = computePosition2(targetElement, svg, container);
511
+ const position = computePosition(targetElement, svg, container);
653
512
  const textarea = document.createElement("textarea");
654
513
  textarea.value = currentText;
655
514
  const containerPosition = window.getComputedStyle(container).position;
@@ -738,7 +597,7 @@ function createTextEditOverlay(config) {
738
597
  });
739
598
  const resizeObserver = new ResizeObserver(() => {
740
599
  if (destroyed) return;
741
- const newPosition = computePosition2(targetElement, svg, container);
600
+ const newPosition = computePosition(targetElement, svg, container);
742
601
  textarea.style.top = `${newPosition.top}px`;
743
602
  textarea.style.left = `${newPosition.left}px`;
744
603
  textarea.style.width = `${newPosition.width}px`;
@@ -3564,7 +3423,7 @@ function createYouDrawIt(options) {
3564
3423
  }
3565
3424
 
3566
3425
  // src/mount.ts
3567
- import { cssTokenDefault as cssTokenDefault2, isGraphSpec, isLayerSpec } from "@opendata-ai/openchart-core";
3426
+ import { cssTokenDefault, isGraphSpec, isLayerSpec } from "@opendata-ai/openchart-core";
3568
3427
  import {
3569
3428
  compileChart,
3570
3429
  compileLayer,
@@ -5308,16 +5167,6 @@ function scheduleFontReload(fontFamily, isAlive, onReady) {
5308
5167
  return true;
5309
5168
  }
5310
5169
 
5311
- // src/resolve-dark-mode.ts
5312
- function resolveDarkMode(mode) {
5313
- if (mode === "force") return true;
5314
- if (mode === "off" || mode === void 0) return false;
5315
- if (typeof window !== "undefined" && window.matchMedia) {
5316
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
5317
- }
5318
- return false;
5319
- }
5320
-
5321
5170
  // src/scatter-canvas/interactions.ts
5322
5171
  var HIT_TOLERANCE_MOUSE = 8;
5323
5172
  var HIT_TOLERANCE_TOUCH = 24;
@@ -5415,191 +5264,6 @@ function wireCanvasInteractions({
5415
5264
  };
5416
5265
  }
5417
5266
 
5418
- // src/motion/scheduler.ts
5419
- var AnimationScheduler = class {
5420
- animations = /* @__PURE__ */ new Set();
5421
- requestFrame;
5422
- /**
5423
- * @param requestFrame Called on the idle→active transition to arm the first
5424
- * frame (typically the mount's `scheduleRender`).
5425
- */
5426
- constructor(requestFrame) {
5427
- this.requestFrame = requestFrame;
5428
- }
5429
- /** Add an animation. Arms the first frame if the scheduler was idle. */
5430
- add(a) {
5431
- const wasIdle = this.animations.size === 0;
5432
- this.animations.add(a);
5433
- if (wasIdle) this.requestFrame();
5434
- }
5435
- /** Remove an animation without finishing or cancelling it. Safe mid-tick. */
5436
- remove(a) {
5437
- this.animations.delete(a);
5438
- }
5439
- /**
5440
- * Advance all animations to `now`. Completed animations are removed. Returns
5441
- * true if any animation ran this frame (so the caller marks the frame dirty).
5442
- *
5443
- * Iterates a snapshot so an `onDone` that mutates the set (via update/destroy)
5444
- * can't corrupt the loop.
5445
- */
5446
- tick(now) {
5447
- if (this.animations.size === 0) return false;
5448
- const snapshot = [...this.animations];
5449
- let ran = false;
5450
- for (const a of snapshot) {
5451
- if (!this.animations.has(a)) continue;
5452
- ran = true;
5453
- const running = a.tick(now);
5454
- if (!running) this.animations.delete(a);
5455
- }
5456
- return ran;
5457
- }
5458
- /** True while any animation is active (drives rAF re-arming). */
5459
- get active() {
5460
- return this.animations.size > 0;
5461
- }
5462
- /** Cancel every animation (no final state, no onDone). Used on teardown. */
5463
- cancelAll() {
5464
- const snapshot = [...this.animations];
5465
- this.animations.clear();
5466
- for (const a of snapshot) a.cancel();
5467
- }
5468
- /** Finish every animation (snap to final, fire onDone). Used before update(). */
5469
- finishAll() {
5470
- const snapshot = [...this.animations];
5471
- this.animations.clear();
5472
- for (const a of snapshot) a.finish();
5473
- }
5474
- };
5475
-
5476
- // src/spatial-index.ts
5477
- import { quadtree } from "d3-quadtree";
5478
- var SpatialIndex = class {
5479
- tree = null;
5480
- nodes = [];
5481
- maxRadius = 0;
5482
- generation = 0;
5483
- /** Rebuild the quadtree from the current set of positioned nodes. */
5484
- rebuild(nodes) {
5485
- this.nodes = nodes;
5486
- this.maxRadius = 0;
5487
- for (const n of nodes) {
5488
- if (n.radius > this.maxRadius) this.maxRadius = n.radius;
5489
- }
5490
- this.tree = quadtree().x((d) => d.x).y((d) => d.y).addAll(nodes);
5491
- this.generation++;
5492
- }
5493
- /** Current generation counter. Increments on each rebuild. */
5494
- getGeneration() {
5495
- return this.generation;
5496
- }
5497
- /**
5498
- * Find the nearest node to (x, y) within maxDistance.
5499
- * Accounts for node radius: a hit occurs if the point is inside
5500
- * the node circle (distance to center < node.radius), or if the
5501
- * edge-to-edge distance is within maxDistance.
5502
- */
5503
- findNearest(x, y, maxDistance = Infinity) {
5504
- if (!this.tree || this.nodes.length === 0) return null;
5505
- const searchRadius = maxDistance + this.maxRadius;
5506
- let best = null;
5507
- let bestEffectiveDist = maxDistance + this.maxRadius + 1;
5508
- this.tree.visit((node, x0, y0, x1, y1) => {
5509
- const closestX = Math.max(x0, Math.min(x, x1));
5510
- const closestY = Math.max(y0, Math.min(y, y1));
5511
- const quadDist = Math.hypot(closestX - x, closestY - y);
5512
- if (quadDist > searchRadius) return true;
5513
- if (!node.length) {
5514
- let current = node;
5515
- do {
5516
- const d = current.data;
5517
- if (d) {
5518
- const dist = Math.hypot(d.x - x, d.y - y);
5519
- const effectiveDist = Math.max(0, dist - d.radius);
5520
- if (effectiveDist <= maxDistance && effectiveDist < bestEffectiveDist) {
5521
- bestEffectiveDist = effectiveDist;
5522
- best = d;
5523
- }
5524
- }
5525
- } while (current = current.next);
5526
- }
5527
- return false;
5528
- });
5529
- return best;
5530
- }
5531
- /** Find all nodes whose centers fall within the given rectangle. */
5532
- findInRect(x1, y1, x2, y2) {
5533
- if (!this.tree) return [];
5534
- const minX = Math.min(x1, x2);
5535
- const minY = Math.min(y1, y2);
5536
- const maxX = Math.max(x1, x2);
5537
- const maxY = Math.max(y1, y2);
5538
- const results = [];
5539
- this.tree.visit((node, qx0, qy0, qx1, qy1) => {
5540
- if (qx0 > maxX || qx1 < minX || qy0 > maxY || qy1 < minY) {
5541
- return true;
5542
- }
5543
- if (!node.length) {
5544
- let current = node;
5545
- do {
5546
- const d = current.data;
5547
- if (d && d.x >= minX && d.x <= maxX && d.y >= minY && d.y <= maxY) {
5548
- results.push(d);
5549
- }
5550
- } while (current = current.next);
5551
- }
5552
- return false;
5553
- });
5554
- return results;
5555
- }
5556
- };
5557
-
5558
- // src/motion/tween.ts
5559
- function resolveEase(ease) {
5560
- return ease === "snappy" ? cubicOut : cubicInOut;
5561
- }
5562
- function prefersReducedMotion() {
5563
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
5564
- try {
5565
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
5566
- } catch {
5567
- return false;
5568
- }
5569
- }
5570
- function createTween(opts) {
5571
- const { duration, ease, apply, onDone } = opts;
5572
- let startTime = null;
5573
- let finished = false;
5574
- function settleAt(t) {
5575
- apply(ease(t));
5576
- }
5577
- return {
5578
- tick(now) {
5579
- if (finished) return false;
5580
- if (startTime === null) startTime = now;
5581
- const elapsed = now - startTime;
5582
- const raw = duration <= 0 ? 1 : Math.min(1, elapsed / duration);
5583
- settleAt(raw);
5584
- if (raw >= 1) {
5585
- finished = true;
5586
- onDone?.();
5587
- return false;
5588
- }
5589
- return true;
5590
- },
5591
- finish() {
5592
- if (finished) return;
5593
- finished = true;
5594
- settleAt(1);
5595
- onDone?.();
5596
- },
5597
- cancel() {
5598
- finished = true;
5599
- }
5600
- };
5601
- }
5602
-
5603
5267
  // src/scatter-canvas/entrance.ts
5604
5268
  var MAX_TOTAL_STAGGER_MS = 300;
5605
5269
  var POINT_DURATION_FRACTION = 0.4;
@@ -6036,64 +5700,11 @@ function createSeriesSearch(options) {
6036
5700
  };
6037
5701
  }
6038
5702
 
6039
- // src/theme-tokens.ts
6040
- import { adaptForLightLineStroke, cssTokenDefault } from "@opendata-ai/openchart-core";
6041
- function resolvedSurface(theme) {
6042
- return theme.colors.neutral.surface;
6043
- }
6044
- function stampThemeProperties(el, theme) {
6045
- const accent = theme.colors.categorical[0] ?? cssTokenDefault("--oc-accent", "light");
6046
- const bg = resolvedSurface(theme);
6047
- const n = theme.colors.neutral;
6048
- const props = [
6049
- ["--oc-font-family", theme.fonts.family],
6050
- ["--oc-font-mono", theme.fonts.mono],
6051
- ["--oc-title-size", `${theme.chrome.title.fontSize}px`],
6052
- ["--oc-title-weight", `${theme.chrome.title.fontWeight}`],
6053
- ["--oc-subtitle-size", `${theme.chrome.subtitle.fontSize}px`],
6054
- ["--oc-subtitle-weight", `${theme.chrome.subtitle.fontWeight}`],
6055
- ["--oc-source-size", `${theme.chrome.source.fontSize}px`],
6056
- ["--oc-source-weight", `${theme.chrome.source.fontWeight}`],
6057
- ["--oc-body-size", `${theme.fonts.sizes.body}px`],
6058
- ["--oc-eyebrow-size", `${theme.chrome.eyebrow.fontSize}px`],
6059
- ["--oc-eyebrow-weight", `${theme.chrome.eyebrow.fontWeight}`],
6060
- ["--oc-bg", bg],
6061
- ["--oc-text", theme.colors.text],
6062
- ["--oc-text-muted", theme.colors.axis],
6063
- // Secondary grays are derived from the theme's own text/background pair
6064
- // (ResolvedTheme.colors.neutral), so a warm or cool theme gets warm or
6065
- // cool grays instead of zinc. One direction only: theme -> CSS.
6066
- ["--oc-text-secondary", n.secondary],
6067
- ["--oc-text-faint", n.faint],
6068
- ["--oc-border", n.border],
6069
- ["--oc-gray-100", n[100]],
6070
- ["--oc-gray-200", n[200]],
6071
- ["--oc-gray-300", n[300]],
6072
- ["--oc-gray-400", n[400]],
6073
- ["--oc-gray-600", n[600]],
6074
- ["--oc-gray-800", n[800]],
6075
- ["--oc-gridline", theme.colors.gridline],
6076
- // --oc-axis is the hairline the axis line is drawn with, not the ink its
6077
- // tick labels take (that is --oc-text-muted, above).
6078
- ["--oc-axis", theme.colors.hairline],
6079
- ["--oc-border-radius", `${theme.borderRadius}px`],
6080
- ["--oc-accent", accent],
6081
- ["--oc-accent-strong", adaptForLightLineStroke(accent)],
6082
- ["--oc-positive", theme.colors.positive],
6083
- ["--oc-negative", theme.colors.negative],
6084
- ["--oc-space-2", `${theme.spacing.chromeGap * 2}px`],
6085
- ["--oc-space-4", `${theme.spacing.padding}px`]
6086
- ];
6087
- for (const [name, value] of props) {
6088
- el.style.setProperty(name, value);
6089
- }
6090
- }
6091
-
6092
5703
  // src/mount.ts
6093
5704
  var FALLBACK_HEIGHT = 400;
6094
5705
  var EDITABLE_HOVER_CSS = `
6095
5706
  .oc-editable-hover {
6096
- outline: 1.5px solid ${cssTokenDefault2("--oc-editable-hover", "light")};
5707
+ outline: 1.5px solid ${cssTokenDefault("--oc-editable-hover", "light")};
6097
5708
  outline-offset: 2px;
6098
5709
  border-radius: 2px;
6099
5710
  }
@@ -7411,7 +7022,7 @@ function renderBorders(parent, borders) {
7411
7022
  }
7412
7023
  parent.appendChild(g);
7413
7024
  }
7414
- function renderFeatures(parent, features, animation, staggerBudget = 0, focusIds) {
7025
+ function renderFeatures(parent, features, animation, staggerBudget = 0, focusIds, patternMap) {
7415
7026
  const g = createSVGElement2("g");
7416
7027
  g.setAttribute("class", "oc-map-features");
7417
7028
  g.setAttribute("role", "list");
@@ -7440,13 +7051,17 @@ function renderFeatures(parent, features, animation, staggerBudget = 0, focusIds
7440
7051
  const path = createSVGElement2("path");
7441
7052
  path.setAttribute("class", "oc-map-feature");
7442
7053
  path.setAttribute("role", "listitem");
7054
+ const fill = feature.pattern && patternMap ? resolvePatternFill(feature.pattern, patternMap) : feature.fill;
7443
7055
  setAttrs2(path, {
7444
7056
  d: feature.path,
7445
- fill: feature.fill,
7057
+ fill,
7446
7058
  stroke: feature.stroke,
7447
7059
  "stroke-width": feature.strokeWidth
7448
7060
  });
7449
7061
  path.setAttribute("data-feature-id", String(feature.id));
7062
+ if (feature.noData) {
7063
+ path.setAttribute("data-nodata", "true");
7064
+ }
7450
7065
  path.setAttribute("data-key", String(feature.id));
7451
7066
  if (feature.binIndex !== void 0) {
7452
7067
  path.setAttribute("data-bin-index", String(feature.binIndex));
@@ -7466,7 +7081,7 @@ function renderFeatures(parent, features, animation, staggerBudget = 0, focusIds
7466
7081
  const idx = feature.animationIndex ?? i;
7467
7082
  path.setAttribute("data-animation-index", String(idx));
7468
7083
  s.setProperty("--oc-mark-index", String(idx));
7469
- s.setProperty("--oc-feature-fill", feature.fill);
7084
+ s.setProperty("--oc-feature-fill", fill);
7470
7085
  if (dimmed) {
7471
7086
  s.setProperty("--oc-feature-target-opacity", String(FOCUS_DIM_OPACITY));
7472
7087
  }
@@ -7539,6 +7154,8 @@ function renderMapSVG(layout, opts) {
7539
7154
  }
7540
7155
  const defs = createSVGElement2("defs");
7541
7156
  svg.appendChild(defs);
7157
+ const noDataSwatch = layout.continuousLegend?.noData ?? layout.categoricalLegend?.noData;
7158
+ const patternMap = buildPatternDefs(noDataSwatch ? [...features, noDataSwatch] : features, defs);
7542
7159
  const mapGroup = createSVGElement2("g");
7543
7160
  mapGroup.setAttribute("class", "oc-map-group");
7544
7161
  mapGroup.setAttribute("transform", `translate(${layout.area.x},${layout.area.y})`);
@@ -7551,7 +7168,8 @@ function renderMapSVG(layout, opts) {
7551
7168
  features,
7552
7169
  animate ? animation : void 0,
7553
7170
  mapStaggerBudget,
7554
- focusIdSet
7171
+ focusIdSet,
7172
+ patternMap
7555
7173
  );
7556
7174
  renderBorders(cameraGroup, borders);
7557
7175
  if (animate && animation?.enter && layout.pointMarks.length > 1) {
@@ -7564,9 +7182,23 @@ function renderMapSVG(layout, opts) {
7564
7182
  svg.appendChild(mapGroup);
7565
7183
  renderChrome(svg, layout);
7566
7184
  if (layout.continuousLegend) {
7567
- renderLegend(svg, layout.continuousLegend);
7185
+ const contNoData = layout.continuousLegend.noData;
7186
+ renderLegend(
7187
+ svg,
7188
+ contNoData?.pattern ? {
7189
+ ...layout.continuousLegend,
7190
+ noData: { ...contNoData, fill: resolvePatternFill(contNoData.pattern, patternMap) }
7191
+ } : layout.continuousLegend
7192
+ );
7568
7193
  } else if (layout.categoricalLegend) {
7569
- renderLegend(svg, layout.categoricalLegend);
7194
+ const catNoData = layout.categoricalLegend.noData;
7195
+ renderLegend(
7196
+ svg,
7197
+ catNoData?.pattern ? {
7198
+ ...layout.categoricalLegend,
7199
+ noData: { ...catNoData, fill: resolvePatternFill(catNoData.pattern, patternMap) }
7200
+ } : layout.categoricalLegend
7201
+ );
7570
7202
  }
7571
7203
  const ptLegend = layout.pointCategoricalLegend ?? layout.pointContinuousLegend;
7572
7204
  if (ptLegend) {
@@ -8307,21 +7939,10 @@ export {
8307
7939
  createMeasureText,
8308
7940
  resolveFontFamily,
8309
7941
  scheduleFontReload,
8310
- observeResize,
8311
- resolveDarkMode,
8312
- createTooltipManager,
8313
7942
  VECTOR_EXPORT_MAX_POINTS,
8314
7943
  materializeCanvasModeSVG,
8315
7944
  applySrOnlyStyles,
8316
- AnimationScheduler,
8317
- SpatialIndex,
8318
- clamp01,
8319
- lerp,
8320
- resolveEase,
8321
- prefersReducedMotion,
8322
- createTween,
8323
7945
  createTextEditOverlay,
8324
- resolvedSurface,
8325
7946
  DEFAULT_UPDATE_MAX_MARKS,
8326
7947
  CANVAS_DEFAULT_UPDATE_MAX_MARKS,
8327
7948
  canTransitionSpecShape,
@@ -8329,7 +7950,7 @@ export {
8329
7950
  createChart,
8330
7951
  easingFns,
8331
7952
  storyMotion,
8332
- createTween2,
7953
+ createTween2 as createTween,
8333
7954
  FULL_VIEW,
8334
7955
  fitTarget,
8335
7956
  interpolateCamera,
@@ -8340,4 +7961,4 @@ export {
8340
7961
  scrubCamera,
8341
7962
  createGeoMap
8342
7963
  };
8343
- //# sourceMappingURL=chunk-45XZOWYC.js.map
7964
+ //# sourceMappingURL=chunk-5KV3ZBBL.js.map