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