@opendata-ai/openchart-vanilla 7.6.0 → 7.7.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,3 +1,9 @@
1
+ import {
2
+ registerMarkRenderer,
3
+ renderChartSVG,
4
+ renderLegend
5
+ } from "./chunk-PSMDJEXK.js";
6
+
1
7
  // src/barlist-mount.ts
2
8
  import { compileBarList } from "@opendata-ai/openchart-engine";
3
9
 
@@ -3364,7 +3370,7 @@ function createGraph(container, spec, options) {
3364
3370
  }
3365
3371
  chromeEl = document.createElement("div");
3366
3372
  chromeEl.className = "oc-graph-chrome";
3367
- renderChrome5();
3373
+ renderChrome4();
3368
3374
  wrapper.appendChild(chromeEl);
3369
3375
  canvas = document.createElement("canvas");
3370
3376
  canvas.className = "oc-graph-canvas";
@@ -3384,7 +3390,7 @@ function createGraph(container, spec, options) {
3384
3390
  renderer = new GraphCanvasRenderer(canvas);
3385
3391
  renderer.resize(width, canvasHeight);
3386
3392
  }
3387
- function renderChrome5() {
3393
+ function renderChrome4() {
3388
3394
  if (!chromeEl) return;
3389
3395
  let html = "";
3390
3396
  if (compilation.chrome.title) {
@@ -3698,7 +3704,7 @@ function createGraph(container, spec, options) {
3698
3704
  compilation = compile();
3699
3705
  adjacencyMap = buildAdjacencyMap(compilation.edges);
3700
3706
  buildDataMaps();
3701
- renderChrome5();
3707
+ renderChrome4();
3702
3708
  renderLegend3();
3703
3709
  initSimulation();
3704
3710
  initInteraction();
@@ -3733,7 +3739,7 @@ function createGraph(container, spec, options) {
3733
3739
  };
3734
3740
  });
3735
3741
  spatialIndex.rebuild(positionedNodes);
3736
- renderChrome5();
3742
+ renderChrome4();
3737
3743
  renderLegend3();
3738
3744
  needsRender = true;
3739
3745
  scheduleRender();
@@ -3830,7 +3836,7 @@ function escapeHtml(str) {
3830
3836
  }
3831
3837
 
3832
3838
  // src/mount.ts
3833
- import { isLayerSpec } from "@opendata-ai/openchart-core";
3839
+ import { isGraphSpec, isLayerSpec } from "@opendata-ai/openchart-core";
3834
3840
  import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
3835
3841
 
3836
3842
  // src/interactions/chart-events.ts
@@ -4329,7 +4335,7 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
4329
4335
  };
4330
4336
  }
4331
4337
  function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4332
- const SVG_NS6 = "http://www.w3.org/2000/svg";
4338
+ const SVG_NS4 = "http://www.w3.org/2000/svg";
4333
4339
  const cleanups = [];
4334
4340
  const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
4335
4341
  for (const el of annotationGroups) {
@@ -4368,7 +4374,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4368
4374
  const createdHandles = [];
4369
4375
  for (const ep of endpoints) {
4370
4376
  if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
4371
- const handleEl = document.createElementNS(SVG_NS6, "circle");
4377
+ const handleEl = document.createElementNS(SVG_NS4, "circle");
4372
4378
  handleEl.setAttribute("class", "oc-connector-handle");
4373
4379
  handleEl.setAttribute("data-endpoint", ep.name);
4374
4380
  handleEl.setAttribute("cx", String(ep.cx));
@@ -4983,1702 +4989,194 @@ function wireTooltipEvents(svg, tooltipDescriptors, tooltipManager) {
4983
4989
  };
4984
4990
  }
4985
4991
 
4986
- // src/svg-renderer.ts
4987
- import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
4988
-
4989
- // src/gradient-utils.ts
4990
- import { isGradientDef } from "@opendata-ai/openchart-core";
4991
-
4992
- // src/svg-ids.ts
4993
- var counter = 0;
4994
- function nextSvgId(prefix) {
4995
- return `${prefix}-${counter++}`;
4996
- }
4997
-
4998
- // src/gradient-utils.ts
4999
- var SVG_NS2 = "http://www.w3.org/2000/svg";
5000
- function gradientKey(def) {
5001
- return sortedStringify(def);
5002
- }
5003
- function sortedStringify(value) {
5004
- if (value === null || value === void 0) return String(value);
5005
- if (Array.isArray(value)) return `[${value.map(sortedStringify).join(",")}]`;
5006
- if (typeof value === "object") {
5007
- const sorted = Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${sortedStringify(value[k])}`);
5008
- return `{${sorted.join(",")}}`;
5009
- }
5010
- return JSON.stringify(value);
5011
- }
5012
- function createGradientElement(def, id) {
5013
- if (def.gradient === "linear") {
5014
- return createLinearGradient(def, id);
5015
- }
5016
- return createRadialGradient(def, id);
5017
- }
5018
- function createLinearGradient(def, id) {
5019
- const el = document.createElementNS(SVG_NS2, "linearGradient");
5020
- el.setAttribute("id", id);
5021
- el.setAttribute("gradientUnits", "objectBoundingBox");
5022
- el.setAttribute("x1", String(def.x1 ?? 0));
5023
- el.setAttribute("y1", String(def.y1 ?? 0));
5024
- el.setAttribute("x2", String(def.x2 ?? 0));
5025
- el.setAttribute("y2", String(def.y2 ?? 1));
5026
- for (const stop of def.stops) {
5027
- appendStop(el, stop);
5028
- }
5029
- return el;
5030
- }
5031
- function createRadialGradient(def, id) {
5032
- const el = document.createElementNS(SVG_NS2, "radialGradient");
5033
- el.setAttribute("id", id);
5034
- el.setAttribute("gradientUnits", "objectBoundingBox");
5035
- el.setAttribute("cx", String(def.x2 ?? 0.5));
5036
- el.setAttribute("cy", String(def.y2 ?? 0.5));
5037
- el.setAttribute("r", String(def.r2 ?? 0.5));
5038
- el.setAttribute("fx", String(def.x1 ?? 0.5));
5039
- el.setAttribute("fy", String(def.y1 ?? 0.5));
5040
- el.setAttribute("fr", String(def.r1 ?? 0));
5041
- for (const stop of def.stops) {
5042
- appendStop(el, stop);
5043
- }
5044
- return el;
5045
- }
5046
- function appendStop(parent, stop) {
5047
- const stopEl = document.createElementNS(SVG_NS2, "stop");
5048
- stopEl.setAttribute("offset", String(stop.offset));
5049
- stopEl.setAttribute("stop-color", stop.color);
5050
- if (stop.opacity !== void 0) {
5051
- stopEl.setAttribute("stop-opacity", String(stop.opacity));
5052
- }
5053
- parent.appendChild(stopEl);
5054
- }
5055
- function buildGradientDefs(marks, defs) {
5056
- const map = /* @__PURE__ */ new Map();
5057
- for (const mark of marks) {
5058
- const fill = mark.fill;
5059
- if (fill && isGradientDef(fill)) {
5060
- const key = gradientKey(fill);
5061
- if (!map.has(key)) {
5062
- const id = nextSvgId("oc-grad");
5063
- const el = createGradientElement(fill, id);
5064
- defs.appendChild(el);
5065
- map.set(key, id);
5066
- }
5067
- }
5068
- }
5069
- return map;
5070
- }
5071
- function resolveMarkFill(fill, gradientMap) {
5072
- if (typeof fill === "string") return fill;
5073
- const key = gradientKey(fill);
5074
- const id = gradientMap.get(key);
5075
- return id ? `url(#${id})` : "#000000";
4992
+ // src/text-edit-overlay.ts
4993
+ function getScale(svg) {
4994
+ const viewBox = svg.viewBox?.baseVal;
4995
+ const svgRect = svg.getBoundingClientRect();
4996
+ return {
4997
+ scaleX: viewBox?.width && svgRect.width ? svgRect.width / viewBox.width : 1,
4998
+ scaleY: viewBox?.height && svgRect.height ? svgRect.height / viewBox.height : 1
4999
+ };
5076
5000
  }
5077
-
5078
- // src/renderers/svg-dom.ts
5079
- var SVG_NS3 = "http://www.w3.org/2000/svg";
5080
- var XLINK_NS2 = "http://www.w3.org/1999/xlink";
5081
- function createSVGElement2(tag) {
5082
- return document.createElementNS(SVG_NS3, tag);
5001
+ function getTextStyles(targetElement, scale) {
5002
+ const computed = window.getComputedStyle(targetElement);
5003
+ const svgFontSize = parseFloat(
5004
+ targetElement.getAttribute("font-size") ?? computed.fontSize ?? "14"
5005
+ );
5006
+ const pixelFontSize = svgFontSize * scale.scaleY;
5007
+ const fontFamily = targetElement.getAttribute("font-family") ?? computed.fontFamily ?? "inherit";
5008
+ const fontWeight = targetElement.getAttribute("font-weight") ?? computed.fontWeight ?? "400";
5009
+ const fill = targetElement.style.getPropertyValue("fill") || targetElement.getAttribute("fill") || computed.color || "#000";
5010
+ const textAnchor = targetElement.getAttribute("text-anchor") ?? "start";
5011
+ let textAlign = "left";
5012
+ if (textAnchor === "middle") textAlign = "center";
5013
+ else if (textAnchor === "end") textAlign = "right";
5014
+ return {
5015
+ fontFamily,
5016
+ fontSize: `${pixelFontSize}px`,
5017
+ fontWeight,
5018
+ color: fill,
5019
+ textAlign,
5020
+ lineHeight: "1.3"
5021
+ };
5083
5022
  }
5084
- function setAttrs2(el, attrs) {
5085
- for (const [key, value] of Object.entries(attrs)) {
5086
- el.setAttribute(key, String(value));
5087
- }
5023
+ function computePosition2(targetElement, svg, container) {
5024
+ const bbox = targetElement.getBBox();
5025
+ const scale = getScale(svg);
5026
+ const svgRect = svg.getBoundingClientRect();
5027
+ const containerRect = container.getBoundingClientRect();
5028
+ const padding = 4;
5029
+ const left = bbox.x * scale.scaleX + (svgRect.left - containerRect.left) - padding;
5030
+ const top = bbox.y * scale.scaleY + (svgRect.top - containerRect.top) - padding;
5031
+ const width = bbox.width * scale.scaleX + padding * 2;
5032
+ const height = bbox.height * scale.scaleY + padding * 2;
5033
+ return {
5034
+ top: Math.max(0, top),
5035
+ left: Math.max(0, left),
5036
+ width: Math.max(width, 60),
5037
+ height: Math.max(height, 24)
5038
+ };
5088
5039
  }
5089
- function applyTextStyle(el, style) {
5090
- const inline = el.style;
5091
- inline.setProperty("fill", style.fill);
5092
- inline.setProperty("font-size", `${style.fontSize}px`);
5093
- inline.setProperty("font-weight", String(style.fontWeight));
5094
- inline.setProperty("font-family", style.fontFamily);
5095
- if (style.textAnchor) {
5096
- el.setAttribute("text-anchor", style.textAnchor);
5097
- }
5098
- if (style.dominantBaseline) {
5099
- el.setAttribute("dominant-baseline", style.dominantBaseline);
5100
- }
5101
- if (style.fontVariant) {
5102
- el.setAttribute("font-variant", style.fontVariant);
5040
+ function createTextEditOverlay(config) {
5041
+ const { container, svg, targetElement, currentText, onCommit, onCancel } = config;
5042
+ let destroyed = false;
5043
+ const originalOpacity = targetElement.getAttribute("opacity");
5044
+ targetElement.setAttribute("opacity", "0");
5045
+ const scale = getScale(svg);
5046
+ const styles = getTextStyles(targetElement, scale);
5047
+ const position = computePosition2(targetElement, svg, container);
5048
+ const textarea = document.createElement("textarea");
5049
+ textarea.value = currentText;
5050
+ const containerPosition = window.getComputedStyle(container).position;
5051
+ const containerWasStatic = containerPosition === "static";
5052
+ if (containerWasStatic) {
5053
+ container.style.position = "relative";
5103
5054
  }
5104
- }
5105
-
5106
- // src/renderers/annotations.ts
5107
- function renderCurvedArrow(parent, from, to, stroke) {
5108
- const pad = 6;
5109
- const tipY = to.y - pad;
5110
- const dy = tipY - from.y;
5111
- const dist = Math.sqrt((to.x - from.x) ** 2 + dy ** 2) || 1;
5112
- const arrowLen = 8;
5113
- const arrowWidth = 4;
5114
- const bulge = Math.max(dist * 0.4, 35);
5115
- const cp1x = from.x + bulge;
5116
- const cp1y = from.y + dy * 0.35;
5117
- const cp2x = to.x;
5118
- const cp2y = tipY - Math.abs(dy) * 0.25;
5119
- const tx = to.x - cp2x;
5120
- const ty = tipY - cp2y;
5121
- const tLen = Math.sqrt(tx * tx + ty * ty) || 1;
5122
- const ux = tx / tLen;
5123
- const uy = ty / tLen;
5124
- const baseX = to.x - ux * arrowLen;
5125
- const baseY = tipY - uy * arrowLen;
5126
- const path = createSVGElement2("path");
5127
- path.setAttribute("class", "oc-annotation-connector");
5128
- setAttrs2(path, {
5129
- d: `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${baseX} ${baseY}`,
5130
- fill: "none",
5131
- stroke,
5132
- "stroke-width": 1.5
5133
- });
5134
- parent.appendChild(path);
5135
- const px = -uy;
5136
- const py = ux;
5137
- const arrow = createSVGElement2("polygon");
5138
- arrow.setAttribute("class", "oc-annotation-connector");
5139
- setAttrs2(arrow, {
5140
- points: [
5141
- `${to.x},${tipY}`,
5142
- `${baseX + px * arrowWidth},${baseY + py * arrowWidth}`,
5143
- `${baseX - px * arrowWidth},${baseY - py * arrowWidth}`
5144
- ].join(" "),
5145
- fill: stroke
5055
+ Object.assign(textarea.style, {
5056
+ position: "absolute",
5057
+ top: `${position.top}px`,
5058
+ left: `${position.left}px`,
5059
+ width: `${position.width}px`,
5060
+ minHeight: `${position.height}px`,
5061
+ fontFamily: styles.fontFamily,
5062
+ fontSize: styles.fontSize,
5063
+ fontWeight: styles.fontWeight,
5064
+ color: styles.color,
5065
+ textAlign: styles.textAlign,
5066
+ lineHeight: styles.lineHeight,
5067
+ padding: "2px 4px",
5068
+ margin: "0",
5069
+ border: "1px solid rgba(79, 70, 229, 0.4)",
5070
+ borderRadius: "3px",
5071
+ background: "rgba(255, 255, 255, 0.95)",
5072
+ outline: "none",
5073
+ resize: "none",
5074
+ overflow: "hidden",
5075
+ boxSizing: "border-box",
5076
+ zIndex: "10000",
5077
+ // Remove default textarea appearance
5078
+ WebkitAppearance: "none",
5079
+ appearance: "none"
5146
5080
  });
5147
- parent.appendChild(arrow);
5148
- }
5149
- function renderAnnotation(parent, annotation, index2, bgColor) {
5150
- const g = createSVGElement2("g");
5151
- g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
5152
- g.setAttribute("data-annotation-index", String(index2));
5153
- if (annotation.id) {
5154
- g.setAttribute("data-annotation-id", annotation.id);
5155
- }
5156
- if (annotation.rect) {
5157
- const rect = createSVGElement2("rect");
5158
- rect.setAttribute("class", "oc-annotation-range");
5159
- rect.setAttribute("pointer-events", "none");
5160
- setAttrs2(rect, {
5161
- x: annotation.rect.x,
5162
- y: annotation.rect.y,
5163
- width: annotation.rect.width,
5164
- height: annotation.rect.height
5165
- });
5166
- if (annotation.fill) rect.setAttribute("fill", annotation.fill);
5167
- if (annotation.opacity !== void 0) {
5168
- rect.setAttribute("fill-opacity", String(annotation.opacity));
5169
- }
5170
- g.appendChild(rect);
5171
- }
5172
- if (annotation.line) {
5173
- const line = createSVGElement2("line");
5174
- line.setAttribute("class", "oc-annotation-line");
5175
- setAttrs2(line, {
5176
- x1: annotation.line.start.x,
5177
- y1: annotation.line.start.y,
5178
- x2: annotation.line.end.x,
5179
- y2: annotation.line.end.y,
5180
- "stroke-width": annotation.strokeWidth ?? 1
5181
- });
5182
- if (annotation.stroke) line.setAttribute("stroke", annotation.stroke);
5183
- if (annotation.strokeDasharray) {
5184
- line.setAttribute("stroke-dasharray", annotation.strokeDasharray);
5185
- }
5186
- g.appendChild(line);
5187
- }
5188
- if (annotation.label?.visible) {
5189
- if (annotation.label.connector) {
5190
- const c2 = annotation.label.connector;
5191
- if (c2.style === "curve") {
5192
- renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
5193
- } else if (c2.style === "drop-line") {
5194
- const connector = createSVGElement2("line");
5195
- connector.setAttribute("class", "oc-annotation-connector oc-annotation-drop-line");
5196
- setAttrs2(connector, {
5197
- x1: c2.from.x,
5198
- y1: c2.from.y,
5199
- x2: c2.to.x,
5200
- y2: c2.to.y,
5201
- stroke: c2.stroke,
5202
- "stroke-width": 1,
5203
- "stroke-opacity": 0.6,
5204
- "shape-rendering": "crispEdges"
5205
- });
5206
- g.appendChild(connector);
5207
- } else {
5208
- const connector = createSVGElement2("line");
5209
- connector.setAttribute("class", "oc-annotation-connector");
5210
- setAttrs2(connector, {
5211
- x1: c2.from.x,
5212
- y1: c2.from.y,
5213
- x2: c2.to.x,
5214
- y2: c2.to.y,
5215
- stroke: c2.stroke,
5216
- "stroke-width": 1,
5217
- "stroke-opacity": 0.5
5218
- });
5219
- g.appendChild(connector);
5220
- }
5221
- if (c2.endpoint && c2.style !== "curve") {
5222
- const ring = createSVGElement2("circle");
5223
- ring.setAttribute("class", "oc-annotation-endpoint-ring");
5224
- setAttrs2(ring, {
5225
- cx: c2.endpoint.x,
5226
- cy: c2.endpoint.y,
5227
- r: 5,
5228
- fill: bgColor ?? "#ffffff",
5229
- stroke: c2.stroke,
5230
- "stroke-width": 1.5
5231
- });
5232
- g.appendChild(ring);
5233
- const dot = createSVGElement2("circle");
5234
- dot.setAttribute("class", "oc-annotation-endpoint-dot");
5235
- setAttrs2(dot, {
5236
- cx: c2.endpoint.x,
5237
- cy: c2.endpoint.y,
5238
- r: 2,
5239
- fill: c2.stroke
5240
- });
5241
- g.appendChild(dot);
5242
- }
5243
- }
5244
- if (annotation.dot) {
5245
- const dot = createSVGElement2("circle");
5246
- dot.setAttribute("class", "oc-annotation-dot");
5247
- setAttrs2(dot, {
5248
- cx: annotation.dot.x,
5249
- cy: annotation.dot.y,
5250
- r: annotation.dot.radius,
5251
- fill: annotation.dot.fill,
5252
- stroke: annotation.dot.stroke,
5253
- "stroke-width": annotation.dot.strokeWidth
5254
- });
5255
- g.appendChild(dot);
5256
- }
5257
- const text = createSVGElement2("text");
5258
- text.setAttribute("class", "oc-annotation-label");
5259
- setAttrs2(text, { x: annotation.label.x, y: annotation.label.y });
5260
- applyTextStyle(text, annotation.label.style);
5261
- const lines = annotation.label.text.split("\n");
5262
- const fontSize = annotation.label.style.fontSize ?? 12;
5263
- const lineHeight = fontSize * (annotation.label.style.lineHeight ?? 1.3);
5264
- const isMultiLine = lines.length > 1;
5265
- if (isMultiLine) {
5266
- for (let i = 0; i < lines.length; i++) {
5267
- const tspan = createSVGElement2("tspan");
5268
- setAttrs2(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
5269
- tspan.textContent = lines[i];
5270
- text.appendChild(tspan);
5271
- }
5081
+ container.appendChild(textarea);
5082
+ textarea.focus();
5083
+ textarea.select();
5084
+ function destroy() {
5085
+ if (destroyed) return;
5086
+ destroyed = true;
5087
+ if (originalOpacity !== null) {
5088
+ targetElement.setAttribute("opacity", originalOpacity);
5272
5089
  } else {
5273
- text.textContent = annotation.label.text;
5274
- }
5275
- if (annotation.label.background) {
5276
- const pad = 3;
5277
- let bgX;
5278
- let bgY;
5279
- let bgW;
5280
- let bgH;
5281
- if (annotation.label.bounds) {
5282
- const b = annotation.label.bounds;
5283
- bgX = b.x - pad;
5284
- bgY = b.y - pad;
5285
- bgW = b.width + pad * 2;
5286
- bgH = b.height + pad * 2;
5287
- } else {
5288
- const charWidth = fontSize * 0.55;
5289
- const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
5290
- const totalHeight = lines.length * lineHeight;
5291
- bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
5292
- bgY = annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad;
5293
- bgW = maxLineWidth + pad * 2;
5294
- bgH = totalHeight + pad * 2;
5295
- }
5296
- const bgRect = createSVGElement2("rect");
5297
- bgRect.setAttribute("class", "oc-annotation-bg");
5298
- setAttrs2(bgRect, {
5299
- x: bgX,
5300
- y: bgY,
5301
- width: bgW,
5302
- height: bgH,
5303
- fill: annotation.label.background,
5304
- rx: 2
5305
- });
5306
- g.appendChild(bgRect);
5307
- } else if (bgColor && annotation.label.halo !== false) {
5308
- text.style.paintOrder = "stroke";
5309
- text.style.stroke = bgColor;
5310
- text.style.strokeWidth = `${Math.round(fontSize * 0.3)}px`;
5311
- text.style.strokeLinejoin = "round";
5090
+ targetElement.removeAttribute("opacity");
5312
5091
  }
5313
- g.appendChild(text);
5314
- if (annotation.subtitle) {
5315
- const sub = createSVGElement2("text");
5316
- sub.setAttribute("class", "oc-annotation-subtitle");
5317
- setAttrs2(sub, { x: annotation.subtitle.x, y: annotation.subtitle.y });
5318
- applyTextStyle(sub, annotation.subtitle.style);
5319
- sub.textContent = annotation.subtitle.text;
5320
- g.appendChild(sub);
5092
+ textarea.removeEventListener("keydown", handleKeyDown);
5093
+ document.removeEventListener("mousedown", handleClickOutside);
5094
+ resizeObserver.disconnect();
5095
+ if (containerWasStatic) {
5096
+ container.style.position = "";
5097
+ }
5098
+ if (textarea.parentNode) {
5099
+ textarea.parentNode.removeChild(textarea);
5321
5100
  }
5322
5101
  }
5323
- parent.appendChild(g);
5324
- }
5325
- function renderAnnotations(parent, layout) {
5326
- if (layout.annotations.length === 0) return;
5327
- const g = createSVGElement2("g");
5328
- g.setAttribute("class", "oc-annotations");
5329
- const bgColor = layout.theme.colors.background;
5330
- for (let i = 0; i < layout.annotations.length; i++) {
5331
- renderAnnotation(g, layout.annotations[i], i, bgColor);
5102
+ function commit() {
5103
+ if (destroyed) return;
5104
+ const newText = textarea.value;
5105
+ destroy();
5106
+ onCommit(newText);
5332
5107
  }
5333
- parent.appendChild(g);
5334
- }
5335
-
5336
- // src/renderers/axes.ts
5337
- import {
5338
- axisTitleOffset,
5339
- estimateTextWidth as estimateTextWidth2,
5340
- getAxisTitleOffset,
5341
- TICK_LABEL_OFFSET,
5342
- textAscent as textAscent2
5343
- } from "@opendata-ai/openchart-core";
5344
- function appendCompoundLabel(parent, primaryText, subtitle, fontWeight) {
5345
- const primarySpan = createSVGElement2("tspan");
5346
- primarySpan.setAttribute("font-weight", String(fontWeight));
5347
- primarySpan.textContent = primaryText;
5348
- parent.appendChild(primarySpan);
5349
- const subtitleSpan = createSVGElement2("tspan");
5350
- subtitleSpan.setAttribute("dx", "0.5em");
5351
- subtitleSpan.textContent = subtitle;
5352
- subtitleSpan.setAttribute("font-weight", "400");
5353
- subtitleSpan.setAttribute("fill-opacity", "0.6");
5354
- parent.appendChild(subtitleSpan);
5355
- }
5356
- function renderAxis(parent, axis, orientation, layout) {
5357
- const g = createSVGElement2("g");
5358
- const isRight = orientation === "y" && axis.orient === "right";
5359
- const isInlineY = orientation === "y" && axis.tickPosition === "inline" && !isRight;
5360
- g.setAttribute(
5361
- "class",
5362
- `oc-axis oc-axis-${isRight ? "y2" : orientation}${isInlineY ? " oc-axis-inline" : ""}`
5363
- );
5364
- const { area } = layout;
5365
- if (orientation === "x" && axis.domainLine !== false) {
5366
- const line = createSVGElement2("line");
5367
- line.setAttribute("class", "oc-axis-line");
5368
- setAttrs2(line, {
5369
- x1: axis.start.x,
5370
- y1: axis.start.y,
5371
- x2: axis.end.x,
5372
- y2: axis.end.y,
5373
- stroke: layout.theme.colors.axis,
5374
- "stroke-width": 1
5375
- });
5376
- g.appendChild(line);
5377
- }
5378
- for (const tick of axis.ticks) {
5379
- if (orientation === "x") {
5380
- const label = createSVGElement2("text");
5381
- label.setAttribute("class", "oc-axis-tick");
5382
- if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
5383
- const labelX = tick.position;
5384
- const xLabelPad = axis.labelPadding ?? layout.theme.spacing.xAxisLabelPadding;
5385
- const labelY = area.y + area.height + xLabelPad;
5386
- setAttrs2(label, {
5387
- x: labelX,
5388
- y: labelY,
5389
- "text-anchor": axis.tickAngle < 0 ? "end" : "start",
5390
- "dominant-baseline": "central",
5391
- transform: `rotate(${axis.tickAngle}, ${labelX}, ${labelY})`
5392
- });
5393
- } else {
5394
- const xLabelPad = axis.labelPadding ?? layout.theme.spacing.xAxisLabelPadding;
5395
- setAttrs2(label, {
5396
- x: tick.position,
5397
- y: area.y + area.height + xLabelPad + textAscent2(axis.tickLabelStyle.fontSize),
5398
- "text-anchor": "middle"
5399
- });
5400
- }
5401
- applyTextStyle(label, axis.tickLabelStyle);
5402
- label.textContent = tick.label;
5403
- g.appendChild(label);
5404
- } else if (isInlineY) {
5405
- const label = createSVGElement2("text");
5406
- label.setAttribute("class", "oc-axis-tick oc-axis-tick-inline");
5407
- setAttrs2(label, {
5408
- x: area.x,
5409
- y: tick.position - 6,
5410
- "text-anchor": "start"
5411
- });
5412
- applyTextStyle(label, axis.tickLabelStyle);
5413
- label.textContent = tick.label;
5414
- g.appendChild(label);
5415
- } else {
5416
- const label = createSVGElement2("text");
5417
- label.setAttribute("class", "oc-axis-tick");
5418
- setAttrs2(label, {
5419
- x: isRight ? area.x + area.width + TICK_LABEL_OFFSET : area.x - TICK_LABEL_OFFSET,
5420
- y: tick.position,
5421
- "text-anchor": isRight ? "start" : "end",
5422
- "dominant-baseline": "central"
5423
- });
5424
- applyTextStyle(label, axis.tickLabelStyle);
5425
- if (!isRight) {
5426
- const availableWidth = area.x - TICK_LABEL_OFFSET;
5427
- const fontSize = axis.tickLabelStyle.fontSize;
5428
- const fontWeight = axis.tickLabelStyle.fontWeight;
5429
- if (tick.subtitle) {
5430
- const gapWidth = fontSize * 0.5;
5431
- const subtitleWidth = estimateTextWidth2(tick.subtitle, fontSize, fontWeight);
5432
- const primaryWidth = estimateTextWidth2(tick.label, fontSize, fontWeight);
5433
- const totalWidth = primaryWidth + gapWidth + subtitleWidth;
5434
- if (totalWidth > availableWidth && availableWidth > 20) {
5435
- const ellipsis = "\u2026";
5436
- const ellipsisWidth = estimateTextWidth2(ellipsis, fontSize, fontWeight);
5437
- const budgetForPrimary = availableWidth - gapWidth - subtitleWidth - ellipsisWidth;
5438
- let primaryText = tick.label;
5439
- if (budgetForPrimary > 0) {
5440
- let lo = 0;
5441
- let hi = tick.label.length;
5442
- while (lo < hi) {
5443
- const mid = lo + hi + 1 >>> 1;
5444
- const candidate = tick.label.slice(0, mid);
5445
- if (estimateTextWidth2(candidate, fontSize, fontWeight) <= budgetForPrimary) {
5446
- lo = mid;
5447
- } else {
5448
- hi = mid - 1;
5449
- }
5450
- }
5451
- primaryText = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;
5452
- } else {
5453
- primaryText = ellipsis;
5454
- }
5455
- appendCompoundLabel(label, primaryText, tick.subtitle, fontWeight);
5456
- const titleEl = createSVGElement2("title");
5457
- titleEl.textContent = `${tick.label} ${tick.subtitle}`;
5458
- label.appendChild(titleEl);
5459
- } else {
5460
- appendCompoundLabel(label, tick.label, tick.subtitle, fontWeight);
5461
- }
5462
- } else {
5463
- const fullWidth = estimateTextWidth2(tick.label, fontSize, fontWeight);
5464
- if (fullWidth > availableWidth && availableWidth > 20) {
5465
- const ellipsis = "\u2026";
5466
- const ellipsisWidth = estimateTextWidth2(ellipsis, fontSize, fontWeight);
5467
- let lo = 0;
5468
- let hi = tick.label.length;
5469
- while (lo < hi) {
5470
- const mid = lo + hi + 1 >>> 1;
5471
- const candidate = tick.label.slice(0, mid);
5472
- if (estimateTextWidth2(candidate, fontSize, fontWeight) + ellipsisWidth <= availableWidth) {
5473
- lo = mid;
5474
- } else {
5475
- hi = mid - 1;
5476
- }
5477
- }
5478
- label.textContent = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;
5479
- const titleEl = createSVGElement2("title");
5480
- titleEl.textContent = tick.label;
5481
- label.appendChild(titleEl);
5482
- } else {
5483
- label.textContent = tick.label;
5484
- }
5485
- }
5486
- } else {
5487
- label.textContent = tick.label;
5488
- }
5489
- g.appendChild(label);
5490
- }
5491
- }
5492
- if (!isRight) {
5493
- for (const gridline of axis.gridlines) {
5494
- const gl = createSVGElement2("line");
5495
- gl.setAttribute("class", "oc-gridline");
5496
- if (orientation === "y") {
5497
- setAttrs2(gl, {
5498
- x1: area.x,
5499
- y1: gridline.position,
5500
- x2: area.x + area.width,
5501
- y2: gridline.position,
5502
- stroke: layout.theme.colors.gridline,
5503
- "stroke-width": 1,
5504
- "stroke-opacity": 0.6
5505
- });
5506
- } else {
5507
- setAttrs2(gl, {
5508
- x1: gridline.position,
5509
- y1: area.y,
5510
- x2: gridline.position,
5511
- y2: area.y + area.height,
5512
- stroke: layout.theme.colors.gridline,
5513
- "stroke-width": 1,
5514
- "stroke-opacity": 0.6
5515
- });
5516
- }
5517
- g.appendChild(gl);
5518
- }
5108
+ function cancel() {
5109
+ if (destroyed) return;
5110
+ destroy();
5111
+ onCancel();
5519
5112
  }
5520
- if (axis.label && axis.labelStyle) {
5521
- const axisLabel = createSVGElement2("text");
5522
- axisLabel.setAttribute("class", "oc-axis-title");
5523
- applyTextStyle(axisLabel, axis.labelStyle);
5524
- axisLabel.textContent = axis.label;
5525
- const tp = axis.titlePosition;
5526
- if (tp) {
5527
- const attrs = {
5528
- x: tp.x,
5529
- y: tp.y,
5530
- "text-anchor": "middle"
5531
- };
5532
- if (tp.angle) {
5533
- attrs.transform = `rotate(${tp.angle}, ${tp.x}, ${tp.y})`;
5534
- }
5535
- setAttrs2(axisLabel, attrs);
5536
- } else if (orientation === "x") {
5537
- let titleY = area.y + area.height + 35;
5538
- if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
5539
- const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);
5540
- let maxLabelWidth = 40;
5541
- for (const tick of axis.ticks) {
5542
- const w = estimateTextWidth2(
5543
- tick.label,
5544
- axis.tickLabelStyle.fontSize,
5545
- axis.tickLabelStyle.fontWeight
5546
- );
5547
- if (w > maxLabelWidth) maxLabelWidth = w;
5548
- }
5549
- const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
5550
- titleY = area.y + area.height + rotatedHeight + 14;
5551
- }
5552
- setAttrs2(axisLabel, {
5553
- x: area.x + area.width / 2,
5554
- y: titleY,
5555
- "text-anchor": "middle"
5556
- });
5557
- } else if (isRight) {
5558
- const titleOffset = getAxisTitleOffset(layout.dimensions.width);
5559
- const titleX = area.x + area.width + titleOffset;
5560
- setAttrs2(axisLabel, {
5561
- x: titleX,
5562
- y: area.y + area.height / 2,
5563
- "text-anchor": "middle",
5564
- transform: `rotate(90, ${titleX}, ${area.y + area.height / 2})`
5565
- });
5566
- } else {
5567
- const maxTickLabelWidth = axis.ticks.reduce((max, t) => {
5568
- const w = estimateTextWidth2(
5569
- t.label,
5570
- axis.tickLabelStyle.fontSize,
5571
- axis.tickLabelStyle.fontWeight ?? 400
5572
- );
5573
- return Math.max(max, w);
5574
- }, 0);
5575
- const titleOffset = axisTitleOffset(
5576
- maxTickLabelWidth,
5577
- axis.labelStyle.fontSize,
5578
- layout.dimensions.width
5579
- );
5580
- setAttrs2(axisLabel, {
5581
- x: area.x - titleOffset,
5582
- y: area.y + area.height / 2,
5583
- "text-anchor": "middle",
5584
- transform: `rotate(-90, ${area.x - titleOffset}, ${area.y + area.height / 2})`
5585
- });
5113
+ const handleKeyDown = (e) => {
5114
+ if (e.key === "Enter" && !e.shiftKey) {
5115
+ e.preventDefault();
5116
+ commit();
5117
+ } else if (e.key === "Escape") {
5118
+ e.preventDefault();
5119
+ cancel();
5586
5120
  }
5587
- g.appendChild(axisLabel);
5588
- }
5589
- parent.appendChild(g);
5121
+ };
5122
+ textarea.addEventListener("keydown", handleKeyDown);
5123
+ const handleClickOutside = (e) => {
5124
+ if (!textarea.contains(e.target)) {
5125
+ commit();
5126
+ }
5127
+ };
5128
+ requestAnimationFrame(() => {
5129
+ if (!destroyed) {
5130
+ document.addEventListener("mousedown", handleClickOutside);
5131
+ }
5132
+ });
5133
+ const resizeObserver = new ResizeObserver(() => {
5134
+ if (destroyed) return;
5135
+ const newPosition = computePosition2(targetElement, svg, container);
5136
+ textarea.style.top = `${newPosition.top}px`;
5137
+ textarea.style.left = `${newPosition.left}px`;
5138
+ textarea.style.width = `${newPosition.width}px`;
5139
+ textarea.style.minHeight = `${newPosition.height}px`;
5140
+ });
5141
+ resizeObserver.observe(container);
5142
+ return { destroy };
5590
5143
  }
5591
- function renderAxes(parent, layout) {
5592
- if (layout.axes.x) {
5593
- renderAxis(parent, layout.axes.x, "x", layout);
5594
- }
5595
- if (layout.axes.y) {
5596
- renderAxis(parent, layout.axes.y, "y", layout);
5597
- }
5598
- if (layout.axes.y2) {
5599
- renderAxis(parent, layout.axes.y2, "y", layout);
5144
+
5145
+ // src/theme-tokens.ts
5146
+ import { adaptForLightLineStroke } from "@opendata-ai/openchart-core";
5147
+ function stampThemeProperties(el, theme) {
5148
+ const accent = theme.colors.categorical[0] ?? "#06b6d4";
5149
+ const bg = theme.colors.background === "transparent" ? theme.isDark ? "#09090b" : "#ffffff" : theme.colors.background;
5150
+ const props = [
5151
+ ["--oc-font-family", theme.fonts.family],
5152
+ ["--oc-font-mono", theme.fonts.mono],
5153
+ ["--oc-title-size", `${theme.chrome.title.fontSize}px`],
5154
+ ["--oc-title-weight", `${theme.chrome.title.fontWeight}`],
5155
+ ["--oc-subtitle-size", `${theme.chrome.subtitle.fontSize}px`],
5156
+ ["--oc-subtitle-weight", `${theme.chrome.subtitle.fontWeight}`],
5157
+ ["--oc-source-size", `${theme.chrome.source.fontSize}px`],
5158
+ ["--oc-source-weight", `${theme.chrome.source.fontWeight}`],
5159
+ ["--oc-body-size", `${theme.fonts.sizes.body}px`],
5160
+ ["--oc-eyebrow-size", `${theme.chrome.eyebrow.fontSize}px`],
5161
+ ["--oc-eyebrow-weight", `${theme.chrome.eyebrow.fontWeight}`],
5162
+ ["--oc-bg", bg],
5163
+ ["--oc-text", theme.colors.text],
5164
+ ["--oc-text-muted", theme.colors.axis],
5165
+ ["--oc-gridline", theme.colors.gridline],
5166
+ ["--oc-axis", theme.colors.axis],
5167
+ ["--oc-border-radius", `${theme.borderRadius}px`],
5168
+ ["--oc-accent", accent],
5169
+ ["--oc-accent-strong", adaptForLightLineStroke(accent)],
5170
+ ["--oc-positive", theme.colors.positive],
5171
+ ["--oc-negative", theme.colors.negative],
5172
+ ["--oc-space-2", `${theme.spacing.chromeGap * 2}px`],
5173
+ ["--oc-space-4", `${theme.spacing.padding}px`]
5174
+ ];
5175
+ for (const [name, value] of props) {
5176
+ el.style.setProperty(name, value);
5600
5177
  }
5601
5178
  }
5602
5179
 
5603
- // src/renderers/brand.ts
5604
- import { BRAND_FONT_SIZE as BRAND_FONT_SIZE2, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2, textAscent as textAscent3 } from "@opendata-ai/openchart-core";
5605
- var BRAND_URL2 = "https://tryopendata.ai";
5606
- function renderBrand(parent, layout) {
5607
- if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
5608
- const { width } = layout.dimensions;
5609
- const padding = layout.theme.spacing.padding;
5610
- const rightEdge = width - padding;
5611
- const fill = layout.theme.colors.axis;
5612
- const { chrome } = layout;
5613
- const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
5614
- const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
5615
- const { legend } = layout;
5616
- const bottomLegendOffset = legend.position === "bottom" && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;
5617
- const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter + bottomLegendOffset;
5618
- const a2 = createSVGElement2("a");
5619
- a2.setAttribute("href", BRAND_URL2);
5620
- a2.setAttributeNS(XLINK_NS2, "xlink:href", BRAND_URL2);
5621
- a2.setAttribute("target", "_blank");
5622
- a2.setAttribute("rel", "noopener");
5623
- a2.setAttribute("class", "oc-chrome-ref");
5624
- const BRAND_LARGE = 16;
5625
- const text = createSVGElement2("text");
5626
- setAttrs2(text, {
5627
- x: rightEdge,
5628
- y: chromeY + textAscent3(BRAND_LARGE),
5629
- "font-family": layout.theme.fonts.family,
5630
- "font-size": BRAND_FONT_SIZE2,
5631
- "text-anchor": "end",
5632
- "fill-opacity": 0.55
5633
- });
5634
- text.style.setProperty("fill", fill);
5635
- const trySpan = createSVGElement2("tspan");
5636
- trySpan.setAttribute("font-weight", "500");
5637
- trySpan.textContent = "try";
5638
- text.appendChild(trySpan);
5639
- const openDataSpan = createSVGElement2("tspan");
5640
- openDataSpan.setAttribute("font-weight", "600");
5641
- openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
5642
- openDataSpan.textContent = "OpenData";
5643
- text.appendChild(openDataSpan);
5644
- const aiSpan = createSVGElement2("tspan");
5645
- aiSpan.setAttribute("font-weight", "500");
5646
- aiSpan.textContent = ".ai";
5647
- text.appendChild(aiSpan);
5648
- a2.appendChild(text);
5649
- parent.appendChild(a2);
5650
- }
5651
-
5652
- // src/renderers/chrome.ts
5653
- import { estimateTextWidth as estimateTextWidth3, textAscent as textAscent4, wrapText } from "@opendata-ai/openchart-core";
5654
- function renderChromeElement(parent, element, className, chromeKey, measureText, uppercase = false) {
5655
- const text = createSVGElement2("text");
5656
- setAttrs2(text, { x: element.x, y: element.y + textAscent4(element.style.fontSize) });
5657
- applyTextStyle(text, element.style);
5658
- text.setAttribute("class", className);
5659
- text.setAttribute("data-chrome-key", chromeKey);
5660
- const renderedText = uppercase ? element.text.toUpperCase() : element.text;
5661
- const lines = wrapText(
5662
- renderedText,
5663
- element.style.fontSize,
5664
- element.style.fontWeight,
5665
- element.maxWidth,
5666
- measureText
5667
- );
5668
- if (lines.length === 1) {
5669
- text.textContent = renderedText;
5670
- } else {
5671
- const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
5672
- for (let i = 0; i < lines.length; i++) {
5673
- const tspan = createSVGElement2("tspan");
5674
- setAttrs2(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
5675
- tspan.textContent = lines[i];
5676
- text.appendChild(tspan);
5677
- }
5678
- }
5679
- parent.appendChild(text);
5680
- }
5681
- function renderChrome2(parent, layout) {
5682
- const g = createSVGElement2("g");
5683
- g.setAttribute("class", "oc-chrome");
5684
- const { chrome, measureText } = layout;
5685
- if (chrome.eyebrow) {
5686
- const eyebrow = chrome.eyebrow;
5687
- const dotR = 3;
5688
- const dotGap = 8;
5689
- const dotX = eyebrow.x + dotR;
5690
- const dotY = eyebrow.y + eyebrow.style.fontSize * 0.42;
5691
- const dot = createSVGElement2("circle");
5692
- dot.setAttribute("class", "oc-eyebrow-dot");
5693
- setAttrs2(dot, { cx: dotX, cy: dotY, r: dotR });
5694
- dot.setAttribute("fill", eyebrow.style.fill ?? "currentColor");
5695
- g.appendChild(dot);
5696
- const shifted = {
5697
- ...eyebrow,
5698
- x: eyebrow.x + dotR * 2 + dotGap
5699
- };
5700
- renderChromeElement(g, shifted, "oc-eyebrow", "eyebrow", measureText, true);
5701
- }
5702
- if (chrome.title) {
5703
- renderChromeElement(g, chrome.title, "oc-title", "title", measureText);
5704
- }
5705
- if (chrome.subtitle) {
5706
- renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
5707
- }
5708
- const bottomOffset = layout.chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
5709
- if (chrome.source) {
5710
- renderChromeElement(
5711
- g,
5712
- { ...chrome.source, y: bottomOffset + chrome.source.y },
5713
- "oc-source",
5714
- "source",
5715
- measureText
5716
- );
5717
- }
5718
- if (chrome.byline) {
5719
- renderChromeElement(
5720
- g,
5721
- { ...chrome.byline, y: bottomOffset + chrome.byline.y },
5722
- "oc-byline",
5723
- "byline",
5724
- measureText
5725
- );
5726
- }
5727
- if (chrome.footer) {
5728
- renderChromeElement(
5729
- g,
5730
- { ...chrome.footer, y: bottomOffset + chrome.footer.y },
5731
- "oc-footer",
5732
- "footer",
5733
- measureText
5734
- );
5735
- }
5736
- if (chrome.brand) {
5737
- const brandY = bottomOffset + chrome.brand.y;
5738
- renderChromeElement(g, { ...chrome.brand, y: brandY }, "oc-brand", "brand", measureText);
5739
- const textWidth = estimateTextWidth3(
5740
- chrome.brand.text,
5741
- chrome.brand.style.fontSize,
5742
- chrome.brand.style.fontWeight
5743
- );
5744
- const dotX = chrome.brand.x - textWidth - 12;
5745
- const dotY = brandY + chrome.brand.style.fontSize / 2;
5746
- const dot = createSVGElement2("circle");
5747
- dot.setAttribute("class", "oc-brand-dot");
5748
- setAttrs2(dot, { cx: dotX, cy: dotY, r: 3 });
5749
- g.appendChild(dot);
5750
- }
5751
- parent.appendChild(g);
5752
- }
5753
-
5754
- // src/renderers/endpoint-labels.ts
5755
- var LEADER_STROKE_WIDTH = 1;
5756
- var LEADER_OPACITY = 0.45;
5757
- function renderEndpointLabels(parent, layout) {
5758
- const ep = layout.endpointLabels;
5759
- if (!ep || ep.entries.length === 0) return;
5760
- const chartArea = layout.area;
5761
- const chartRightX = chartArea.x + chartArea.width;
5762
- const root = createSVGElement2("g");
5763
- root.setAttribute("class", "oc-endpoint-labels");
5764
- root.setAttribute("role", "list");
5765
- root.setAttribute("aria-label", "Endpoint labels");
5766
- const labelFontSize = ep.labelStyle.fontSize ?? 11;
5767
- const labelLineHeight = labelFontSize * (ep.labelStyle.lineHeight ?? 1.25);
5768
- const valueFontSize = ep.valueStyle.fontSize ?? 11;
5769
- const chipX = ep.bounds.x;
5770
- const chipWidth = ep.swatchSize;
5771
- const textX = chipX + chipWidth + ep.gap;
5772
- for (let i = 0; i < ep.entries.length; i++) {
5773
- const entry = ep.entries[i];
5774
- const entryG = createSVGElement2("g");
5775
- entryG.setAttribute("class", "oc-endpoint-label-entry");
5776
- entryG.setAttribute("role", "listitem");
5777
- entryG.setAttribute("data-endpoint-index", String(i));
5778
- entryG.setAttribute("data-endpoint-key", entry.seriesKey);
5779
- entryG.setAttribute("aria-label", `${entry.seriesKey}: ${entry.value}`);
5780
- if (entry.showLeader) {
5781
- const leader = createSVGElement2("line");
5782
- leader.setAttribute("class", "oc-endpoint-leader");
5783
- setAttrs2(leader, {
5784
- x1: chipX,
5785
- y1: entry.labelY + labelFontSize / 2,
5786
- x2: chartRightX,
5787
- y2: entry.dataY,
5788
- stroke: entry.color,
5789
- "stroke-width": LEADER_STROKE_WIDTH,
5790
- "stroke-opacity": LEADER_OPACITY
5791
- });
5792
- entryG.appendChild(leader);
5793
- }
5794
- const rowY = entry.labelY + labelFontSize / 2;
5795
- const line = createSVGElement2("line");
5796
- line.setAttribute("class", "oc-endpoint-swatch-line");
5797
- setAttrs2(line, {
5798
- x1: chipX,
5799
- y1: rowY,
5800
- x2: chipX + chipWidth,
5801
- y2: rowY,
5802
- stroke: entry.color,
5803
- "stroke-width": 2,
5804
- "stroke-linecap": "round"
5805
- });
5806
- entryG.appendChild(line);
5807
- const label = createSVGElement2("text");
5808
- label.setAttribute("class", "oc-endpoint-label");
5809
- setAttrs2(label, { x: textX, y: entry.labelY + labelFontSize });
5810
- applyTextStyle(label, ep.labelStyle);
5811
- label.style.setProperty("fill", entry.color);
5812
- if (entry.labelLines.length <= 1) {
5813
- label.textContent = entry.labelLines[0] ?? entry.seriesKey;
5814
- } else {
5815
- for (let li = 0; li < entry.labelLines.length; li++) {
5816
- const tspan = createSVGElement2("tspan");
5817
- setAttrs2(tspan, { x: textX, dy: li === 0 ? 0 : labelLineHeight });
5818
- tspan.textContent = entry.labelLines[li];
5819
- label.appendChild(tspan);
5820
- }
5821
- }
5822
- entryG.appendChild(label);
5823
- const lineCount = Math.max(entry.labelLines.length, 1);
5824
- const valueY = entry.labelY + labelFontSize + (lineCount - 1) * labelLineHeight + ep.valueGap + valueFontSize;
5825
- const value = createSVGElement2("text");
5826
- value.setAttribute("class", "oc-endpoint-value");
5827
- setAttrs2(value, { x: textX, y: valueY });
5828
- applyTextStyle(value, ep.valueStyle);
5829
- value.textContent = entry.value;
5830
- entryG.appendChild(value);
5831
- if (entry.marker) {
5832
- const marker = createSVGElement2("circle");
5833
- marker.setAttribute("class", "oc-endpoint-marker");
5834
- setAttrs2(marker, {
5835
- cx: entry.marker.x,
5836
- cy: entry.marker.y,
5837
- r: entry.marker.radius,
5838
- fill: entry.marker.fill,
5839
- stroke: entry.marker.stroke,
5840
- "stroke-width": entry.marker.strokeWidth
5841
- });
5842
- entryG.appendChild(marker);
5843
- }
5844
- root.appendChild(entryG);
5845
- }
5846
- parent.appendChild(root);
5847
- }
5848
-
5849
- // src/renderers/legend.ts
5850
- import { estimateTextWidth as estimateTextWidth4 } from "@opendata-ai/openchart-core";
5851
- function isCategorical(legend) {
5852
- return !legend.type || legend.type === "categorical";
5853
- }
5854
- function renderLegend(parent, legend) {
5855
- if (!isCategorical(legend) || legend.entries.length === 0) return;
5856
- const g = createSVGElement2("g");
5857
- g.setAttribute("class", "oc-legend");
5858
- g.setAttribute("role", "list");
5859
- g.setAttribute("aria-label", "Chart legend");
5860
- const isHorizontal = legend.position === "top" || legend.position === "bottom";
5861
- const positions = "entryPositions" in legend ? legend.entryPositions : void 0;
5862
- let offsetX = legend.bounds.x;
5863
- let offsetY = legend.bounds.y;
5864
- for (let i = 0; i < legend.entries.length; i++) {
5865
- const entry = legend.entries[i];
5866
- const pos = positions?.[i];
5867
- if (pos) {
5868
- offsetX = pos.x;
5869
- offsetY = pos.y;
5870
- } else if (isHorizontal && i > 0) {
5871
- const labelWidth = estimateTextWidth4(
5872
- entry.label,
5873
- legend.labelStyle.fontSize,
5874
- legend.labelStyle.fontWeight
5875
- );
5876
- const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
5877
- if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {
5878
- offsetX = legend.bounds.x;
5879
- offsetY += legend.swatchSize + 6;
5880
- }
5881
- }
5882
- const entryG = createSVGElement2("g");
5883
- entryG.setAttribute("class", "oc-legend-entry");
5884
- entryG.setAttribute("role", "listitem");
5885
- entryG.setAttribute("data-legend-index", String(i));
5886
- entryG.setAttribute("data-legend-label", entry.label);
5887
- if (entry.overflow) {
5888
- entryG.setAttribute("data-legend-overflow", "true");
5889
- entryG.setAttribute("aria-label", entry.label);
5890
- entryG.setAttribute("opacity", "0.5");
5891
- } else {
5892
- entryG.setAttribute(
5893
- "aria-label",
5894
- `${entry.label}: ${entry.active !== false ? "visible" : "hidden"}`
5895
- );
5896
- entryG.setAttribute("style", "cursor: pointer");
5897
- if (entry.active === false) {
5898
- entryG.setAttribute("opacity", "0.3");
5899
- }
5900
- }
5901
- const midX = offsetX + legend.swatchSize / 2;
5902
- const midY = offsetY + legend.swatchSize / 2;
5903
- if (entry.shape === "circle") {
5904
- const circle = createSVGElement2("circle");
5905
- setAttrs2(circle, {
5906
- cx: midX,
5907
- cy: midY,
5908
- r: legend.swatchSize / 2,
5909
- fill: entry.color
5910
- });
5911
- entryG.appendChild(circle);
5912
- } else if (entry.shape === "line") {
5913
- const lineWidth = legend.swatchSize;
5914
- const line = createSVGElement2("line");
5915
- line.setAttribute("class", "oc-legend-swatch-line");
5916
- setAttrs2(line, {
5917
- x1: offsetX,
5918
- y1: midY,
5919
- x2: offsetX + lineWidth,
5920
- y2: midY,
5921
- stroke: entry.color,
5922
- "stroke-width": 2,
5923
- "stroke-linecap": "round"
5924
- });
5925
- entryG.appendChild(line);
5926
- } else {
5927
- const rectSize = Math.round(legend.swatchSize * 0.6);
5928
- const rect = createSVGElement2("rect");
5929
- rect.setAttribute("class", "oc-legend-swatch-rect");
5930
- setAttrs2(rect, {
5931
- x: offsetX + (legend.swatchSize - rectSize) / 2,
5932
- y: midY - rectSize / 2,
5933
- width: rectSize,
5934
- height: rectSize,
5935
- rx: 2,
5936
- ry: 2,
5937
- fill: entry.color
5938
- });
5939
- entryG.appendChild(rect);
5940
- }
5941
- const label = createSVGElement2("text");
5942
- setAttrs2(label, {
5943
- x: offsetX + legend.swatchSize + legend.swatchGap,
5944
- y: offsetY + legend.swatchSize / 2,
5945
- "dominant-baseline": "central"
5946
- });
5947
- applyTextStyle(label, legend.labelStyle);
5948
- label.textContent = entry.label;
5949
- entryG.appendChild(label);
5950
- g.appendChild(entryG);
5951
- if (!pos) {
5952
- if (isHorizontal) {
5953
- const labelWidth = estimateTextWidth4(
5954
- entry.label,
5955
- legend.labelStyle.fontSize,
5956
- legend.labelStyle.fontWeight
5957
- );
5958
- const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
5959
- offsetX += entryWidth;
5960
- } else {
5961
- offsetY += "rowHeight" in legend && legend.rowHeight ? legend.rowHeight : legend.swatchSize + legend.entryGap;
5962
- }
5963
- }
5964
- }
5965
- parent.appendChild(g);
5966
- }
5967
-
5968
- // src/renderers/marks.ts
5969
- var currentAnimation;
5970
- var currentGradientMap = /* @__PURE__ */ new Map();
5971
- function setMarkRenderState(state) {
5972
- currentAnimation = state.animation;
5973
- currentGradientMap = state.gradientMap;
5974
- }
5975
- function resetMarkRenderState() {
5976
- currentAnimation = void 0;
5977
- currentGradientMap = /* @__PURE__ */ new Map();
5978
- }
5979
- function stampAnimationAttrs(el, mark, fallbackIndex) {
5980
- if (!currentAnimation?.enabled) return;
5981
- const idx = mark.animationIndex ?? fallbackIndex;
5982
- el.setAttribute("data-animation-index", String(idx));
5983
- el.style.setProperty("--oc-mark-index", String(idx));
5984
- }
5985
- var markRenderers = {};
5986
- function registerMarkRenderer(type, renderer) {
5987
- markRenderers[type] = renderer;
5988
- }
5989
- function renderLineMark(mark, index2) {
5990
- const g = createSVGElement2("g");
5991
- g.setAttribute("data-mark-id", `line-${mark.seriesKey ?? index2}`);
5992
- g.setAttribute("class", "oc-mark oc-mark-line");
5993
- stampAnimationAttrs(g, mark, index2);
5994
- if (mark.points.length > 1) {
5995
- const path = createSVGElement2("path");
5996
- const d = mark.path ?? mark.points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
5997
- setAttrs2(path, {
5998
- d,
5999
- fill: "none",
6000
- stroke: mark.stroke,
6001
- "stroke-width": mark.strokeWidth
6002
- });
6003
- if (mark.strokeDasharray) {
6004
- path.setAttribute("stroke-dasharray", mark.strokeDasharray);
6005
- }
6006
- if (mark.opacity != null) {
6007
- path.setAttribute("opacity", String(mark.opacity));
6008
- }
6009
- g.appendChild(path);
6010
- }
6011
- if (mark.label?.visible) {
6012
- const label = createSVGElement2("text");
6013
- label.setAttribute("class", "oc-mark-label");
6014
- if (mark.seriesKey) {
6015
- label.setAttribute("data-series", mark.seriesKey);
6016
- }
6017
- setAttrs2(label, { x: mark.label.x, y: mark.label.y });
6018
- applyTextStyle(label, mark.label.style);
6019
- label.textContent = mark.label.text;
6020
- g.appendChild(label);
6021
- if (mark.label.connector) {
6022
- const connector = createSVGElement2("line");
6023
- connector.setAttribute("class", "oc-mark-connector");
6024
- setAttrs2(connector, {
6025
- x1: mark.label.connector.from.x,
6026
- y1: mark.label.connector.from.y,
6027
- x2: mark.label.connector.to.x,
6028
- y2: mark.label.connector.to.y,
6029
- stroke: mark.label.connector.stroke,
6030
- "stroke-width": 1,
6031
- "stroke-opacity": 0.5
6032
- });
6033
- g.appendChild(connector);
6034
- }
6035
- }
6036
- return g;
6037
- }
6038
- function renderAreaMark(mark, index2) {
6039
- const g = createSVGElement2("g");
6040
- g.setAttribute("data-mark-id", `area-${mark.seriesKey ?? index2}`);
6041
- g.setAttribute("class", "oc-mark oc-mark-area");
6042
- stampAnimationAttrs(g, mark, index2);
6043
- if (mark.path) {
6044
- const fill = createSVGElement2("path");
6045
- setAttrs2(fill, {
6046
- d: mark.path,
6047
- fill: resolveMarkFill(mark.fill, currentGradientMap),
6048
- "fill-opacity": mark.fillOpacity,
6049
- stroke: "none"
6050
- });
6051
- g.appendChild(fill);
6052
- if (mark.stroke && mark.topPath) {
6053
- const strokePath = createSVGElement2("path");
6054
- strokePath.setAttribute("class", "oc-area-top");
6055
- setAttrs2(strokePath, {
6056
- d: mark.topPath,
6057
- fill: "none",
6058
- stroke: mark.stroke,
6059
- "stroke-width": mark.strokeWidth ?? 1
6060
- });
6061
- g.appendChild(strokePath);
6062
- }
6063
- }
6064
- return g;
6065
- }
6066
- function _rectPathWithCorners(mark, sides) {
6067
- const { x: x3, y: y3, width: w, height: h } = mark;
6068
- const r = Math.max(0, Math.min(mark.cornerRadius ?? 0, w / 2, h / 2));
6069
- const tl = sides.tl ? r : 0;
6070
- const tr = sides.tr ? r : 0;
6071
- const br = sides.br ? r : 0;
6072
- const bl = sides.bl ? r : 0;
6073
- return [
6074
- `M${x3 + tl},${y3}`,
6075
- `H${x3 + w - tr}`,
6076
- tr ? `A${tr},${tr} 0 0 1 ${x3 + w},${y3 + tr}` : "",
6077
- `V${y3 + h - br}`,
6078
- br ? `A${br},${br} 0 0 1 ${x3 + w - br},${y3 + h}` : "",
6079
- `H${x3 + bl}`,
6080
- bl ? `A${bl},${bl} 0 0 1 ${x3},${y3 + h - bl}` : "",
6081
- `V${y3 + tl}`,
6082
- tl ? `A${tl},${tl} 0 0 1 ${x3 + tl},${y3}` : "",
6083
- "Z"
6084
- ].filter(Boolean).join(" ");
6085
- }
6086
- function renderRectMark(mark, index2) {
6087
- const g = createSVGElement2("g");
6088
- g.setAttribute("data-mark-id", `rect-${index2}`);
6089
- g.setAttribute("class", "oc-mark oc-mark-rect");
6090
- stampAnimationAttrs(g, mark, index2);
6091
- if (currentAnimation?.enabled && mark.orient === "horizontal") {
6092
- g.setAttribute("data-orient", "horizontal");
6093
- }
6094
- const sides = mark.cornerRadiusSides;
6095
- const partialCorners = !!sides && (!sides.tl || !sides.tr || !sides.br || !sides.bl) && !!mark.cornerRadius;
6096
- const shapeEl = partialCorners ? createSVGElement2("path") : createSVGElement2("rect");
6097
- if (partialCorners) {
6098
- shapeEl.setAttribute("d", _rectPathWithCorners(mark, sides));
6099
- } else {
6100
- setAttrs2(shapeEl, {
6101
- x: mark.x,
6102
- y: mark.y,
6103
- width: mark.width,
6104
- height: mark.height
6105
- });
6106
- if (mark.cornerRadius) {
6107
- setAttrs2(shapeEl, { rx: mark.cornerRadius, ry: mark.cornerRadius });
6108
- }
6109
- }
6110
- shapeEl.setAttribute("fill", String(resolveMarkFill(mark.fill, currentGradientMap)));
6111
- if (mark.stroke) {
6112
- shapeEl.setAttribute("stroke", mark.stroke);
6113
- }
6114
- if (mark.strokeWidth) {
6115
- shapeEl.setAttribute("stroke-width", String(mark.strokeWidth));
6116
- }
6117
- g.appendChild(shapeEl);
6118
- return g;
6119
- }
6120
- function renderArcMark(mark, index2) {
6121
- const g = createSVGElement2("g");
6122
- g.setAttribute("data-mark-id", `arc-${index2}`);
6123
- g.setAttribute("class", "oc-mark oc-mark-arc");
6124
- g.setAttribute("transform", `translate(${mark.center.x},${mark.center.y})`);
6125
- stampAnimationAttrs(g, mark, index2);
6126
- const path = createSVGElement2("path");
6127
- setAttrs2(path, {
6128
- d: mark.path,
6129
- fill: resolveMarkFill(mark.fill, currentGradientMap),
6130
- stroke: mark.stroke,
6131
- "stroke-width": mark.strokeWidth
6132
- });
6133
- g.appendChild(path);
6134
- if (mark.label?.visible) {
6135
- const label = createSVGElement2("text");
6136
- label.setAttribute("class", "oc-mark-label");
6137
- setAttrs2(label, {
6138
- x: mark.label.x - mark.center.x,
6139
- y: mark.label.y - mark.center.y
6140
- });
6141
- applyTextStyle(label, mark.label.style);
6142
- label.textContent = mark.label.text;
6143
- g.appendChild(label);
6144
- }
6145
- return g;
6146
- }
6147
- function renderPointMark(mark, index2) {
6148
- const circle = createSVGElement2("circle");
6149
- circle.setAttribute("data-mark-id", `point-${index2}`);
6150
- circle.setAttribute("class", "oc-mark oc-mark-point");
6151
- stampAnimationAttrs(circle, mark, index2);
6152
- setAttrs2(circle, {
6153
- cx: mark.cx,
6154
- cy: mark.cy,
6155
- r: mark.r,
6156
- fill: resolveMarkFill(mark.fill, currentGradientMap),
6157
- stroke: mark.stroke,
6158
- "stroke-width": mark.strokeWidth
6159
- });
6160
- if (mark.fillOpacity !== void 0) {
6161
- circle.setAttribute("fill-opacity", String(mark.fillOpacity));
6162
- }
6163
- return circle;
6164
- }
6165
- function renderTextMark(mark, index2) {
6166
- const text = createSVGElement2("text");
6167
- text.setAttribute("data-mark-id", `textMark-${index2}`);
6168
- text.setAttribute("class", "oc-mark oc-mark-text");
6169
- stampAnimationAttrs(text, mark, index2);
6170
- setAttrs2(text, {
6171
- x: mark.x,
6172
- y: mark.y,
6173
- "font-size": mark.fontSize,
6174
- "text-anchor": mark.textAnchor
6175
- });
6176
- text.style.setProperty("fill", mark.fill);
6177
- if (mark.fontWeight) {
6178
- text.setAttribute("font-weight", String(mark.fontWeight));
6179
- }
6180
- if (mark.fontFamily) {
6181
- text.setAttribute("font-family", mark.fontFamily);
6182
- }
6183
- if (mark.angle) {
6184
- text.setAttribute("transform", `rotate(${mark.angle}, ${mark.x}, ${mark.y})`);
6185
- }
6186
- text.textContent = mark.text;
6187
- return text;
6188
- }
6189
- function renderRuleMark(mark, index2) {
6190
- const line = createSVGElement2("line");
6191
- line.setAttribute("data-mark-id", `rule-${index2}`);
6192
- line.setAttribute("class", "oc-mark oc-mark-rule");
6193
- stampAnimationAttrs(line, mark, index2);
6194
- setAttrs2(line, {
6195
- x1: mark.x1,
6196
- y1: mark.y1,
6197
- x2: mark.x2,
6198
- y2: mark.y2,
6199
- stroke: mark.stroke,
6200
- "stroke-width": mark.strokeWidth
6201
- });
6202
- if (mark.strokeDasharray) {
6203
- line.setAttribute("stroke-dasharray", mark.strokeDasharray);
6204
- }
6205
- if (mark.opacity != null) {
6206
- line.setAttribute("opacity", String(mark.opacity));
6207
- }
6208
- return line;
6209
- }
6210
- function renderTickMark(mark, index2) {
6211
- const line = createSVGElement2("line");
6212
- line.setAttribute("data-mark-id", `tick-${index2}`);
6213
- line.setAttribute("class", "oc-mark oc-mark-tick");
6214
- stampAnimationAttrs(line, mark, index2);
6215
- const half = mark.length / 2;
6216
- if (mark.orient === "vertical") {
6217
- setAttrs2(line, {
6218
- x1: mark.x,
6219
- y1: mark.y - half,
6220
- x2: mark.x,
6221
- y2: mark.y + half,
6222
- stroke: mark.stroke,
6223
- "stroke-width": mark.strokeWidth
6224
- });
6225
- } else {
6226
- setAttrs2(line, {
6227
- x1: mark.x - half,
6228
- y1: mark.y,
6229
- x2: mark.x + half,
6230
- y2: mark.y,
6231
- stroke: mark.stroke,
6232
- "stroke-width": mark.strokeWidth
6233
- });
6234
- }
6235
- if (mark.opacity != null) {
6236
- line.setAttribute("opacity", String(mark.opacity));
6237
- }
6238
- return line;
6239
- }
6240
- registerMarkRenderer("line", renderLineMark);
6241
- registerMarkRenderer("area", renderAreaMark);
6242
- registerMarkRenderer("rect", renderRectMark);
6243
- registerMarkRenderer("arc", renderArcMark);
6244
- registerMarkRenderer("point", renderPointMark);
6245
- registerMarkRenderer("textMark", renderTextMark);
6246
- registerMarkRenderer("rule", renderRuleMark);
6247
- registerMarkRenderer("tick", renderTickMark);
6248
- function getMarkSeries(mark) {
6249
- if (mark.type === "line" || mark.type === "area") {
6250
- return mark.seriesKey;
6251
- }
6252
- if (mark.type === "arc") {
6253
- return mark.aria.label?.split(":")[0]?.trim();
6254
- }
6255
- if (mark.aria?.label) {
6256
- const beforeColon = mark.aria.label.split(":")[0]?.trim();
6257
- if (beforeColon) return beforeColon;
6258
- }
6259
- return void 0;
6260
- }
6261
- function renderMarks(parent, layout) {
6262
- const g = createSVGElement2("g");
6263
- g.setAttribute("class", "oc-marks");
6264
- for (let i = 0; i < layout.marks.length; i++) {
6265
- const mark = layout.marks[i];
6266
- const renderer = markRenderers[mark.type];
6267
- if (!renderer) continue;
6268
- const el = renderer(mark, i);
6269
- if (mark.aria?.decorative) {
6270
- el.setAttribute("aria-hidden", "true");
6271
- } else if (mark.aria?.label) {
6272
- el.setAttribute("aria-label", mark.aria.label);
6273
- }
6274
- const series = getMarkSeries(mark);
6275
- if (series) {
6276
- el.setAttribute("data-series", series);
6277
- }
6278
- if (currentAnimation?.enabled && mark.type === "rect") {
6279
- const rect = mark;
6280
- if (rect.stackGroup && rect.stackPos !== void 0) {
6281
- el.setAttribute("data-stack-pos", String(rect.stackPos));
6282
- el.style.setProperty(
6283
- "--oc-stack-pos",
6284
- String(rect.stackPos)
6285
- );
6286
- }
6287
- }
6288
- g.appendChild(el);
6289
- }
6290
- parent.appendChild(g);
6291
- let labelsGroup;
6292
- for (let i = 0; i < layout.marks.length; i++) {
6293
- const mark = layout.marks[i];
6294
- if (mark.type !== "rect") continue;
6295
- const rect = mark;
6296
- if (!rect.label?.visible) continue;
6297
- if (!labelsGroup) {
6298
- labelsGroup = createSVGElement2("g");
6299
- labelsGroup.setAttribute("class", "oc-mark-labels");
6300
- }
6301
- const label = createSVGElement2("text");
6302
- label.setAttribute("class", "oc-mark-label");
6303
- setAttrs2(label, { x: rect.label.x, y: rect.label.y });
6304
- applyTextStyle(label, rect.label.style);
6305
- label.textContent = rect.label.text;
6306
- if (currentAnimation?.enabled) {
6307
- const idx = rect.animationIndex ?? i;
6308
- label.setAttribute("data-animation-index", String(idx));
6309
- label.style.setProperty(
6310
- "--oc-mark-index",
6311
- String(idx)
6312
- );
6313
- }
6314
- labelsGroup.appendChild(label);
6315
- }
6316
- return labelsGroup;
6317
- }
6318
-
6319
- // src/renderers/metrics.ts
6320
- var DELTA_SIZE_RATIO = 12 / 22;
6321
- function renderMetrics(parent, layout) {
6322
- const bar = layout.metrics;
6323
- if (!bar || bar.cells.length === 0) return;
6324
- const labelSize = layout.theme.fonts.sizes.metricLabel;
6325
- const valueSize = layout.theme.fonts.sizes.metricValue;
6326
- const deltaSize = Math.round(valueSize * DELTA_SIZE_RATIO);
6327
- const g = createSVGElement2("g");
6328
- g.setAttribute("class", "oc-metrics");
6329
- for (const cell of bar.cells) {
6330
- const label = createSVGElement2("text");
6331
- label.setAttribute("class", "oc-metric-label");
6332
- setAttrs2(label, { x: cell.x, y: cell.labelY, "font-size": labelSize });
6333
- label.textContent = cell.metric.label.toUpperCase();
6334
- g.appendChild(label);
6335
- const value = createSVGElement2("text");
6336
- value.setAttribute("class", "oc-metric-value");
6337
- setAttrs2(value, { x: cell.x, y: cell.valueY, "font-size": valueSize });
6338
- value.textContent = cell.metric.value;
6339
- if (cell.metric.delta) {
6340
- const delta = createSVGElement2("tspan");
6341
- const tone = cell.metric.deltaTone ?? "up";
6342
- delta.setAttribute("class", tone === "down" ? "oc-metric-delta-down" : "oc-metric-delta-up");
6343
- delta.setAttribute("dx", "8");
6344
- delta.setAttribute("font-size", String(deltaSize));
6345
- delta.textContent = cell.metric.delta;
6346
- value.appendChild(delta);
6347
- }
6348
- if (cell.metric.secondary) {
6349
- const secondary = createSVGElement2("tspan");
6350
- secondary.setAttribute("class", "oc-metric-secondary");
6351
- secondary.setAttribute("dx", "6");
6352
- secondary.setAttribute("font-size", String(deltaSize));
6353
- secondary.textContent = cell.metric.secondary;
6354
- value.appendChild(secondary);
6355
- }
6356
- g.appendChild(value);
6357
- }
6358
- parent.appendChild(g);
6359
- }
6360
-
6361
- // src/svg-renderer.ts
6362
- var EASE_VAR_MAP = {
6363
- smooth: "var(--oc-ease-smooth)",
6364
- snappy: "var(--oc-ease-snappy)"
6365
- };
6366
- function renderChartSVG(layout, container, opts) {
6367
- const { width, height } = layout.dimensions;
6368
- const animation = layout.animation;
6369
- const svg = createSVGElement2("svg");
6370
- setAttrs2(svg, {
6371
- viewBox: `0 0 ${width} ${height}`,
6372
- xmlns: SVG_NS3,
6373
- // The SVG spec default is overflow:"hidden", which clips anything a hair
6374
- // outside the viewBox. We now position all text on the alphabetic/central
6375
- // baseline (dominant-baseline:hanging was dropped because WebKit computed
6376
- // it from different metrics), but WebKit/iOS still reports getBBox extents
6377
- // with a few pixels of slack around tspans, so text touching an edge can
6378
- // still get clipped. overflow:visible avoids that. Chart marks are already
6379
- // constrained by a clipPath, so nothing bleeds out.
6380
- overflow: "visible",
6381
- // Hint browsers to enable sub-pixel font hinting and kerning for chart text.
6382
- "text-rendering": "optimizeLegibility"
6383
- });
6384
- svg.style.height = `${height}px`;
6385
- svg.setAttribute("role", layout.a11y.role);
6386
- svg.setAttribute("aria-label", layout.a11y.altText);
6387
- if (layout.display === "sparkline") {
6388
- svg.setAttribute("data-display", "sparkline");
6389
- }
6390
- const classes = opts?.animate ? "oc-chart oc-animate" : "oc-chart";
6391
- svg.setAttribute("class", classes);
6392
- if (animation?.enabled) {
6393
- const markCount = layout.marks.length;
6394
- const stagger = clampStaggerDelay(animation.staggerDelay, markCount);
6395
- svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
6396
- svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
6397
- svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
6398
- const easeVar = EASE_VAR_MAP[animation.ease] || EASE_VAR_MAP.smooth;
6399
- svg.style.setProperty("--oc-animation-ease", easeVar);
6400
- let maxSegments = 0;
6401
- for (const m2 of layout.marks) {
6402
- if (m2.type === "rect") {
6403
- const pos = m2.stackPos;
6404
- if (pos !== void 0 && pos + 1 > maxSegments) {
6405
- maxSegments = pos + 1;
6406
- }
6407
- }
6408
- }
6409
- if (maxSegments > 0) {
6410
- const segDuration = Math.round(animation.duration / maxSegments);
6411
- svg.style.setProperty("--oc-stack-segment-duration", `${segDuration}ms`);
6412
- }
6413
- }
6414
- if (layout.display !== "sparkline") {
6415
- const bg = createSVGElement2("rect");
6416
- setAttrs2(bg, {
6417
- x: 0,
6418
- y: 0,
6419
- width,
6420
- height,
6421
- fill: layout.theme.colors.background
6422
- });
6423
- svg.appendChild(bg);
6424
- }
6425
- const clipId = nextSvgId("oc-clip");
6426
- const defs = createSVGElement2("defs");
6427
- const clipPath = createSVGElement2("clipPath");
6428
- clipPath.setAttribute("id", clipId);
6429
- const maxPointR = layout.marks.reduce(
6430
- (max, m2) => m2.type === "point" && m2.r ? Math.max(max, m2.r) : max,
6431
- 0
6432
- );
6433
- const clipPad = Math.max(maxPointR, 2);
6434
- const clipRect = createSVGElement2("rect");
6435
- setAttrs2(clipRect, {
6436
- x: 0,
6437
- y: layout.area.y - clipPad,
6438
- width,
6439
- height: layout.area.height + clipPad * 2
6440
- });
6441
- clipPath.appendChild(clipRect);
6442
- defs.appendChild(clipPath);
6443
- const gradientMap = buildGradientDefs(layout.marks, defs);
6444
- svg.appendChild(defs);
6445
- setMarkRenderState({ animation, gradientMap });
6446
- try {
6447
- renderAxes(svg, layout);
6448
- const clippedGroup = createSVGElement2("g");
6449
- clippedGroup.setAttribute("clip-path", `url(#${clipId})`);
6450
- const markLabelsOverlay = renderMarks(clippedGroup, layout);
6451
- const hasLineOrAreaWithDataPoints = layout.marks.some(
6452
- (m2) => (m2.type === "line" || m2.type === "area") && m2.dataPoints && m2.dataPoints.length > 0
6453
- );
6454
- if (hasLineOrAreaWithDataPoints) {
6455
- const pointEls = clippedGroup.querySelectorAll("circle.oc-mark-point");
6456
- for (const el of pointEls) {
6457
- el.setAttribute("pointer-events", "none");
6458
- }
6459
- const overlay = createSVGElement2("rect");
6460
- setAttrs2(overlay, {
6461
- x: layout.area.x,
6462
- y: layout.area.y,
6463
- width: layout.area.width,
6464
- height: layout.area.height,
6465
- fill: "transparent"
6466
- });
6467
- overlay.setAttribute("class", "oc-voronoi-overlay");
6468
- overlay.setAttribute("data-voronoi-overlay", "true");
6469
- clippedGroup.appendChild(overlay);
6470
- if (opts?.crosshair) {
6471
- const crosshairLine = createSVGElement2("line");
6472
- crosshairLine.setAttribute("data-crosshair", "true");
6473
- crosshairLine.setAttribute("class", "oc-crosshair");
6474
- setAttrs2(crosshairLine, {
6475
- x1: 0,
6476
- y1: layout.area.y,
6477
- x2: 0,
6478
- y2: layout.area.y + layout.area.height,
6479
- stroke: layout.theme.colors.axis,
6480
- "stroke-opacity": "0.4",
6481
- "stroke-dasharray": "3,3",
6482
- "stroke-width": "1",
6483
- "pointer-events": "none"
6484
- });
6485
- crosshairLine.style.display = "none";
6486
- clippedGroup.appendChild(crosshairLine);
6487
- }
6488
- const dotsGroup = createSVGElement2("g");
6489
- dotsGroup.setAttribute("data-snap-dots", "true");
6490
- dotsGroup.setAttribute("class", "oc-snap-dots");
6491
- dotsGroup.setAttribute("pointer-events", "none");
6492
- clippedGroup.appendChild(dotsGroup);
6493
- }
6494
- svg.appendChild(clippedGroup);
6495
- if (markLabelsOverlay) {
6496
- svg.appendChild(markLabelsOverlay);
6497
- }
6498
- renderAnnotations(svg, layout);
6499
- renderEndpointLabels(svg, layout);
6500
- const epEntries = layout.endpointLabels?.entries ?? [];
6501
- if (epEntries.length > 0) {
6502
- const pointEls = clippedGroup.querySelectorAll("circle.oc-mark-point");
6503
- for (const entry of epEntries) {
6504
- if (!entry.marker) continue;
6505
- const mx = entry.marker.dataX;
6506
- const my = entry.marker.y;
6507
- for (const el of pointEls) {
6508
- const cx = Number(el.getAttribute("cx"));
6509
- const cy = Number(el.getAttribute("cy"));
6510
- if (Math.abs(cx - mx) < 0.5 && Math.abs(cy - my) < 0.5) {
6511
- el.setAttribute("opacity", "0");
6512
- }
6513
- }
6514
- }
6515
- }
6516
- renderLegend(svg, layout.legend);
6517
- renderChrome2(svg, layout);
6518
- renderMetrics(svg, layout);
6519
- if (layout.watermark && !layout.chrome.brand) {
6520
- renderBrand(svg, layout);
6521
- }
6522
- } finally {
6523
- resetMarkRenderState();
6524
- }
6525
- container.appendChild(svg);
6526
- return svg;
6527
- }
6528
-
6529
- // src/text-edit-overlay.ts
6530
- function getScale(svg) {
6531
- const viewBox = svg.viewBox?.baseVal;
6532
- const svgRect = svg.getBoundingClientRect();
6533
- return {
6534
- scaleX: viewBox?.width && svgRect.width ? svgRect.width / viewBox.width : 1,
6535
- scaleY: viewBox?.height && svgRect.height ? svgRect.height / viewBox.height : 1
6536
- };
6537
- }
6538
- function getTextStyles(targetElement, scale) {
6539
- const computed = window.getComputedStyle(targetElement);
6540
- const svgFontSize = parseFloat(
6541
- targetElement.getAttribute("font-size") ?? computed.fontSize ?? "14"
6542
- );
6543
- const pixelFontSize = svgFontSize * scale.scaleY;
6544
- const fontFamily = targetElement.getAttribute("font-family") ?? computed.fontFamily ?? "inherit";
6545
- const fontWeight = targetElement.getAttribute("font-weight") ?? computed.fontWeight ?? "400";
6546
- const fill = targetElement.style.getPropertyValue("fill") || targetElement.getAttribute("fill") || computed.color || "#000";
6547
- const textAnchor = targetElement.getAttribute("text-anchor") ?? "start";
6548
- let textAlign = "left";
6549
- if (textAnchor === "middle") textAlign = "center";
6550
- else if (textAnchor === "end") textAlign = "right";
6551
- return {
6552
- fontFamily,
6553
- fontSize: `${pixelFontSize}px`,
6554
- fontWeight,
6555
- color: fill,
6556
- textAlign,
6557
- lineHeight: "1.3"
6558
- };
6559
- }
6560
- function computePosition2(targetElement, svg, container) {
6561
- const bbox = targetElement.getBBox();
6562
- const scale = getScale(svg);
6563
- const svgRect = svg.getBoundingClientRect();
6564
- const containerRect = container.getBoundingClientRect();
6565
- const padding = 4;
6566
- const left = bbox.x * scale.scaleX + (svgRect.left - containerRect.left) - padding;
6567
- const top = bbox.y * scale.scaleY + (svgRect.top - containerRect.top) - padding;
6568
- const width = bbox.width * scale.scaleX + padding * 2;
6569
- const height = bbox.height * scale.scaleY + padding * 2;
6570
- return {
6571
- top: Math.max(0, top),
6572
- left: Math.max(0, left),
6573
- width: Math.max(width, 60),
6574
- height: Math.max(height, 24)
6575
- };
6576
- }
6577
- function createTextEditOverlay(config) {
6578
- const { container, svg, targetElement, currentText, onCommit, onCancel } = config;
6579
- let destroyed = false;
6580
- const originalOpacity = targetElement.getAttribute("opacity");
6581
- targetElement.setAttribute("opacity", "0");
6582
- const scale = getScale(svg);
6583
- const styles = getTextStyles(targetElement, scale);
6584
- const position = computePosition2(targetElement, svg, container);
6585
- const textarea = document.createElement("textarea");
6586
- textarea.value = currentText;
6587
- const containerPosition = window.getComputedStyle(container).position;
6588
- const containerWasStatic = containerPosition === "static";
6589
- if (containerWasStatic) {
6590
- container.style.position = "relative";
6591
- }
6592
- Object.assign(textarea.style, {
6593
- position: "absolute",
6594
- top: `${position.top}px`,
6595
- left: `${position.left}px`,
6596
- width: `${position.width}px`,
6597
- minHeight: `${position.height}px`,
6598
- fontFamily: styles.fontFamily,
6599
- fontSize: styles.fontSize,
6600
- fontWeight: styles.fontWeight,
6601
- color: styles.color,
6602
- textAlign: styles.textAlign,
6603
- lineHeight: styles.lineHeight,
6604
- padding: "2px 4px",
6605
- margin: "0",
6606
- border: "1px solid rgba(79, 70, 229, 0.4)",
6607
- borderRadius: "3px",
6608
- background: "rgba(255, 255, 255, 0.95)",
6609
- outline: "none",
6610
- resize: "none",
6611
- overflow: "hidden",
6612
- boxSizing: "border-box",
6613
- zIndex: "10000",
6614
- // Remove default textarea appearance
6615
- WebkitAppearance: "none",
6616
- appearance: "none"
6617
- });
6618
- container.appendChild(textarea);
6619
- textarea.focus();
6620
- textarea.select();
6621
- function destroy() {
6622
- if (destroyed) return;
6623
- destroyed = true;
6624
- if (originalOpacity !== null) {
6625
- targetElement.setAttribute("opacity", originalOpacity);
6626
- } else {
6627
- targetElement.removeAttribute("opacity");
6628
- }
6629
- textarea.removeEventListener("keydown", handleKeyDown);
6630
- document.removeEventListener("mousedown", handleClickOutside);
6631
- resizeObserver.disconnect();
6632
- if (containerWasStatic) {
6633
- container.style.position = "";
6634
- }
6635
- if (textarea.parentNode) {
6636
- textarea.parentNode.removeChild(textarea);
6637
- }
6638
- }
6639
- function commit() {
6640
- if (destroyed) return;
6641
- const newText = textarea.value;
6642
- destroy();
6643
- onCommit(newText);
6644
- }
6645
- function cancel() {
6646
- if (destroyed) return;
6647
- destroy();
6648
- onCancel();
6649
- }
6650
- const handleKeyDown = (e) => {
6651
- if (e.key === "Enter" && !e.shiftKey) {
6652
- e.preventDefault();
6653
- commit();
6654
- } else if (e.key === "Escape") {
6655
- e.preventDefault();
6656
- cancel();
6657
- }
6658
- };
6659
- textarea.addEventListener("keydown", handleKeyDown);
6660
- const handleClickOutside = (e) => {
6661
- if (!textarea.contains(e.target)) {
6662
- commit();
6663
- }
6664
- };
6665
- requestAnimationFrame(() => {
6666
- if (!destroyed) {
6667
- document.addEventListener("mousedown", handleClickOutside);
6668
- }
6669
- });
6670
- const resizeObserver = new ResizeObserver(() => {
6671
- if (destroyed) return;
6672
- const newPosition = computePosition2(targetElement, svg, container);
6673
- textarea.style.top = `${newPosition.top}px`;
6674
- textarea.style.left = `${newPosition.left}px`;
6675
- textarea.style.width = `${newPosition.width}px`;
6676
- textarea.style.minHeight = `${newPosition.height}px`;
6677
- });
6678
- resizeObserver.observe(container);
6679
- return { destroy };
6680
- }
6681
-
6682
5180
  // src/mount.ts
6683
5181
  function resolveDarkMode3(mode) {
6684
5182
  if (mode === "force") return true;
@@ -7179,6 +5677,7 @@ function createChart(container, spec, options) {
7179
5677
  } else {
7180
5678
  container.classList.remove("oc-dark");
7181
5679
  }
5680
+ stampThemeProperties(container, currentLayout.theme);
7182
5681
  if (shouldAnimate && svgElement) {
7183
5682
  cleanupAnimations = setupAnimationCleanup(svgElement, () => {
7184
5683
  cleanupAnimations = null;
@@ -7221,6 +5720,27 @@ function createChart(container, spec, options) {
7221
5720
  }
7222
5721
  render();
7223
5722
  }
5723
+ function setHighlight(values) {
5724
+ if (destroyed) return;
5725
+ if (isLayerSpec(currentSpec) || isGraphSpec(currentSpec))
5726
+ return;
5727
+ const spec2 = currentSpec;
5728
+ const colorEnc = spec2.encoding?.color;
5729
+ if (!colorEnc || typeof colorEnc !== "object" || !("field" in colorEnc)) return;
5730
+ const current = colorEnc.highlight;
5731
+ if (!values?.length && !current) return;
5732
+ const updatedColor = { ...colorEnc };
5733
+ if (values && values.length > 0) {
5734
+ updatedColor.highlight = values;
5735
+ } else {
5736
+ delete updatedColor.highlight;
5737
+ }
5738
+ currentSpec = {
5739
+ ...spec2,
5740
+ encoding: { ...spec2.encoding, color: updatedColor }
5741
+ };
5742
+ render();
5743
+ }
7224
5744
  function doExport(format, exportOptions) {
7225
5745
  if (!svgElement) {
7226
5746
  throw new Error("Chart is not rendered yet");
@@ -7331,7 +5851,8 @@ function createChart(container, spec, options) {
7331
5851
  },
7332
5852
  get isEditing() {
7333
5853
  return isTextEditingActive;
7334
- }
5854
+ },
5855
+ setHighlight
7335
5856
  };
7336
5857
  }
7337
5858
 
@@ -7660,30 +6181,30 @@ import { compileSankey } from "@opendata-ai/openchart-engine";
7660
6181
 
7661
6182
  // src/sankey-renderer.ts
7662
6183
  import {
7663
- BRAND_FONT_SIZE as BRAND_FONT_SIZE3,
7664
- BRAND_MIN_WIDTH as BRAND_MIN_WIDTH3,
7665
- estimateTextWidth as estimateTextWidth5,
7666
- textAscent as textAscent5,
7667
- wrapText as wrapText2
6184
+ BRAND_FONT_SIZE as BRAND_FONT_SIZE2,
6185
+ BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2,
6186
+ estimateTextWidth as estimateTextWidth2,
6187
+ textAscent as textAscent2,
6188
+ wrapText
7668
6189
  } from "@opendata-ai/openchart-core";
7669
- import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
7670
- var SVG_NS4 = "http://www.w3.org/2000/svg";
7671
- var XLINK_NS3 = "http://www.w3.org/1999/xlink";
7672
- var BRAND_URL3 = "https://tryopendata.ai";
7673
- var EASE_VAR_MAP2 = {
6190
+ import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
6191
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
6192
+ var XLINK_NS2 = "http://www.w3.org/1999/xlink";
6193
+ var BRAND_URL2 = "https://tryopendata.ai";
6194
+ var EASE_VAR_MAP = {
7674
6195
  smooth: "var(--oc-ease-smooth)",
7675
6196
  snappy: "var(--oc-ease-snappy)"
7676
6197
  };
7677
- function createSVGElement3(tag) {
7678
- return document.createElementNS(SVG_NS4, tag);
6198
+ function createSVGElement2(tag) {
6199
+ return document.createElementNS(SVG_NS2, tag);
7679
6200
  }
7680
- function setAttrs3(el, attrs) {
6201
+ function setAttrs2(el, attrs) {
7681
6202
  for (const [key, value] of Object.entries(attrs)) {
7682
6203
  el.setAttribute(key, String(value));
7683
6204
  }
7684
6205
  }
7685
- function applyTextStyle2(el, style) {
7686
- setAttrs3(el, {
6206
+ function applyTextStyle(el, style) {
6207
+ setAttrs2(el, {
7687
6208
  "font-family": style.fontFamily,
7688
6209
  "font-size": style.fontSize,
7689
6210
  "font-weight": style.fontWeight
@@ -7699,19 +6220,19 @@ function applyTextStyle2(el, style) {
7699
6220
  el.setAttribute("font-variant", style.fontVariant);
7700
6221
  }
7701
6222
  }
7702
- function stampAnimationAttrs2(el, mark, fallbackIndex, animation) {
6223
+ function stampAnimationAttrs(el, mark, fallbackIndex, animation) {
7703
6224
  if (!animation?.enabled) return;
7704
6225
  const idx = mark.animationIndex ?? fallbackIndex;
7705
6226
  el.setAttribute("data-animation-index", String(idx));
7706
6227
  el.style.setProperty("--oc-mark-index", String(idx));
7707
6228
  }
7708
- function renderChromeElement2(parent, element, className, chromeKey, measureText) {
7709
- const text = createSVGElement3("text");
7710
- setAttrs3(text, { x: element.x, y: element.y + textAscent5(element.style.fontSize) });
7711
- applyTextStyle2(text, element.style);
6229
+ function renderChromeElement(parent, element, className, chromeKey, measureText) {
6230
+ const text = createSVGElement2("text");
6231
+ setAttrs2(text, { x: element.x, y: element.y + textAscent2(element.style.fontSize) });
6232
+ applyTextStyle(text, element.style);
7712
6233
  text.setAttribute("class", className);
7713
6234
  text.setAttribute("data-chrome-key", chromeKey);
7714
- const lines = wrapText2(
6235
+ const lines = wrapText(
7715
6236
  element.text,
7716
6237
  element.style.fontSize,
7717
6238
  element.style.fontWeight,
@@ -7723,27 +6244,27 @@ function renderChromeElement2(parent, element, className, chromeKey, measureText
7723
6244
  } else {
7724
6245
  const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
7725
6246
  for (let i = 0; i < lines.length; i++) {
7726
- const tspan = createSVGElement3("tspan");
7727
- setAttrs3(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
6247
+ const tspan = createSVGElement2("tspan");
6248
+ setAttrs2(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
7728
6249
  tspan.textContent = lines[i];
7729
6250
  text.appendChild(tspan);
7730
6251
  }
7731
6252
  }
7732
6253
  parent.appendChild(text);
7733
6254
  }
7734
- function renderChrome3(parent, layout) {
7735
- const g = createSVGElement3("g");
6255
+ function renderChrome2(parent, layout) {
6256
+ const g = createSVGElement2("g");
7736
6257
  g.setAttribute("class", "oc-chrome");
7737
6258
  const { chrome, measureText } = layout;
7738
6259
  if (chrome.title) {
7739
- renderChromeElement2(g, chrome.title, "oc-title", "title", measureText);
6260
+ renderChromeElement(g, chrome.title, "oc-title", "title", measureText);
7740
6261
  }
7741
6262
  if (chrome.subtitle) {
7742
- renderChromeElement2(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
6263
+ renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
7743
6264
  }
7744
6265
  const bottomOffset = layout.area.y + layout.area.height;
7745
6266
  if (chrome.source) {
7746
- renderChromeElement2(
6267
+ renderChromeElement(
7747
6268
  g,
7748
6269
  { ...chrome.source, y: bottomOffset + chrome.source.y },
7749
6270
  "oc-source",
@@ -7752,7 +6273,7 @@ function renderChrome3(parent, layout) {
7752
6273
  );
7753
6274
  }
7754
6275
  if (chrome.byline) {
7755
- renderChromeElement2(
6276
+ renderChromeElement(
7756
6277
  g,
7757
6278
  { ...chrome.byline, y: bottomOffset + chrome.byline.y },
7758
6279
  "oc-byline",
@@ -7761,7 +6282,7 @@ function renderChrome3(parent, layout) {
7761
6282
  );
7762
6283
  }
7763
6284
  if (chrome.footer) {
7764
- renderChromeElement2(
6285
+ renderChromeElement(
7765
6286
  g,
7766
6287
  { ...chrome.footer, y: bottomOffset + chrome.footer.y },
7767
6288
  "oc-footer",
@@ -7771,8 +6292,8 @@ function renderChrome3(parent, layout) {
7771
6292
  }
7772
6293
  parent.appendChild(g);
7773
6294
  }
7774
- function renderBrand2(parent, layout) {
7775
- if (layout.dimensions.width < BRAND_MIN_WIDTH3) return;
6295
+ function renderBrand(parent, layout) {
6296
+ if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
7776
6297
  const { width } = layout.dimensions;
7777
6298
  const padding = layout.theme.spacing.padding;
7778
6299
  const rightEdge = width - padding;
@@ -7781,32 +6302,32 @@ function renderBrand2(parent, layout) {
7781
6302
  const { chrome } = layout;
7782
6303
  const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
7783
6304
  const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
7784
- const a2 = createSVGElement3("a");
7785
- a2.setAttribute("href", BRAND_URL3);
7786
- a2.setAttributeNS(XLINK_NS3, "xlink:href", BRAND_URL3);
6305
+ const a2 = createSVGElement2("a");
6306
+ a2.setAttribute("href", BRAND_URL2);
6307
+ a2.setAttributeNS(XLINK_NS2, "xlink:href", BRAND_URL2);
7787
6308
  a2.setAttribute("target", "_blank");
7788
6309
  a2.setAttribute("rel", "noopener");
7789
6310
  a2.setAttribute("class", "oc-chrome-ref");
7790
6311
  const BRAND_LARGE = 16;
7791
- const text = createSVGElement3("text");
7792
- setAttrs3(text, {
6312
+ const text = createSVGElement2("text");
6313
+ setAttrs2(text, {
7793
6314
  x: rightEdge,
7794
6315
  y: chromeY + BRAND_LARGE,
7795
6316
  "dominant-baseline": "alphabetic",
7796
6317
  "text-anchor": "end",
7797
6318
  "font-family": layout.theme.fonts.family,
7798
- "font-size": BRAND_FONT_SIZE3,
6319
+ "font-size": BRAND_FONT_SIZE2,
7799
6320
  "fill-opacity": 0.55
7800
6321
  });
7801
6322
  text.style.setProperty("fill", fill);
7802
- const trySpan = createSVGElement3("tspan");
7803
- setAttrs3(trySpan, { "font-weight": 500 });
6323
+ const trySpan = createSVGElement2("tspan");
6324
+ setAttrs2(trySpan, { "font-weight": 500 });
7804
6325
  trySpan.textContent = "try";
7805
- const openDataSpan = createSVGElement3("tspan");
7806
- setAttrs3(openDataSpan, { "font-weight": 600, "font-size": BRAND_LARGE });
6326
+ const openDataSpan = createSVGElement2("tspan");
6327
+ setAttrs2(openDataSpan, { "font-weight": 600, "font-size": BRAND_LARGE });
7807
6328
  openDataSpan.textContent = "OpenData";
7808
- const aiSpan = createSVGElement3("tspan");
7809
- setAttrs3(aiSpan, { "font-weight": 500 });
6329
+ const aiSpan = createSVGElement2("tspan");
6330
+ setAttrs2(aiSpan, { "font-weight": 500 });
7810
6331
  aiSpan.textContent = ".ai";
7811
6332
  text.appendChild(trySpan);
7812
6333
  text.appendChild(openDataSpan);
@@ -7817,7 +6338,7 @@ function renderBrand2(parent, layout) {
7817
6338
  function renderLegend2(parent, legendLayout) {
7818
6339
  if (!("entries" in legendLayout) || legendLayout.entries.length === 0) return;
7819
6340
  const legend = legendLayout;
7820
- const g = createSVGElement3("g");
6341
+ const g = createSVGElement2("g");
7821
6342
  g.setAttribute("class", "oc-legend");
7822
6343
  g.setAttribute("role", "list");
7823
6344
  g.setAttribute("aria-label", "Chart legend");
@@ -7827,7 +6348,7 @@ function renderLegend2(parent, legendLayout) {
7827
6348
  for (let i = 0; i < legend.entries.length; i++) {
7828
6349
  const entry = legend.entries[i];
7829
6350
  if (isHorizontal && i > 0) {
7830
- const labelWidth = estimateTextWidth5(
6351
+ const labelWidth = estimateTextWidth2(
7831
6352
  entry.label,
7832
6353
  legend.labelStyle.fontSize,
7833
6354
  legend.labelStyle.fontWeight
@@ -7838,7 +6359,7 @@ function renderLegend2(parent, legendLayout) {
7838
6359
  offsetY += legend.swatchSize + 6;
7839
6360
  }
7840
6361
  }
7841
- const entryG = createSVGElement3("g");
6362
+ const entryG = createSVGElement2("g");
7842
6363
  entryG.setAttribute("class", "oc-legend-entry");
7843
6364
  entryG.setAttribute("role", "listitem");
7844
6365
  entryG.setAttribute("data-legend-index", String(i));
@@ -7857,8 +6378,8 @@ function renderLegend2(parent, legendLayout) {
7857
6378
  entryG.setAttribute("opacity", "0.3");
7858
6379
  }
7859
6380
  }
7860
- const rect = createSVGElement3("rect");
7861
- setAttrs3(rect, {
6381
+ const rect = createSVGElement2("rect");
6382
+ setAttrs2(rect, {
7862
6383
  x: offsetX,
7863
6384
  y: offsetY,
7864
6385
  width: legend.swatchSize,
@@ -7867,18 +6388,18 @@ function renderLegend2(parent, legendLayout) {
7867
6388
  rx: 2
7868
6389
  });
7869
6390
  entryG.appendChild(rect);
7870
- const label = createSVGElement3("text");
7871
- setAttrs3(label, {
6391
+ const label = createSVGElement2("text");
6392
+ setAttrs2(label, {
7872
6393
  x: offsetX + legend.swatchSize + legend.swatchGap,
7873
6394
  y: offsetY + legend.swatchSize / 2,
7874
6395
  "dominant-baseline": "central"
7875
6396
  });
7876
- applyTextStyle2(label, legend.labelStyle);
6397
+ applyTextStyle(label, legend.labelStyle);
7877
6398
  label.textContent = entry.label;
7878
6399
  entryG.appendChild(label);
7879
6400
  g.appendChild(entryG);
7880
6401
  if (isHorizontal) {
7881
- const labelWidth = estimateTextWidth5(
6402
+ const labelWidth = estimateTextWidth2(
7882
6403
  entry.label,
7883
6404
  legend.labelStyle.fontSize,
7884
6405
  legend.labelStyle.fontWeight
@@ -7906,7 +6427,7 @@ function renderGradientDefs(defs, links, nodePositions) {
7906
6427
  const link = links[i];
7907
6428
  if (link.sourceColor === link.targetColor) continue;
7908
6429
  const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;
7909
- const gradient = createSVGElement3("linearGradient");
6430
+ const gradient = createSVGElement2("linearGradient");
7910
6431
  gradient.setAttribute("id", gradId);
7911
6432
  gradient.setAttribute("gradientUnits", "userSpaceOnUse");
7912
6433
  const sourcePos = nodePositions.get(link.sourceId);
@@ -7915,21 +6436,21 @@ function renderGradientDefs(defs, links, nodePositions) {
7915
6436
  const x22 = targetPos ? targetPos.x : 0;
7916
6437
  gradient.setAttribute("x1", String(x1));
7917
6438
  gradient.setAttribute("x2", String(x22));
7918
- const stop0 = createSVGElement3("stop");
7919
- setAttrs3(stop0, { offset: "0%", "stop-color": link.sourceColor });
6439
+ const stop0 = createSVGElement2("stop");
6440
+ setAttrs2(stop0, { offset: "0%", "stop-color": link.sourceColor });
7920
6441
  gradient.appendChild(stop0);
7921
- const stop1 = createSVGElement3("stop");
7922
- setAttrs3(stop1, { offset: "100%", "stop-color": link.targetColor });
6442
+ const stop1 = createSVGElement2("stop");
6443
+ setAttrs2(stop1, { offset: "100%", "stop-color": link.targetColor });
7923
6444
  gradient.appendChild(stop1);
7924
6445
  defs.appendChild(gradient);
7925
6446
  }
7926
6447
  }
7927
6448
  function renderLinks(parent, links, _nodePositions, animation) {
7928
- const g = createSVGElement3("g");
6449
+ const g = createSVGElement2("g");
7929
6450
  g.setAttribute("class", "oc-sankey-links");
7930
6451
  for (let i = 0; i < links.length; i++) {
7931
6452
  const link = links[i];
7932
- const linkG = createSVGElement3("g");
6453
+ const linkG = createSVGElement2("g");
7933
6454
  linkG.setAttribute("class", "oc-sankey-link");
7934
6455
  linkG.setAttribute("data-mark-id", `link-${link.sourceId}-${link.targetId}-${i}`);
7935
6456
  linkG.setAttribute("data-source", link.sourceId);
@@ -7937,8 +6458,8 @@ function renderLinks(parent, links, _nodePositions, animation) {
7937
6458
  if (link.aria?.label) {
7938
6459
  linkG.setAttribute("aria-label", link.aria.label);
7939
6460
  }
7940
- stampAnimationAttrs2(linkG, link, i, animation);
7941
- const path = createSVGElement3("path");
6461
+ stampAnimationAttrs(linkG, link, i, animation);
6462
+ const path = createSVGElement2("path");
7942
6463
  path.setAttribute("d", link.path);
7943
6464
  path.setAttribute("stroke", "none");
7944
6465
  path.setAttribute("fill-opacity", String(link.fillOpacity));
@@ -7954,20 +6475,20 @@ function renderLinks(parent, links, _nodePositions, animation) {
7954
6475
  parent.appendChild(g);
7955
6476
  }
7956
6477
  function renderNodes(parent, nodes, animation) {
7957
- const g = createSVGElement3("g");
6478
+ const g = createSVGElement2("g");
7958
6479
  g.setAttribute("class", "oc-sankey-nodes");
7959
6480
  for (let i = 0; i < nodes.length; i++) {
7960
6481
  const node = nodes[i];
7961
- const nodeG = createSVGElement3("g");
6482
+ const nodeG = createSVGElement2("g");
7962
6483
  nodeG.setAttribute("class", "oc-sankey-node");
7963
6484
  nodeG.setAttribute("data-mark-id", `node-${node.nodeId}`);
7964
6485
  nodeG.setAttribute("data-node-id", node.nodeId);
7965
6486
  if (node.aria?.label) {
7966
6487
  nodeG.setAttribute("aria-label", node.aria.label);
7967
6488
  }
7968
- stampAnimationAttrs2(nodeG, node, i, animation);
7969
- const rect = createSVGElement3("rect");
7970
- setAttrs3(rect, {
6489
+ stampAnimationAttrs(nodeG, node, i, animation);
6490
+ const rect = createSVGElement2("rect");
6491
+ setAttrs2(rect, {
7971
6492
  x: node.x,
7972
6493
  y: node.y,
7973
6494
  width: node.width,
@@ -7988,24 +6509,24 @@ function renderNodes(parent, nodes, animation) {
7988
6509
  parent.appendChild(g);
7989
6510
  }
7990
6511
  function renderLabels(parent, nodes, measureText) {
7991
- const g = createSVGElement3("g");
6512
+ const g = createSVGElement2("g");
7992
6513
  g.setAttribute("class", "oc-sankey-labels");
7993
6514
  for (const node of nodes) {
7994
6515
  const { label } = node;
7995
6516
  if (!label.visible) continue;
7996
- const text = createSVGElement3("text");
7997
- setAttrs3(text, { x: label.x, y: label.y });
7998
- applyTextStyle2(text, label.style);
6517
+ const text = createSVGElement2("text");
6518
+ setAttrs2(text, { x: label.x, y: label.y });
6519
+ applyTextStyle(text, label.style);
7999
6520
  if (label.maxWidth !== void 0 && label.maxWidth > 0) {
8000
6521
  const fontSize = label.style.fontSize ?? 12;
8001
6522
  const fontWeight = label.style.fontWeight ?? 400;
8002
- const lines = wrapText2(label.text, fontSize, fontWeight, label.maxWidth, measureText);
6523
+ const lines = wrapText(label.text, fontSize, fontWeight, label.maxWidth, measureText);
8003
6524
  if (lines.length > 1) {
8004
6525
  const lineHeight = fontSize * (label.style.lineHeight ?? 1.3);
8005
6526
  const totalHeight = (lines.length - 1) * lineHeight;
8006
6527
  const startY = label.y - totalHeight / 2;
8007
6528
  for (let i = 0; i < lines.length; i++) {
8008
- const tspan = createSVGElement3("tspan");
6529
+ const tspan = createSVGElement2("tspan");
8009
6530
  tspan.setAttribute("x", String(label.x));
8010
6531
  tspan.setAttribute("y", String(startY + i * lineHeight));
8011
6532
  tspan.textContent = lines[i];
@@ -8023,10 +6544,10 @@ function renderLabels(parent, nodes, measureText) {
8023
6544
  }
8024
6545
  function renderSankeySVG(layout, animation) {
8025
6546
  const { width, height } = layout.dimensions;
8026
- const svg = createSVGElement3("svg");
8027
- setAttrs3(svg, {
6547
+ const svg = createSVGElement2("svg");
6548
+ setAttrs2(svg, {
8028
6549
  viewBox: `0 0 ${width} ${height}`,
8029
- xmlns: SVG_NS4,
6550
+ xmlns: SVG_NS2,
8030
6551
  overflow: "visible"
8031
6552
  });
8032
6553
  svg.style.height = `${height}px`;
@@ -8037,16 +6558,16 @@ function renderSankeySVG(layout, animation) {
8037
6558
  svg.setAttribute("class", classes);
8038
6559
  if (animation?.enabled) {
8039
6560
  const totalMarks = layout.nodes.length + layout.links.length;
8040
- const stagger = clampStaggerDelay2(animation.staggerDelay, totalMarks);
6561
+ const stagger = clampStaggerDelay(animation.staggerDelay, totalMarks);
8041
6562
  svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
8042
6563
  svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
8043
6564
  svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
8044
- const easeVar = EASE_VAR_MAP2[animation.ease] || EASE_VAR_MAP2.smooth;
6565
+ const easeVar = EASE_VAR_MAP[animation.ease] || EASE_VAR_MAP.smooth;
8045
6566
  svg.style.setProperty("--oc-animation-ease", easeVar);
8046
6567
  }
8047
- const bg = createSVGElement3("rect");
6568
+ const bg = createSVGElement2("rect");
8048
6569
  bg.setAttribute("class", "oc-background");
8049
- setAttrs3(bg, {
6570
+ setAttrs2(bg, {
8050
6571
  x: 0,
8051
6572
  y: 0,
8052
6573
  width,
@@ -8055,16 +6576,16 @@ function renderSankeySVG(layout, animation) {
8055
6576
  });
8056
6577
  svg.appendChild(bg);
8057
6578
  const nodePositions = buildNodePositionMap(layout.nodes);
8058
- const defs = createSVGElement3("defs");
6579
+ const defs = createSVGElement2("defs");
8059
6580
  renderGradientDefs(defs, layout.links, nodePositions);
8060
6581
  svg.appendChild(defs);
8061
6582
  renderLinks(svg, layout.links, nodePositions, animation);
8062
6583
  renderNodes(svg, layout.nodes, animation);
8063
6584
  renderLabels(svg, layout.nodes, layout.measureText);
8064
6585
  renderLegend2(svg, layout.legend);
8065
- renderChrome3(svg, layout);
6586
+ renderChrome2(svg, layout);
8066
6587
  if (layout.watermark) {
8067
- renderBrand2(svg, layout);
6588
+ renderBrand(svg, layout);
8068
6589
  }
8069
6590
  return svg;
8070
6591
  }
@@ -8627,13 +7148,13 @@ import { getBreakpoint } from "@opendata-ai/openchart-core";
8627
7148
  import { compileTable } from "@opendata-ai/openchart-engine";
8628
7149
 
8629
7150
  // src/table-renderer.ts
8630
- import { BRAND_FONT_SIZE as BRAND_FONT_SIZE4 } from "@opendata-ai/openchart-core";
8631
- import { clampStaggerDelay as clampStaggerDelay3 } from "@opendata-ai/openchart-engine";
8632
- var EASE_VAR_MAP3 = {
7151
+ import { BRAND_FONT_SIZE as BRAND_FONT_SIZE3 } from "@opendata-ai/openchart-core";
7152
+ import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
7153
+ var EASE_VAR_MAP2 = {
8633
7154
  smooth: "var(--oc-ease-smooth)",
8634
7155
  snappy: "var(--oc-ease-snappy)"
8635
7156
  };
8636
- var BRAND_URL4 = "https://tryopendata.ai";
7157
+ var BRAND_URL3 = "https://tryopendata.ai";
8637
7158
  function renderChromeBlock(layout, position) {
8638
7159
  const chrome = layout.chrome;
8639
7160
  if (position === "header") {
@@ -8880,10 +7401,10 @@ function renderTable(layout, container, opts) {
8880
7401
  const brand = document.createElement("div");
8881
7402
  brand.className = "oc-table-ref";
8882
7403
  const brandLink = document.createElement("a");
8883
- brandLink.href = BRAND_URL4;
7404
+ brandLink.href = BRAND_URL3;
8884
7405
  brandLink.target = "_blank";
8885
7406
  brandLink.rel = "noopener";
8886
- brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE4}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : "sans-serif"};`;
7407
+ brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE3}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : "sans-serif"};`;
8887
7408
  brandLink.textContent = "tryOpenData.ai";
8888
7409
  brand.appendChild(brandLink);
8889
7410
  footerRow.appendChild(brand);
@@ -8894,11 +7415,11 @@ function renderTable(layout, container, opts) {
8894
7415
  if (opts?.animate && layout.animation?.enabled) {
8895
7416
  const anim = layout.animation;
8896
7417
  const rowCount = layout.rows.length;
8897
- const stagger = clampStaggerDelay3(anim.staggerDelay, rowCount);
7418
+ const stagger = clampStaggerDelay2(anim.staggerDelay, rowCount);
8898
7419
  const s = wrapper.style;
8899
7420
  s.setProperty("--oc-animation-duration", `${anim.duration}ms`);
8900
7421
  s.setProperty("--oc-animation-stagger", `${stagger}ms`);
8901
- s.setProperty("--oc-animation-ease", EASE_VAR_MAP3[anim.ease] || EASE_VAR_MAP3.smooth);
7422
+ s.setProperty("--oc-animation-ease", EASE_VAR_MAP2[anim.ease] || EASE_VAR_MAP2.smooth);
8902
7423
  wrapper.classList.add("oc-animate");
8903
7424
  }
8904
7425
  container.appendChild(wrapper);
@@ -9273,33 +7794,33 @@ function createTable(container, spec, options) {
9273
7794
  import { compileTileMap } from "@opendata-ai/openchart-engine";
9274
7795
 
9275
7796
  // src/tilemap-renderer.ts
9276
- import { textAscent as textAscent6 } from "@opendata-ai/openchart-core";
9277
- var SVG_NS5 = "http://www.w3.org/2000/svg";
9278
- var XLINK_NS4 = "http://www.w3.org/1999/xlink";
9279
- var BRAND_URL5 = "https://tryopendata.ai";
9280
- var EASE_VAR_MAP4 = {
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 = {
9281
7802
  smooth: "var(--oc-ease-smooth)",
9282
7803
  snappy: "var(--oc-ease-snappy)"
9283
7804
  };
9284
7805
  var gradientIdCounter = 0;
9285
- function createSVGElement4(tag) {
9286
- return document.createElementNS(SVG_NS5, tag);
7806
+ function createSVGElement3(tag) {
7807
+ return document.createElementNS(SVG_NS3, tag);
9287
7808
  }
9288
- function setAttrs4(el, attrs) {
7809
+ function setAttrs3(el, attrs) {
9289
7810
  for (const [key, value] of Object.entries(attrs)) {
9290
7811
  el.setAttribute(key, String(value));
9291
7812
  }
9292
7813
  }
9293
- function renderChrome4(parent, layout) {
9294
- const g = createSVGElement4("g");
7814
+ function renderChrome3(parent, layout) {
7815
+ const g = createSVGElement3("g");
9295
7816
  g.setAttribute("class", "oc-chrome");
9296
7817
  const { chrome } = layout;
9297
7818
  const bottomOffset = layout.area.y + layout.area.height;
9298
7819
  if (chrome.title) {
9299
- const text = createSVGElement4("text");
9300
- setAttrs4(text, {
7820
+ const text = createSVGElement3("text");
7821
+ setAttrs3(text, {
9301
7822
  x: chrome.title.x,
9302
- y: chrome.title.y + textAscent6(chrome.title.style.fontSize)
7823
+ y: chrome.title.y + textAscent3(chrome.title.style.fontSize)
9303
7824
  });
9304
7825
  text.setAttribute("class", "oc-title");
9305
7826
  text.setAttribute("font-family", chrome.title.style.fontFamily);
@@ -9310,10 +7831,10 @@ function renderChrome4(parent, layout) {
9310
7831
  g.appendChild(text);
9311
7832
  }
9312
7833
  if (chrome.subtitle) {
9313
- const text = createSVGElement4("text");
9314
- setAttrs4(text, {
7834
+ const text = createSVGElement3("text");
7835
+ setAttrs3(text, {
9315
7836
  x: chrome.subtitle.x,
9316
- y: chrome.subtitle.y + textAscent6(chrome.subtitle.style.fontSize)
7837
+ y: chrome.subtitle.y + textAscent3(chrome.subtitle.style.fontSize)
9317
7838
  });
9318
7839
  text.setAttribute("class", "oc-subtitle");
9319
7840
  text.setAttribute("font-family", chrome.subtitle.style.fontFamily);
@@ -9327,10 +7848,10 @@ function renderChrome4(parent, layout) {
9327
7848
  g.appendChild(text);
9328
7849
  }
9329
7850
  if (chrome.source) {
9330
- const text = createSVGElement4("text");
9331
- setAttrs4(text, {
7851
+ const text = createSVGElement3("text");
7852
+ setAttrs3(text, {
9332
7853
  x: chrome.source.x,
9333
- y: bottomOffset + chrome.source.y + textAscent6(chrome.source.style.fontSize)
7854
+ y: bottomOffset + chrome.source.y + textAscent3(chrome.source.style.fontSize)
9334
7855
  });
9335
7856
  text.setAttribute("class", "oc-source");
9336
7857
  text.setAttribute("font-family", chrome.source.style.fontFamily);
@@ -9344,10 +7865,10 @@ function renderChrome4(parent, layout) {
9344
7865
  g.appendChild(text);
9345
7866
  }
9346
7867
  if (chrome.byline) {
9347
- const text = createSVGElement4("text");
9348
- setAttrs4(text, {
7868
+ const text = createSVGElement3("text");
7869
+ setAttrs3(text, {
9349
7870
  x: chrome.byline.x,
9350
- y: bottomOffset + chrome.byline.y + textAscent6(chrome.byline.style.fontSize)
7871
+ y: bottomOffset + chrome.byline.y + textAscent3(chrome.byline.style.fontSize)
9351
7872
  });
9352
7873
  text.setAttribute("class", "oc-byline");
9353
7874
  text.setAttribute("font-family", chrome.byline.style.fontFamily);
@@ -9361,10 +7882,10 @@ function renderChrome4(parent, layout) {
9361
7882
  g.appendChild(text);
9362
7883
  }
9363
7884
  if (chrome.footer) {
9364
- const text = createSVGElement4("text");
9365
- setAttrs4(text, {
7885
+ const text = createSVGElement3("text");
7886
+ setAttrs3(text, {
9366
7887
  x: chrome.footer.x,
9367
- y: bottomOffset + chrome.footer.y + textAscent6(chrome.footer.style.fontSize)
7888
+ y: bottomOffset + chrome.footer.y + textAscent3(chrome.footer.style.fontSize)
9368
7889
  });
9369
7890
  text.setAttribute("class", "oc-footer");
9370
7891
  text.setAttribute("font-family", chrome.footer.style.fontFamily);
@@ -9387,14 +7908,14 @@ function renderWatermark2(parent, layout) {
9387
7908
  const rightEdge = width - padding;
9388
7909
  const bottomEdge = height - padding;
9389
7910
  const fill = theme.colors.axis;
9390
- const a2 = createSVGElement4("a");
9391
- a2.setAttribute("href", BRAND_URL5);
9392
- a2.setAttributeNS(XLINK_NS4, "xlink:href", BRAND_URL5);
7911
+ const a2 = createSVGElement3("a");
7912
+ a2.setAttribute("href", BRAND_URL4);
7913
+ a2.setAttributeNS(XLINK_NS3, "xlink:href", BRAND_URL4);
9393
7914
  a2.setAttribute("target", "_blank");
9394
7915
  a2.setAttribute("rel", "noopener");
9395
7916
  a2.setAttribute("class", "oc-chrome-ref");
9396
- const text = createSVGElement4("text");
9397
- setAttrs4(text, {
7917
+ const text = createSVGElement3("text");
7918
+ setAttrs3(text, {
9398
7919
  x: rightEdge,
9399
7920
  y: bottomEdge,
9400
7921
  "dominant-baseline": "alphabetic",
@@ -9404,14 +7925,14 @@ function renderWatermark2(parent, layout) {
9404
7925
  "fill-opacity": 0.55
9405
7926
  });
9406
7927
  text.style.setProperty("fill", fill);
9407
- const trySpan = createSVGElement4("tspan");
9408
- setAttrs4(trySpan, { "font-weight": 500 });
7928
+ const trySpan = createSVGElement3("tspan");
7929
+ setAttrs3(trySpan, { "font-weight": 500 });
9409
7930
  trySpan.textContent = "try";
9410
- const openDataSpan = createSVGElement4("tspan");
9411
- setAttrs4(openDataSpan, { "font-weight": 600, "font-size": 16 });
7931
+ const openDataSpan = createSVGElement3("tspan");
7932
+ setAttrs3(openDataSpan, { "font-weight": 600, "font-size": 16 });
9412
7933
  openDataSpan.textContent = "OpenData";
9413
- const aiSpan = createSVGElement4("tspan");
9414
- setAttrs4(aiSpan, { "font-weight": 500 });
7934
+ const aiSpan = createSVGElement3("tspan");
7935
+ setAttrs3(aiSpan, { "font-weight": 500 });
9415
7936
  aiSpan.textContent = ".ai";
9416
7937
  text.appendChild(trySpan);
9417
7938
  text.appendChild(openDataSpan);
@@ -9420,7 +7941,7 @@ function renderWatermark2(parent, layout) {
9420
7941
  parent.appendChild(a2);
9421
7942
  }
9422
7943
  function renderTiles(parent, tiles, animation) {
9423
- const g = createSVGElement4("g");
7944
+ const g = createSVGElement3("g");
9424
7945
  g.setAttribute("class", "oc-tilemap-tiles");
9425
7946
  g.setAttribute("role", "list");
9426
7947
  const tileDelays = [];
@@ -9436,7 +7957,7 @@ function renderTiles(parent, tiles, animation) {
9436
7957
  }
9437
7958
  for (let i = 0; i < tiles.length; i++) {
9438
7959
  const tile = tiles[i];
9439
- const tileGroup = createSVGElement4("g");
7960
+ const tileGroup = createSVGElement3("g");
9440
7961
  tileGroup.setAttribute("class", "oc-tilemap-tile");
9441
7962
  tileGroup.setAttribute("data-state", tile.stateCode);
9442
7963
  tileGroup.setAttribute("role", "listitem");
@@ -9455,8 +7976,8 @@ function renderTiles(parent, tiles, animation) {
9455
7976
  `${tileDelays[i]}ms`
9456
7977
  );
9457
7978
  }
9458
- const rect = createSVGElement4("rect");
9459
- setAttrs4(rect, {
7979
+ const rect = createSVGElement3("rect");
7980
+ setAttrs3(rect, {
9460
7981
  x: tile.x,
9461
7982
  y: tile.y,
9462
7983
  width: tile.size,
@@ -9468,8 +7989,8 @@ function renderTiles(parent, tiles, animation) {
9468
7989
  "stroke-width": tile.strokeWidth
9469
7990
  });
9470
7991
  tileGroup.appendChild(rect);
9471
- const codeLabel = createSVGElement4("text");
9472
- setAttrs4(codeLabel, {
7992
+ const codeLabel = createSVGElement3("text");
7993
+ setAttrs3(codeLabel, {
9473
7994
  x: tile.label.x,
9474
7995
  y: tile.label.y,
9475
7996
  "text-anchor": "middle",
@@ -9485,8 +8006,8 @@ function renderTiles(parent, tiles, animation) {
9485
8006
  codeLabel.textContent = tile.label.text;
9486
8007
  tileGroup.appendChild(codeLabel);
9487
8008
  if (tile.valueLabel.visible && tile.valueLabel.text) {
9488
- const valueLabel = createSVGElement4("text");
9489
- setAttrs4(valueLabel, {
8009
+ const valueLabel = createSVGElement3("text");
8010
+ setAttrs3(valueLabel, {
9490
8011
  x: tile.valueLabel.x,
9491
8012
  y: tile.valueLabel.y,
9492
8013
  "text-anchor": "middle",
@@ -9509,22 +8030,22 @@ function renderTiles(parent, tiles, animation) {
9509
8030
  function renderGradientLegend(parent, layout) {
9510
8031
  if (!layout.gradientLegend) return;
9511
8032
  const { gradientLegend } = layout;
9512
- const g = createSVGElement4("g");
8033
+ const g = createSVGElement3("g");
9513
8034
  g.setAttribute("class", "oc-tilemap-legend");
9514
- const defs = parent.querySelector("defs") || createSVGElement4("defs");
8035
+ const defs = parent.querySelector("defs") || createSVGElement3("defs");
9515
8036
  const exists = parent.querySelector("defs");
9516
8037
  if (!exists) {
9517
8038
  parent.insertBefore(defs, parent.firstChild);
9518
8039
  }
9519
8040
  const gradientId = `oc-tilemap-legend-gradient-${gradientIdCounter++}`;
9520
- const grad = createSVGElement4("linearGradient");
8041
+ const grad = createSVGElement3("linearGradient");
9521
8042
  grad.id = gradientId;
9522
8043
  grad.setAttribute("x1", "0%");
9523
8044
  grad.setAttribute("y1", "0%");
9524
8045
  grad.setAttribute("x2", "100%");
9525
8046
  grad.setAttribute("y2", "0%");
9526
8047
  for (const stop of gradientLegend.colorStops) {
9527
- const s = createSVGElement4("stop");
8048
+ const s = createSVGElement3("stop");
9528
8049
  const attrs = {
9529
8050
  offset: `${stop.offset * 100}%`,
9530
8051
  "stop-color": stop.color
@@ -9532,13 +8053,13 @@ function renderGradientLegend(parent, layout) {
9532
8053
  if (stop.opacity !== void 0) {
9533
8054
  attrs["stop-opacity"] = stop.opacity;
9534
8055
  }
9535
- setAttrs4(s, attrs);
8056
+ setAttrs3(s, attrs);
9536
8057
  grad.appendChild(s);
9537
8058
  }
9538
8059
  defs.appendChild(grad);
9539
8060
  const barHeight = gradientLegend.bounds.height;
9540
- const bar = createSVGElement4("rect");
9541
- setAttrs4(bar, {
8061
+ const bar = createSVGElement3("rect");
8062
+ setAttrs3(bar, {
9542
8063
  x: gradientLegend.bounds.x,
9543
8064
  y: gradientLegend.bounds.y,
9544
8065
  width: gradientLegend.bounds.width,
@@ -9547,8 +8068,8 @@ function renderGradientLegend(parent, layout) {
9547
8068
  fill: `url(#${gradientId})`
9548
8069
  });
9549
8070
  g.appendChild(bar);
9550
- const minText = createSVGElement4("text");
9551
- setAttrs4(minText, {
8071
+ const minText = createSVGElement3("text");
8072
+ setAttrs3(minText, {
9552
8073
  x: gradientLegend.bounds.x,
9553
8074
  y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
9554
8075
  "text-anchor": "start",
@@ -9562,8 +8083,8 @@ function renderGradientLegend(parent, layout) {
9562
8083
  );
9563
8084
  minText.textContent = gradientLegend.minLabel;
9564
8085
  g.appendChild(minText);
9565
- const maxText = createSVGElement4("text");
9566
- setAttrs4(maxText, {
8086
+ const maxText = createSVGElement3("text");
8087
+ setAttrs3(maxText, {
9567
8088
  x: gradientLegend.bounds.x + gradientLegend.bounds.width,
9568
8089
  y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
9569
8090
  "text-anchor": "end",
@@ -9582,7 +8103,7 @@ function renderGradientLegend(parent, layout) {
9582
8103
  function renderTileMapSVG(layout, opts) {
9583
8104
  const { width, height, tiles, a11y, watermark, animation } = layout;
9584
8105
  const animate = opts?.animate && animation?.enabled;
9585
- const svg = createSVGElement4("svg");
8106
+ const svg = createSVGElement3("svg");
9586
8107
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
9587
8108
  svg.setAttribute("role", "img");
9588
8109
  if (a11y.altText) {
@@ -9595,14 +8116,18 @@ function renderTileMapSVG(layout, opts) {
9595
8116
  svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
9596
8117
  svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
9597
8118
  svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
9598
- const easeVar = EASE_VAR_MAP4[animation.ease] || EASE_VAR_MAP4.smooth;
8119
+ const easeVar = EASE_VAR_MAP3[animation.ease] || EASE_VAR_MAP3.smooth;
9599
8120
  svg.style.setProperty("--oc-animation-ease", easeVar);
9600
8121
  }
9601
- const defs = createSVGElement4("defs");
8122
+ const defs = createSVGElement3("defs");
9602
8123
  svg.appendChild(defs);
9603
- renderChrome4(svg, layout);
8124
+ renderChrome3(svg, layout);
9604
8125
  renderTiles(svg, tiles, animate ? animation : void 0);
9605
- renderGradientLegend(svg, layout);
8126
+ if (layout.categoricalLegend) {
8127
+ renderLegend(svg, layout.categoricalLegend);
8128
+ } else {
8129
+ renderGradientLegend(svg, layout);
8130
+ }
9606
8131
  if (watermark) {
9607
8132
  renderWatermark2(svg, layout);
9608
8133
  }