@opendata-ai/openchart-vanilla 7.6.1 → 7.8.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
+ renderTileMapSVG
5
+ } from "./chunk-KYRQV6KR.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
+ renderChrome3();
3368
3374
  wrapper.appendChild(chromeEl);
3369
3375
  canvas = document.createElement("canvas");
3370
3376
  canvas.className = "oc-graph-canvas";
@@ -3376,7 +3382,7 @@ function createGraph(container, spec, options) {
3376
3382
  if (options?.legend !== false) {
3377
3383
  legendEl = document.createElement("div");
3378
3384
  legendEl.className = "oc-graph-legend";
3379
- renderLegend3();
3385
+ renderLegend2();
3380
3386
  wrapper.appendChild(legendEl);
3381
3387
  }
3382
3388
  container.appendChild(wrapper);
@@ -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 renderChrome3() {
3388
3394
  if (!chromeEl) return;
3389
3395
  let html = "";
3390
3396
  if (compilation.chrome.title) {
@@ -3400,7 +3406,7 @@ function createGraph(container, spec, options) {
3400
3406
  chromeEl.style.display = "";
3401
3407
  }
3402
3408
  }
3403
- function renderLegend3() {
3409
+ function renderLegend2() {
3404
3410
  if (!legendEl) return;
3405
3411
  const entries = "entries" in compilation.legend ? compilation.legend.entries : [];
3406
3412
  if (entries.length === 0) {
@@ -3698,8 +3704,8 @@ function createGraph(container, spec, options) {
3698
3704
  compilation = compile();
3699
3705
  adjacencyMap = buildAdjacencyMap(compilation.edges);
3700
3706
  buildDataMaps();
3701
- renderChrome5();
3702
- renderLegend3();
3707
+ renderChrome3();
3708
+ renderLegend2();
3703
3709
  initSimulation();
3704
3710
  initInteraction();
3705
3711
  hoveredNodeId = null;
@@ -3733,8 +3739,8 @@ function createGraph(container, spec, options) {
3733
3739
  };
3734
3740
  });
3735
3741
  spatialIndex.rebuild(positionedNodes);
3736
- renderChrome5();
3737
- renderLegend3();
3742
+ renderChrome3();
3743
+ renderLegend2();
3738
3744
  needsRender = true;
3739
3745
  scheduleRender();
3740
3746
  }
@@ -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_NS3 = "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_NS3, "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,1716 +4989,208 @@ 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);
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";
5097
5054
  }
5098
- if (style.dominantBaseline) {
5099
- el.setAttribute("dominant-baseline", style.dominantBaseline);
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"
5080
+ });
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);
5089
+ } else {
5090
+ targetElement.removeAttribute("opacity");
5091
+ }
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);
5100
+ }
5100
5101
  }
5101
- if (style.fontVariant) {
5102
- el.setAttribute("font-variant", style.fontVariant);
5102
+ function commit() {
5103
+ if (destroyed) return;
5104
+ const newText = textarea.value;
5105
+ destroy();
5106
+ onCommit(newText);
5103
5107
  }
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
5146
- });
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);
5108
+ function cancel() {
5109
+ if (destroyed) return;
5110
+ destroy();
5111
+ onCancel();
5155
5112
  }
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);
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();
5256
5120
  }
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
- }
5272
- } 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";
5121
+ };
5122
+ textarea.addEventListener("keydown", handleKeyDown);
5123
+ const handleClickOutside = (e) => {
5124
+ if (!textarea.contains(e.target)) {
5125
+ commit();
5312
5126
  }
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);
5127
+ };
5128
+ requestAnimationFrame(() => {
5129
+ if (!destroyed) {
5130
+ document.addEventListener("mousedown", handleClickOutside);
5321
5131
  }
5322
- }
5323
- parent.appendChild(g);
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 };
5324
5143
  }
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);
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);
5332
5177
  }
5333
- parent.appendChild(g);
5334
5178
  }
5335
5179
 
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
- }
5519
- }
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
- });
5586
- }
5587
- g.appendChild(axisLabel);
5180
+ // src/mount.ts
5181
+ function resolveDarkMode3(mode) {
5182
+ if (mode === "force") return true;
5183
+ if (mode === "off" || mode === void 0) return false;
5184
+ if (typeof window !== "undefined" && window.matchMedia) {
5185
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
5588
5186
  }
5589
- parent.appendChild(g);
5187
+ return false;
5590
5188
  }
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);
5600
- }
5601
- }
5602
-
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
- // src/mount.ts
6683
- function resolveDarkMode3(mode) {
6684
- if (mode === "force") return true;
6685
- if (mode === "off" || mode === void 0) return false;
6686
- if (typeof window !== "undefined" && window.matchMedia) {
6687
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
6688
- }
6689
- return false;
6690
- }
6691
- var EDITABLE_HOVER_CSS = `
6692
- .oc-editable-hover {
6693
- outline: 1.5px solid rgba(79, 70, 229, 0.35);
6694
- outline-offset: 2px;
6695
- border-radius: 2px;
5189
+ var EDITABLE_HOVER_CSS = `
5190
+ .oc-editable-hover {
5191
+ outline: 1.5px solid rgba(79, 70, 229, 0.35);
5192
+ outline-offset: 2px;
5193
+ border-radius: 2px;
6696
5194
  }
6697
5195
  `;
6698
5196
  function makeEditable(svg) {
@@ -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);
@@ -7814,10 +6335,10 @@ function renderBrand2(parent, layout) {
7814
6335
  a2.appendChild(text);
7815
6336
  parent.appendChild(a2);
7816
6337
  }
7817
- function renderLegend2(parent, legendLayout) {
6338
+ function renderLegend(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
- renderLegend2(svg, layout.legend);
8065
- renderChrome3(svg, layout);
6585
+ renderLegend(svg, layout.legend);
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);
@@ -9271,345 +7792,6 @@ function createTable(container, spec, options) {
9271
7792
 
9272
7793
  // src/tilemap-mount.ts
9273
7794
  import { compileTileMap } from "@opendata-ai/openchart-engine";
9274
-
9275
- // 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 = {
9281
- smooth: "var(--oc-ease-smooth)",
9282
- snappy: "var(--oc-ease-snappy)"
9283
- };
9284
- var gradientIdCounter = 0;
9285
- function createSVGElement4(tag) {
9286
- return document.createElementNS(SVG_NS5, tag);
9287
- }
9288
- function setAttrs4(el, attrs) {
9289
- for (const [key, value] of Object.entries(attrs)) {
9290
- el.setAttribute(key, String(value));
9291
- }
9292
- }
9293
- function renderChrome4(parent, layout) {
9294
- const g = createSVGElement4("g");
9295
- g.setAttribute("class", "oc-chrome");
9296
- const { chrome } = layout;
9297
- const bottomOffset = layout.area.y + layout.area.height;
9298
- if (chrome.title) {
9299
- const text = createSVGElement4("text");
9300
- setAttrs4(text, {
9301
- x: chrome.title.x,
9302
- y: chrome.title.y + textAscent6(chrome.title.style.fontSize)
9303
- });
9304
- text.setAttribute("class", "oc-title");
9305
- text.setAttribute("font-family", chrome.title.style.fontFamily);
9306
- text.setAttribute("font-size", String(chrome.title.style.fontSize));
9307
- text.setAttribute("font-weight", String(chrome.title.style.fontWeight));
9308
- text.style.setProperty("fill", chrome.title.style.fill);
9309
- text.textContent = chrome.title.text;
9310
- g.appendChild(text);
9311
- }
9312
- if (chrome.subtitle) {
9313
- const text = createSVGElement4("text");
9314
- setAttrs4(text, {
9315
- x: chrome.subtitle.x,
9316
- y: chrome.subtitle.y + textAscent6(chrome.subtitle.style.fontSize)
9317
- });
9318
- text.setAttribute("class", "oc-subtitle");
9319
- text.setAttribute("font-family", chrome.subtitle.style.fontFamily);
9320
- text.setAttribute("font-size", String(chrome.subtitle.style.fontSize));
9321
- text.setAttribute("font-weight", String(chrome.subtitle.style.fontWeight));
9322
- text.style.setProperty(
9323
- "fill",
9324
- chrome.subtitle.style.fill
9325
- );
9326
- text.textContent = chrome.subtitle.text;
9327
- g.appendChild(text);
9328
- }
9329
- if (chrome.source) {
9330
- const text = createSVGElement4("text");
9331
- setAttrs4(text, {
9332
- x: chrome.source.x,
9333
- y: bottomOffset + chrome.source.y + textAscent6(chrome.source.style.fontSize)
9334
- });
9335
- text.setAttribute("class", "oc-source");
9336
- text.setAttribute("font-family", chrome.source.style.fontFamily);
9337
- text.setAttribute("font-size", String(chrome.source.style.fontSize));
9338
- text.setAttribute("font-weight", String(chrome.source.style.fontWeight));
9339
- text.style.setProperty(
9340
- "fill",
9341
- chrome.source.style.fill
9342
- );
9343
- text.textContent = chrome.source.text;
9344
- g.appendChild(text);
9345
- }
9346
- if (chrome.byline) {
9347
- const text = createSVGElement4("text");
9348
- setAttrs4(text, {
9349
- x: chrome.byline.x,
9350
- y: bottomOffset + chrome.byline.y + textAscent6(chrome.byline.style.fontSize)
9351
- });
9352
- text.setAttribute("class", "oc-byline");
9353
- text.setAttribute("font-family", chrome.byline.style.fontFamily);
9354
- text.setAttribute("font-size", String(chrome.byline.style.fontSize));
9355
- text.setAttribute("font-weight", String(chrome.byline.style.fontWeight));
9356
- text.style.setProperty(
9357
- "fill",
9358
- chrome.byline.style.fill
9359
- );
9360
- text.textContent = chrome.byline.text;
9361
- g.appendChild(text);
9362
- }
9363
- if (chrome.footer) {
9364
- const text = createSVGElement4("text");
9365
- setAttrs4(text, {
9366
- x: chrome.footer.x,
9367
- y: bottomOffset + chrome.footer.y + textAscent6(chrome.footer.style.fontSize)
9368
- });
9369
- text.setAttribute("class", "oc-footer");
9370
- text.setAttribute("font-family", chrome.footer.style.fontFamily);
9371
- text.setAttribute("font-size", String(chrome.footer.style.fontSize));
9372
- text.setAttribute("font-weight", String(chrome.footer.style.fontWeight));
9373
- text.style.setProperty(
9374
- "fill",
9375
- chrome.footer.style.fill
9376
- );
9377
- text.textContent = chrome.footer.text;
9378
- g.appendChild(text);
9379
- }
9380
- parent.appendChild(g);
9381
- }
9382
- function renderWatermark2(parent, layout) {
9383
- if (layout.width < 480) return;
9384
- const { width, height } = layout;
9385
- const { theme } = layout;
9386
- const padding = theme.spacing.padding;
9387
- const rightEdge = width - padding;
9388
- const bottomEdge = height - padding;
9389
- 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);
9393
- a2.setAttribute("target", "_blank");
9394
- a2.setAttribute("rel", "noopener");
9395
- a2.setAttribute("class", "oc-chrome-ref");
9396
- const text = createSVGElement4("text");
9397
- setAttrs4(text, {
9398
- x: rightEdge,
9399
- y: bottomEdge,
9400
- "dominant-baseline": "alphabetic",
9401
- "text-anchor": "end",
9402
- "font-family": theme.fonts.family,
9403
- "font-size": 12,
9404
- "fill-opacity": 0.55
9405
- });
9406
- text.style.setProperty("fill", fill);
9407
- const trySpan = createSVGElement4("tspan");
9408
- setAttrs4(trySpan, { "font-weight": 500 });
9409
- trySpan.textContent = "try";
9410
- const openDataSpan = createSVGElement4("tspan");
9411
- setAttrs4(openDataSpan, { "font-weight": 600, "font-size": 16 });
9412
- openDataSpan.textContent = "OpenData";
9413
- const aiSpan = createSVGElement4("tspan");
9414
- setAttrs4(aiSpan, { "font-weight": 500 });
9415
- aiSpan.textContent = ".ai";
9416
- text.appendChild(trySpan);
9417
- text.appendChild(openDataSpan);
9418
- text.appendChild(aiSpan);
9419
- a2.appendChild(text);
9420
- parent.appendChild(a2);
9421
- }
9422
- function renderTiles(parent, tiles, animation) {
9423
- const g = createSVGElement4("g");
9424
- g.setAttribute("class", "oc-tilemap-tiles");
9425
- g.setAttribute("role", "list");
9426
- const tileDelays = [];
9427
- if (animation?.enabled) {
9428
- const baseStagger = 800 / Math.max(tiles.length, 1);
9429
- let seed = 17;
9430
- for (let i = 0; i < tiles.length; i++) {
9431
- const idx = tiles[i].animationIndex ?? i;
9432
- seed = seed * 1103515245 + 12345 & 2147483647;
9433
- const jitter = (seed % 1e3 / 1e3 - 0.5) * 0.8;
9434
- tileDelays.push(Math.max(0, Math.round(idx * baseStagger * (1 + jitter))));
9435
- }
9436
- }
9437
- for (let i = 0; i < tiles.length; i++) {
9438
- const tile = tiles[i];
9439
- const tileGroup = createSVGElement4("g");
9440
- tileGroup.setAttribute("class", "oc-tilemap-tile");
9441
- tileGroup.setAttribute("data-state", tile.stateCode);
9442
- tileGroup.setAttribute("role", "listitem");
9443
- if (tile.aria?.label) {
9444
- tileGroup.setAttribute("aria-label", tile.aria.label);
9445
- }
9446
- if (animation?.enabled) {
9447
- const idx = tile.animationIndex ?? i;
9448
- tileGroup.setAttribute("data-animation-index", String(idx));
9449
- tileGroup.style.setProperty(
9450
- "--oc-mark-index",
9451
- String(idx)
9452
- );
9453
- tileGroup.style.setProperty(
9454
- "--oc-tile-delay",
9455
- `${tileDelays[i]}ms`
9456
- );
9457
- }
9458
- const rect = createSVGElement4("rect");
9459
- setAttrs4(rect, {
9460
- x: tile.x,
9461
- y: tile.y,
9462
- width: tile.size,
9463
- height: tile.size,
9464
- rx: tile.cornerRadius,
9465
- fill: tile.fill,
9466
- "fill-opacity": tile.fillOpacity ?? 1,
9467
- stroke: tile.stroke,
9468
- "stroke-width": tile.strokeWidth
9469
- });
9470
- tileGroup.appendChild(rect);
9471
- const codeLabel = createSVGElement4("text");
9472
- setAttrs4(codeLabel, {
9473
- x: tile.label.x,
9474
- y: tile.label.y,
9475
- "text-anchor": "middle",
9476
- "dominant-baseline": "central",
9477
- "font-family": tile.label.style.fontFamily,
9478
- "font-size": tile.label.style.fontSize,
9479
- "font-weight": tile.label.style.fontWeight
9480
- });
9481
- codeLabel.style.setProperty(
9482
- "fill",
9483
- tile.label.style.fill
9484
- );
9485
- codeLabel.textContent = tile.label.text;
9486
- tileGroup.appendChild(codeLabel);
9487
- if (tile.valueLabel.visible && tile.valueLabel.text) {
9488
- const valueLabel = createSVGElement4("text");
9489
- setAttrs4(valueLabel, {
9490
- x: tile.valueLabel.x,
9491
- y: tile.valueLabel.y,
9492
- "text-anchor": "middle",
9493
- "dominant-baseline": "central",
9494
- "font-family": tile.valueLabel.style.fontFamily,
9495
- "font-size": tile.valueLabel.style.fontSize,
9496
- "font-weight": tile.valueLabel.style.fontWeight
9497
- });
9498
- valueLabel.style.setProperty(
9499
- "fill",
9500
- tile.valueLabel.style.fill
9501
- );
9502
- valueLabel.textContent = tile.valueLabel.text;
9503
- tileGroup.appendChild(valueLabel);
9504
- }
9505
- g.appendChild(tileGroup);
9506
- }
9507
- parent.appendChild(g);
9508
- }
9509
- function renderGradientLegend(parent, layout) {
9510
- if (!layout.gradientLegend) return;
9511
- const { gradientLegend } = layout;
9512
- const g = createSVGElement4("g");
9513
- g.setAttribute("class", "oc-tilemap-legend");
9514
- const defs = parent.querySelector("defs") || createSVGElement4("defs");
9515
- const exists = parent.querySelector("defs");
9516
- if (!exists) {
9517
- parent.insertBefore(defs, parent.firstChild);
9518
- }
9519
- const gradientId = `oc-tilemap-legend-gradient-${gradientIdCounter++}`;
9520
- const grad = createSVGElement4("linearGradient");
9521
- grad.id = gradientId;
9522
- grad.setAttribute("x1", "0%");
9523
- grad.setAttribute("y1", "0%");
9524
- grad.setAttribute("x2", "100%");
9525
- grad.setAttribute("y2", "0%");
9526
- for (const stop of gradientLegend.colorStops) {
9527
- const s = createSVGElement4("stop");
9528
- const attrs = {
9529
- offset: `${stop.offset * 100}%`,
9530
- "stop-color": stop.color
9531
- };
9532
- if (stop.opacity !== void 0) {
9533
- attrs["stop-opacity"] = stop.opacity;
9534
- }
9535
- setAttrs4(s, attrs);
9536
- grad.appendChild(s);
9537
- }
9538
- defs.appendChild(grad);
9539
- const barHeight = gradientLegend.bounds.height;
9540
- const bar = createSVGElement4("rect");
9541
- setAttrs4(bar, {
9542
- x: gradientLegend.bounds.x,
9543
- y: gradientLegend.bounds.y,
9544
- width: gradientLegend.bounds.width,
9545
- height: barHeight,
9546
- rx: barHeight / 2,
9547
- fill: `url(#${gradientId})`
9548
- });
9549
- g.appendChild(bar);
9550
- const minText = createSVGElement4("text");
9551
- setAttrs4(minText, {
9552
- x: gradientLegend.bounds.x,
9553
- y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
9554
- "text-anchor": "start",
9555
- "font-family": gradientLegend.labelStyle.fontFamily,
9556
- "font-size": gradientLegend.labelStyle.fontSize,
9557
- "font-weight": gradientLegend.labelStyle.fontWeight
9558
- });
9559
- minText.style.setProperty(
9560
- "fill",
9561
- gradientLegend.labelStyle.fill
9562
- );
9563
- minText.textContent = gradientLegend.minLabel;
9564
- g.appendChild(minText);
9565
- const maxText = createSVGElement4("text");
9566
- setAttrs4(maxText, {
9567
- x: gradientLegend.bounds.x + gradientLegend.bounds.width,
9568
- y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
9569
- "text-anchor": "end",
9570
- "font-family": gradientLegend.labelStyle.fontFamily,
9571
- "font-size": gradientLegend.labelStyle.fontSize,
9572
- "font-weight": gradientLegend.labelStyle.fontWeight
9573
- });
9574
- maxText.style.setProperty(
9575
- "fill",
9576
- gradientLegend.labelStyle.fill
9577
- );
9578
- maxText.textContent = gradientLegend.maxLabel;
9579
- g.appendChild(maxText);
9580
- parent.appendChild(g);
9581
- }
9582
- function renderTileMapSVG(layout, opts) {
9583
- const { width, height, tiles, a11y, watermark, animation } = layout;
9584
- const animate = opts?.animate && animation?.enabled;
9585
- const svg = createSVGElement4("svg");
9586
- svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
9587
- svg.setAttribute("role", "img");
9588
- if (a11y.altText) {
9589
- svg.setAttribute("aria-label", a11y.altText);
9590
- }
9591
- const classes = animate ? "oc-tilemap oc-animate" : "oc-tilemap";
9592
- svg.setAttribute("class", classes);
9593
- if (animate && animation) {
9594
- const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));
9595
- svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
9596
- svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
9597
- svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
9598
- const easeVar = EASE_VAR_MAP4[animation.ease] || EASE_VAR_MAP4.smooth;
9599
- svg.style.setProperty("--oc-animation-ease", easeVar);
9600
- }
9601
- const defs = createSVGElement4("defs");
9602
- svg.appendChild(defs);
9603
- renderChrome4(svg, layout);
9604
- renderTiles(svg, tiles, animate ? animation : void 0);
9605
- renderGradientLegend(svg, layout);
9606
- if (watermark) {
9607
- renderWatermark2(svg, layout);
9608
- }
9609
- return svg;
9610
- }
9611
-
9612
- // src/tilemap-mount.ts
9613
7795
  function resolveDarkMode6(mode) {
9614
7796
  if (mode === "force") return true;
9615
7797
  if (mode === "off" || mode === void 0) return false;