@opendata-ai/openchart-vanilla 6.27.0 → 6.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +36 -2
- package/dist/index.js +901 -486
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/src/barlist-mount.ts +314 -0
- package/src/barlist-renderer.ts +264 -0
- package/src/index.ts +3 -0
- package/src/renderers/axes.ts +2 -2
- package/src/svg-renderer.ts +8 -2
- package/src/tilemap-renderer.ts +13 -4
package/dist/index.js
CHANGED
|
@@ -1,3 +1,236 @@
|
|
|
1
|
+
// src/barlist-mount.ts
|
|
2
|
+
import { compileBarList } from "@opendata-ai/openchart-engine";
|
|
3
|
+
|
|
4
|
+
// src/animation.ts
|
|
5
|
+
function cancelAnimations(svg) {
|
|
6
|
+
if (svg) {
|
|
7
|
+
svg.classList.remove("oc-animate");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function setupAnimationCleanup(svg, onComplete) {
|
|
11
|
+
const style = svg.style;
|
|
12
|
+
const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 600;
|
|
13
|
+
const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
|
|
14
|
+
const annotationDelay = parseFloat(style.getPropertyValue("--oc-annotation-delay")) || 200;
|
|
15
|
+
const animatedElements = svg.querySelectorAll("[data-animation-index]").length;
|
|
16
|
+
const totalStagger = stagger * Math.max(0, animatedElements - 1);
|
|
17
|
+
const totalTime = totalStagger + duration + annotationDelay + 500;
|
|
18
|
+
const timer2 = setTimeout(() => {
|
|
19
|
+
svg.classList.remove("oc-animate");
|
|
20
|
+
onComplete?.();
|
|
21
|
+
}, totalTime);
|
|
22
|
+
return () => {
|
|
23
|
+
clearTimeout(timer2);
|
|
24
|
+
cancelAnimations(svg);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function setupTableAnimationCleanup(wrapper) {
|
|
28
|
+
const style = wrapper.style;
|
|
29
|
+
const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 500;
|
|
30
|
+
const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
|
|
31
|
+
const rows = wrapper.querySelectorAll("tbody tr").length;
|
|
32
|
+
const totalStagger = stagger * Math.max(0, rows - 1);
|
|
33
|
+
const totalTime = totalStagger + duration + 300;
|
|
34
|
+
const timer2 = setTimeout(() => {
|
|
35
|
+
wrapper.classList.remove("oc-animate");
|
|
36
|
+
}, totalTime);
|
|
37
|
+
return () => {
|
|
38
|
+
clearTimeout(timer2);
|
|
39
|
+
wrapper.classList.remove("oc-animate");
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/barlist-renderer.ts
|
|
44
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
45
|
+
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
46
|
+
var BRAND_URL = "https://tryopendata.ai";
|
|
47
|
+
function createSVGElement(tag) {
|
|
48
|
+
return document.createElementNS(SVG_NS, tag);
|
|
49
|
+
}
|
|
50
|
+
function setAttrs(el, attrs) {
|
|
51
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
52
|
+
el.setAttribute(key, String(value));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function renderChrome(parent, layout) {
|
|
56
|
+
const g = createSVGElement("g");
|
|
57
|
+
g.setAttribute("class", "oc-chrome");
|
|
58
|
+
const { chrome } = layout;
|
|
59
|
+
const bottomOffset = layout.area.y + layout.area.height;
|
|
60
|
+
for (const key of ["title", "subtitle", "source", "byline", "footer"]) {
|
|
61
|
+
const el = chrome[key];
|
|
62
|
+
if (!el) continue;
|
|
63
|
+
const isBottom = key === "source" || key === "byline" || key === "footer";
|
|
64
|
+
const text = createSVGElement("text");
|
|
65
|
+
setAttrs(text, {
|
|
66
|
+
x: el.x,
|
|
67
|
+
y: isBottom ? bottomOffset + el.y : el.y
|
|
68
|
+
});
|
|
69
|
+
text.setAttribute("class", `oc-${key}`);
|
|
70
|
+
text.setAttribute("font-family", el.style.fontFamily);
|
|
71
|
+
text.setAttribute("font-size", String(el.style.fontSize));
|
|
72
|
+
text.setAttribute("font-weight", String(el.style.fontWeight));
|
|
73
|
+
text.style.setProperty("fill", el.style.fill);
|
|
74
|
+
text.textContent = el.text;
|
|
75
|
+
g.appendChild(text);
|
|
76
|
+
}
|
|
77
|
+
parent.appendChild(g);
|
|
78
|
+
}
|
|
79
|
+
function renderWatermark(parent, layout) {
|
|
80
|
+
if (layout.width < 480) return;
|
|
81
|
+
const { width, height, theme } = layout;
|
|
82
|
+
const padding = theme.spacing.padding;
|
|
83
|
+
const rightEdge = width - padding;
|
|
84
|
+
const bottomEdge = height - padding;
|
|
85
|
+
const fill = theme.colors.axis;
|
|
86
|
+
const a2 = createSVGElement("a");
|
|
87
|
+
a2.setAttribute("href", BRAND_URL);
|
|
88
|
+
a2.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
|
|
89
|
+
a2.setAttribute("target", "_blank");
|
|
90
|
+
a2.setAttribute("rel", "noopener");
|
|
91
|
+
a2.setAttribute("class", "oc-chrome-ref");
|
|
92
|
+
const text = createSVGElement("text");
|
|
93
|
+
setAttrs(text, {
|
|
94
|
+
x: rightEdge,
|
|
95
|
+
y: bottomEdge,
|
|
96
|
+
"dominant-baseline": "alphabetic",
|
|
97
|
+
"text-anchor": "end",
|
|
98
|
+
"font-family": theme.fonts.family,
|
|
99
|
+
"font-size": 12,
|
|
100
|
+
"fill-opacity": 0.55
|
|
101
|
+
});
|
|
102
|
+
text.style.setProperty("fill", fill);
|
|
103
|
+
const trySpan = createSVGElement("tspan");
|
|
104
|
+
setAttrs(trySpan, { "font-weight": 500 });
|
|
105
|
+
trySpan.textContent = "try";
|
|
106
|
+
const openDataSpan = createSVGElement("tspan");
|
|
107
|
+
setAttrs(openDataSpan, { "font-weight": 600, "font-size": 16 });
|
|
108
|
+
openDataSpan.textContent = "OpenData";
|
|
109
|
+
const aiSpan = createSVGElement("tspan");
|
|
110
|
+
setAttrs(aiSpan, { "font-weight": 500 });
|
|
111
|
+
aiSpan.textContent = ".ai";
|
|
112
|
+
text.appendChild(trySpan);
|
|
113
|
+
text.appendChild(openDataSpan);
|
|
114
|
+
text.appendChild(aiSpan);
|
|
115
|
+
a2.appendChild(text);
|
|
116
|
+
parent.appendChild(a2);
|
|
117
|
+
}
|
|
118
|
+
function renderRows(parent, rows, animation) {
|
|
119
|
+
const g = createSVGElement("g");
|
|
120
|
+
g.setAttribute("class", "oc-barlist-rows");
|
|
121
|
+
g.setAttribute("role", "list");
|
|
122
|
+
for (const row of rows) {
|
|
123
|
+
const rowGroup = createSVGElement("g");
|
|
124
|
+
rowGroup.setAttribute("class", "oc-barlist-row");
|
|
125
|
+
rowGroup.setAttribute("data-row-index", String(row.index));
|
|
126
|
+
rowGroup.setAttribute("role", "listitem");
|
|
127
|
+
if (row.aria?.label) {
|
|
128
|
+
rowGroup.setAttribute("aria-label", row.aria.label);
|
|
129
|
+
}
|
|
130
|
+
if (animation?.enabled) {
|
|
131
|
+
rowGroup.setAttribute("data-animation-index", String(row.animationIndex));
|
|
132
|
+
const style = rowGroup.style;
|
|
133
|
+
style.setProperty("--oc-mark-index", String(row.animationIndex));
|
|
134
|
+
style.setProperty("--oc-row-delay", `${row.animationIndex * 40}ms`);
|
|
135
|
+
}
|
|
136
|
+
const labelEl = createSVGElement("text");
|
|
137
|
+
setAttrs(labelEl, {
|
|
138
|
+
x: row.label.x,
|
|
139
|
+
y: row.label.y,
|
|
140
|
+
"dominant-baseline": "central",
|
|
141
|
+
"text-anchor": "start",
|
|
142
|
+
"font-family": row.label.style.fontFamily,
|
|
143
|
+
"font-size": row.label.style.fontSize,
|
|
144
|
+
"font-weight": row.label.style.fontWeight
|
|
145
|
+
});
|
|
146
|
+
labelEl.style.setProperty("fill", row.label.style.fill);
|
|
147
|
+
labelEl.textContent = row.label.text;
|
|
148
|
+
rowGroup.appendChild(labelEl);
|
|
149
|
+
if (row.subtitle?.visible) {
|
|
150
|
+
const subEl = createSVGElement("text");
|
|
151
|
+
setAttrs(subEl, {
|
|
152
|
+
x: row.subtitle.x,
|
|
153
|
+
y: row.subtitle.y,
|
|
154
|
+
"dominant-baseline": "central",
|
|
155
|
+
"text-anchor": "start",
|
|
156
|
+
"font-family": row.subtitle.style.fontFamily,
|
|
157
|
+
"font-size": row.subtitle.style.fontSize,
|
|
158
|
+
"font-weight": row.subtitle.style.fontWeight
|
|
159
|
+
});
|
|
160
|
+
subEl.style.setProperty(
|
|
161
|
+
"fill",
|
|
162
|
+
row.subtitle.style.fill
|
|
163
|
+
);
|
|
164
|
+
subEl.textContent = row.subtitle.text;
|
|
165
|
+
rowGroup.appendChild(subEl);
|
|
166
|
+
}
|
|
167
|
+
const trackRect = createSVGElement("rect");
|
|
168
|
+
setAttrs(trackRect, {
|
|
169
|
+
x: row.track.x,
|
|
170
|
+
y: row.track.y,
|
|
171
|
+
width: row.track.width,
|
|
172
|
+
height: row.track.height,
|
|
173
|
+
rx: row.track.cornerRadius,
|
|
174
|
+
fill: "currentColor",
|
|
175
|
+
"fill-opacity": 0.06
|
|
176
|
+
});
|
|
177
|
+
trackRect.setAttribute("class", "oc-barlist-track");
|
|
178
|
+
rowGroup.appendChild(trackRect);
|
|
179
|
+
const barRect = createSVGElement("rect");
|
|
180
|
+
setAttrs(barRect, {
|
|
181
|
+
x: row.bar.x,
|
|
182
|
+
y: row.bar.y,
|
|
183
|
+
width: row.bar.width,
|
|
184
|
+
height: row.bar.height,
|
|
185
|
+
rx: row.bar.cornerRadius,
|
|
186
|
+
fill: row.bar.fill
|
|
187
|
+
});
|
|
188
|
+
barRect.setAttribute("class", "oc-barlist-bar");
|
|
189
|
+
rowGroup.appendChild(barRect);
|
|
190
|
+
const valueEl = createSVGElement("text");
|
|
191
|
+
setAttrs(valueEl, {
|
|
192
|
+
x: row.valueLabel.x,
|
|
193
|
+
y: row.valueLabel.y,
|
|
194
|
+
"dominant-baseline": "central",
|
|
195
|
+
"text-anchor": "end",
|
|
196
|
+
"font-family": row.valueLabel.style.fontFamily,
|
|
197
|
+
"font-size": row.valueLabel.style.fontSize,
|
|
198
|
+
"font-weight": row.valueLabel.style.fontWeight
|
|
199
|
+
});
|
|
200
|
+
valueEl.style.setProperty(
|
|
201
|
+
"fill",
|
|
202
|
+
row.valueLabel.style.fill
|
|
203
|
+
);
|
|
204
|
+
valueEl.textContent = row.valueLabel.text;
|
|
205
|
+
rowGroup.appendChild(valueEl);
|
|
206
|
+
g.appendChild(rowGroup);
|
|
207
|
+
}
|
|
208
|
+
parent.appendChild(g);
|
|
209
|
+
}
|
|
210
|
+
function renderBarListSVG(layout, opts) {
|
|
211
|
+
const { width, height, rows, a11y, watermark, animation } = layout;
|
|
212
|
+
const animate = opts?.animate && animation?.enabled;
|
|
213
|
+
const svg = createSVGElement("svg");
|
|
214
|
+
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
215
|
+
svg.setAttribute("role", "list");
|
|
216
|
+
svg.style.height = `${height}px`;
|
|
217
|
+
if (a11y.altText) {
|
|
218
|
+
svg.setAttribute("aria-label", a11y.altText);
|
|
219
|
+
}
|
|
220
|
+
const classes = animate ? "oc-barlist oc-animate" : "oc-barlist";
|
|
221
|
+
svg.setAttribute("class", classes);
|
|
222
|
+
if (animate && animation) {
|
|
223
|
+
svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
|
|
224
|
+
svg.style.setProperty("--oc-animation-stagger", "40ms");
|
|
225
|
+
}
|
|
226
|
+
renderChrome(svg, layout);
|
|
227
|
+
renderRows(svg, rows, animate ? animation : void 0);
|
|
228
|
+
if (watermark) {
|
|
229
|
+
renderWatermark(svg, layout);
|
|
230
|
+
}
|
|
231
|
+
return svg;
|
|
232
|
+
}
|
|
233
|
+
|
|
1
234
|
// src/export.ts
|
|
2
235
|
function getSVGDimensions(svg) {
|
|
3
236
|
const w = parseFloat(svg.getAttribute("width") || "");
|
|
@@ -224,13 +457,351 @@ function exportCSV(data) {
|
|
|
224
457
|
const values = headers.map((h) => csvEscape(String(row[h] ?? "")));
|
|
225
458
|
rows.push(values.join(","));
|
|
226
459
|
}
|
|
227
|
-
return rows.join("\n");
|
|
228
|
-
}
|
|
229
|
-
function csvEscape(value) {
|
|
230
|
-
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
231
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
460
|
+
return rows.join("\n");
|
|
461
|
+
}
|
|
462
|
+
function csvEscape(value) {
|
|
463
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
464
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
465
|
+
}
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/measure-text.ts
|
|
470
|
+
function createMeasureText() {
|
|
471
|
+
let canvas = null;
|
|
472
|
+
let ctx = null;
|
|
473
|
+
return (text, fontSize, fontWeight) => {
|
|
474
|
+
if (!canvas) {
|
|
475
|
+
canvas = document.createElement("canvas");
|
|
476
|
+
ctx = canvas.getContext("2d");
|
|
477
|
+
}
|
|
478
|
+
if (!ctx) {
|
|
479
|
+
return { width: text.length * fontSize * 0.6, height: fontSize * 1.2 };
|
|
480
|
+
}
|
|
481
|
+
const weight = fontWeight ?? 400;
|
|
482
|
+
ctx.font = `${weight} ${fontSize}px Inter, sans-serif`;
|
|
483
|
+
const metrics = ctx.measureText(text);
|
|
484
|
+
return {
|
|
485
|
+
width: metrics.width,
|
|
486
|
+
height: fontSize * 1.2
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/resize-observer.ts
|
|
492
|
+
var DEBOUNCE_MS = 16;
|
|
493
|
+
function observeResize(container, callback) {
|
|
494
|
+
let timeoutId = null;
|
|
495
|
+
const observer = new ResizeObserver((entries) => {
|
|
496
|
+
if (timeoutId !== null) {
|
|
497
|
+
clearTimeout(timeoutId);
|
|
498
|
+
}
|
|
499
|
+
timeoutId = setTimeout(() => {
|
|
500
|
+
for (const entry of entries) {
|
|
501
|
+
const { width, height } = entry.contentRect;
|
|
502
|
+
callback(width, height);
|
|
503
|
+
}
|
|
504
|
+
timeoutId = null;
|
|
505
|
+
}, DEBOUNCE_MS);
|
|
506
|
+
});
|
|
507
|
+
observer.observe(container);
|
|
508
|
+
return () => {
|
|
509
|
+
if (timeoutId !== null) {
|
|
510
|
+
clearTimeout(timeoutId);
|
|
511
|
+
}
|
|
512
|
+
observer.disconnect();
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/tooltip.ts
|
|
517
|
+
import { computePosition, flip, offset, shift } from "@floating-ui/dom";
|
|
518
|
+
var TOOLTIP_OFFSET = 12;
|
|
519
|
+
function createTooltipManager(container) {
|
|
520
|
+
const tooltip = document.createElement("div");
|
|
521
|
+
tooltip.className = "oc-tooltip";
|
|
522
|
+
tooltip.setAttribute("role", "tooltip");
|
|
523
|
+
container.style.position = container.style.position || "relative";
|
|
524
|
+
container.appendChild(tooltip);
|
|
525
|
+
let lastContentKey = "";
|
|
526
|
+
let currentPositionId = 0;
|
|
527
|
+
const handleDocumentTouch = (e) => {
|
|
528
|
+
if (!container.contains(e.target)) {
|
|
529
|
+
hide();
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
document.addEventListener("touchstart", handleDocumentTouch);
|
|
533
|
+
function show(content, x3, y3) {
|
|
534
|
+
const contentKey = `${content.title}|${content.fields.length}|${content.fields[0]?.value}|${content.fields[content.fields.length - 1]?.value}`;
|
|
535
|
+
if (contentKey !== lastContentKey) {
|
|
536
|
+
lastContentKey = contentKey;
|
|
537
|
+
let html = "";
|
|
538
|
+
if (content.title) {
|
|
539
|
+
const titleColor = content.fields.find((f) => f.color)?.color;
|
|
540
|
+
html += '<div class="oc-tooltip-header">';
|
|
541
|
+
if (titleColor) {
|
|
542
|
+
html += `<span class="oc-tooltip-dot" style="background:${esc(titleColor)}"></span>`;
|
|
543
|
+
}
|
|
544
|
+
html += `<span class="oc-tooltip-title">${esc(content.title)}</span>`;
|
|
545
|
+
html += "</div>";
|
|
546
|
+
}
|
|
547
|
+
if (content.fields.length > 0) {
|
|
548
|
+
html += '<div class="oc-tooltip-body">';
|
|
549
|
+
for (const field of content.fields) {
|
|
550
|
+
html += '<div class="oc-tooltip-row">';
|
|
551
|
+
html += `<span class="oc-tooltip-label">${esc(field.label)}</span>`;
|
|
552
|
+
html += `<span class="oc-tooltip-value">${esc(field.value)}</span>`;
|
|
553
|
+
html += "</div>";
|
|
554
|
+
}
|
|
555
|
+
html += "</div>";
|
|
556
|
+
}
|
|
557
|
+
tooltip.innerHTML = html;
|
|
558
|
+
}
|
|
559
|
+
tooltip.style.display = "block";
|
|
560
|
+
const positionId = ++currentPositionId;
|
|
561
|
+
const virtualRef = {
|
|
562
|
+
getBoundingClientRect() {
|
|
563
|
+
const rect = container.getBoundingClientRect();
|
|
564
|
+
return {
|
|
565
|
+
x: rect.left + x3,
|
|
566
|
+
y: rect.top + y3,
|
|
567
|
+
width: 0,
|
|
568
|
+
height: 0,
|
|
569
|
+
top: rect.top + y3,
|
|
570
|
+
left: rect.left + x3,
|
|
571
|
+
right: rect.left + x3,
|
|
572
|
+
bottom: rect.top + y3
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
computePosition(virtualRef, tooltip, {
|
|
577
|
+
placement: "bottom-start",
|
|
578
|
+
middleware: [offset(TOOLTIP_OFFSET), flip(), shift({ padding: 5 })]
|
|
579
|
+
}).then(({ x: fx, y: fy }) => {
|
|
580
|
+
if (positionId !== currentPositionId) return;
|
|
581
|
+
tooltip.style.left = `${fx}px`;
|
|
582
|
+
tooltip.style.top = `${fy}px`;
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function hide() {
|
|
586
|
+
tooltip.style.display = "none";
|
|
587
|
+
lastContentKey = "";
|
|
588
|
+
}
|
|
589
|
+
function destroy() {
|
|
590
|
+
document.removeEventListener("touchstart", handleDocumentTouch);
|
|
591
|
+
if (tooltip.parentNode) {
|
|
592
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return { show, hide, destroy };
|
|
596
|
+
}
|
|
597
|
+
function esc(str) {
|
|
598
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/barlist-mount.ts
|
|
602
|
+
function resolveDarkMode(mode) {
|
|
603
|
+
if (mode === "force") return true;
|
|
604
|
+
if (mode === "off" || mode === void 0) return false;
|
|
605
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
606
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
function createBarList(container, spec, options) {
|
|
611
|
+
let currentSpec = spec;
|
|
612
|
+
let currentLayout;
|
|
613
|
+
let destroyed = false;
|
|
614
|
+
let svgElement = null;
|
|
615
|
+
let tooltipManager = null;
|
|
616
|
+
let cleanupTooltipEvents = null;
|
|
617
|
+
let disconnectResize = null;
|
|
618
|
+
let animationCleanup = null;
|
|
619
|
+
let pendingResize = false;
|
|
620
|
+
const measureText = createMeasureText();
|
|
621
|
+
function getContainerDimensions() {
|
|
622
|
+
const rect = container.getBoundingClientRect();
|
|
623
|
+
return {
|
|
624
|
+
width: Math.max(rect.width || 600, 100),
|
|
625
|
+
height: Math.max(rect.height || 400, 100)
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function compile() {
|
|
629
|
+
const { width, height } = getContainerDimensions();
|
|
630
|
+
const darkMode = resolveDarkMode(options?.darkMode);
|
|
631
|
+
const compileOpts = {
|
|
632
|
+
width,
|
|
633
|
+
height,
|
|
634
|
+
theme: options?.theme,
|
|
635
|
+
darkMode,
|
|
636
|
+
watermark: options?.watermark,
|
|
637
|
+
measureText
|
|
638
|
+
};
|
|
639
|
+
return compileBarList(currentSpec, compileOpts);
|
|
640
|
+
}
|
|
641
|
+
function wireTooltipAndInteraction(svg, layout) {
|
|
642
|
+
const cleanups = [];
|
|
643
|
+
const rowElements = svg.querySelectorAll(".oc-barlist-row");
|
|
644
|
+
for (const el of rowElements) {
|
|
645
|
+
const indexStr = el.getAttribute("data-row-index");
|
|
646
|
+
if (indexStr === null) continue;
|
|
647
|
+
const content = layout.tooltipDescriptors.get(indexStr);
|
|
648
|
+
const row = layout.rows[Number(indexStr)];
|
|
649
|
+
const handleMouseEnter = (e) => {
|
|
650
|
+
const mouseEvent = e;
|
|
651
|
+
if (content && tooltipManager && options?.tooltip !== false) {
|
|
652
|
+
const svgRect = svg.getBoundingClientRect();
|
|
653
|
+
const x3 = mouseEvent.clientX - svgRect.left;
|
|
654
|
+
const y3 = mouseEvent.clientY - svgRect.top;
|
|
655
|
+
tooltipManager.show(content, x3, y3);
|
|
656
|
+
}
|
|
657
|
+
if (row) {
|
|
658
|
+
options?.onRowHover?.({
|
|
659
|
+
label: row.label.text,
|
|
660
|
+
value: row.value,
|
|
661
|
+
data: row.data
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const handleMouseMove = (e) => {
|
|
666
|
+
if (content && tooltipManager && options?.tooltip !== false) {
|
|
667
|
+
const mouseEvent = e;
|
|
668
|
+
const svgRect = svg.getBoundingClientRect();
|
|
669
|
+
const x3 = mouseEvent.clientX - svgRect.left;
|
|
670
|
+
const y3 = mouseEvent.clientY - svgRect.top;
|
|
671
|
+
tooltipManager.show(content, x3, y3);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const handleMouseLeave = () => {
|
|
675
|
+
tooltipManager?.hide();
|
|
676
|
+
options?.onRowHover?.(null);
|
|
677
|
+
};
|
|
678
|
+
const handleClick = () => {
|
|
679
|
+
if (row) {
|
|
680
|
+
options?.onRowClick?.({
|
|
681
|
+
label: row.label.text,
|
|
682
|
+
value: row.value,
|
|
683
|
+
data: row.data
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
el.addEventListener("mouseenter", handleMouseEnter);
|
|
688
|
+
el.addEventListener("mousemove", handleMouseMove);
|
|
689
|
+
el.addEventListener("mouseleave", handleMouseLeave);
|
|
690
|
+
el.addEventListener("click", handleClick);
|
|
691
|
+
cleanups.push(() => {
|
|
692
|
+
el.removeEventListener("mouseenter", handleMouseEnter);
|
|
693
|
+
el.removeEventListener("mousemove", handleMouseMove);
|
|
694
|
+
el.removeEventListener("mouseleave", handleMouseLeave);
|
|
695
|
+
el.removeEventListener("click", handleClick);
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
return () => {
|
|
699
|
+
for (const cleanup of cleanups) cleanup();
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function render(animate = false) {
|
|
703
|
+
if (animationCleanup) {
|
|
704
|
+
animationCleanup();
|
|
705
|
+
animationCleanup = null;
|
|
706
|
+
}
|
|
707
|
+
if (svgElement) {
|
|
708
|
+
if (cleanupTooltipEvents) {
|
|
709
|
+
cleanupTooltipEvents();
|
|
710
|
+
cleanupTooltipEvents = null;
|
|
711
|
+
}
|
|
712
|
+
svgElement.remove();
|
|
713
|
+
}
|
|
714
|
+
const newSvg = renderBarListSVG(currentLayout, { animate });
|
|
715
|
+
container.appendChild(newSvg);
|
|
716
|
+
svgElement = newSvg;
|
|
717
|
+
cleanupTooltipEvents = wireTooltipAndInteraction(newSvg, currentLayout);
|
|
718
|
+
if (options?.tooltip !== false) {
|
|
719
|
+
if (!tooltipManager) {
|
|
720
|
+
tooltipManager = createTooltipManager(container);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (currentLayout.animation?.enabled) {
|
|
724
|
+
animationCleanup = setupAnimationCleanup(newSvg, () => {
|
|
725
|
+
if (pendingResize && !destroyed) {
|
|
726
|
+
pendingResize = false;
|
|
727
|
+
resize();
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function update(newSpec) {
|
|
733
|
+
currentSpec = newSpec;
|
|
734
|
+
currentLayout = compile();
|
|
735
|
+
render();
|
|
736
|
+
}
|
|
737
|
+
function resize() {
|
|
738
|
+
if (destroyed) return;
|
|
739
|
+
if (animationCleanup) {
|
|
740
|
+
pendingResize = true;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
currentLayout = compile();
|
|
744
|
+
render();
|
|
745
|
+
}
|
|
746
|
+
function exportChart(format, options_) {
|
|
747
|
+
if (!svgElement) return "";
|
|
748
|
+
switch (format) {
|
|
749
|
+
case "svg":
|
|
750
|
+
return exportSVG(svgElement);
|
|
751
|
+
case "svg-with-fonts":
|
|
752
|
+
return exportSVGWithFonts(svgElement);
|
|
753
|
+
case "png":
|
|
754
|
+
return exportPNG(svgElement, options_);
|
|
755
|
+
case "jpg":
|
|
756
|
+
return exportJPG(svgElement, options_);
|
|
757
|
+
default:
|
|
758
|
+
return "";
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function destroy() {
|
|
762
|
+
if (destroyed) return;
|
|
763
|
+
destroyed = true;
|
|
764
|
+
if (animationCleanup) {
|
|
765
|
+
cancelAnimations(svgElement);
|
|
766
|
+
animationCleanup();
|
|
767
|
+
animationCleanup = null;
|
|
768
|
+
}
|
|
769
|
+
if (cleanupTooltipEvents) {
|
|
770
|
+
cleanupTooltipEvents();
|
|
771
|
+
cleanupTooltipEvents = null;
|
|
772
|
+
}
|
|
773
|
+
if (svgElement) {
|
|
774
|
+
svgElement.remove();
|
|
775
|
+
svgElement = null;
|
|
776
|
+
}
|
|
777
|
+
if (tooltipManager) {
|
|
778
|
+
tooltipManager.destroy();
|
|
779
|
+
tooltipManager = null;
|
|
780
|
+
}
|
|
781
|
+
if (disconnectResize) {
|
|
782
|
+
disconnectResize();
|
|
783
|
+
disconnectResize = null;
|
|
784
|
+
}
|
|
785
|
+
container.classList.remove("oc-barlist-root", "oc-dark");
|
|
786
|
+
}
|
|
787
|
+
container.classList.add("oc-barlist-root");
|
|
788
|
+
if (resolveDarkMode(options?.darkMode)) {
|
|
789
|
+
container.classList.add("oc-dark");
|
|
232
790
|
}
|
|
233
|
-
|
|
791
|
+
currentLayout = compile();
|
|
792
|
+
render(true);
|
|
793
|
+
if (options?.responsive !== false) {
|
|
794
|
+
disconnectResize = observeResize(container, () => resize());
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
update,
|
|
798
|
+
resize,
|
|
799
|
+
export: exportChart,
|
|
800
|
+
destroy,
|
|
801
|
+
get layout() {
|
|
802
|
+
return currentLayout;
|
|
803
|
+
}
|
|
804
|
+
};
|
|
234
805
|
}
|
|
235
806
|
|
|
236
807
|
// src/graph/simulation-worker-url.ts
|
|
@@ -2547,118 +3118,8 @@ var SpatialIndex = class {
|
|
|
2547
3118
|
}
|
|
2548
3119
|
};
|
|
2549
3120
|
|
|
2550
|
-
// src/resize-observer.ts
|
|
2551
|
-
var DEBOUNCE_MS = 16;
|
|
2552
|
-
function observeResize(container, callback) {
|
|
2553
|
-
let timeoutId = null;
|
|
2554
|
-
const observer = new ResizeObserver((entries) => {
|
|
2555
|
-
if (timeoutId !== null) {
|
|
2556
|
-
clearTimeout(timeoutId);
|
|
2557
|
-
}
|
|
2558
|
-
timeoutId = setTimeout(() => {
|
|
2559
|
-
for (const entry of entries) {
|
|
2560
|
-
const { width, height } = entry.contentRect;
|
|
2561
|
-
callback(width, height);
|
|
2562
|
-
}
|
|
2563
|
-
timeoutId = null;
|
|
2564
|
-
}, DEBOUNCE_MS);
|
|
2565
|
-
});
|
|
2566
|
-
observer.observe(container);
|
|
2567
|
-
return () => {
|
|
2568
|
-
if (timeoutId !== null) {
|
|
2569
|
-
clearTimeout(timeoutId);
|
|
2570
|
-
}
|
|
2571
|
-
observer.disconnect();
|
|
2572
|
-
};
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
// src/tooltip.ts
|
|
2576
|
-
import { computePosition, flip, offset, shift } from "@floating-ui/dom";
|
|
2577
|
-
var TOOLTIP_OFFSET = 12;
|
|
2578
|
-
function createTooltipManager(container) {
|
|
2579
|
-
const tooltip = document.createElement("div");
|
|
2580
|
-
tooltip.className = "oc-tooltip";
|
|
2581
|
-
tooltip.setAttribute("role", "tooltip");
|
|
2582
|
-
container.style.position = container.style.position || "relative";
|
|
2583
|
-
container.appendChild(tooltip);
|
|
2584
|
-
let lastContentKey = "";
|
|
2585
|
-
let currentPositionId = 0;
|
|
2586
|
-
const handleDocumentTouch = (e) => {
|
|
2587
|
-
if (!container.contains(e.target)) {
|
|
2588
|
-
hide();
|
|
2589
|
-
}
|
|
2590
|
-
};
|
|
2591
|
-
document.addEventListener("touchstart", handleDocumentTouch);
|
|
2592
|
-
function show(content, x3, y3) {
|
|
2593
|
-
const contentKey = `${content.title}|${content.fields.length}|${content.fields[0]?.value}|${content.fields[content.fields.length - 1]?.value}`;
|
|
2594
|
-
if (contentKey !== lastContentKey) {
|
|
2595
|
-
lastContentKey = contentKey;
|
|
2596
|
-
let html = "";
|
|
2597
|
-
if (content.title) {
|
|
2598
|
-
const titleColor = content.fields.find((f) => f.color)?.color;
|
|
2599
|
-
html += '<div class="oc-tooltip-header">';
|
|
2600
|
-
if (titleColor) {
|
|
2601
|
-
html += `<span class="oc-tooltip-dot" style="background:${esc(titleColor)}"></span>`;
|
|
2602
|
-
}
|
|
2603
|
-
html += `<span class="oc-tooltip-title">${esc(content.title)}</span>`;
|
|
2604
|
-
html += "</div>";
|
|
2605
|
-
}
|
|
2606
|
-
if (content.fields.length > 0) {
|
|
2607
|
-
html += '<div class="oc-tooltip-body">';
|
|
2608
|
-
for (const field of content.fields) {
|
|
2609
|
-
html += '<div class="oc-tooltip-row">';
|
|
2610
|
-
html += `<span class="oc-tooltip-label">${esc(field.label)}</span>`;
|
|
2611
|
-
html += `<span class="oc-tooltip-value">${esc(field.value)}</span>`;
|
|
2612
|
-
html += "</div>";
|
|
2613
|
-
}
|
|
2614
|
-
html += "</div>";
|
|
2615
|
-
}
|
|
2616
|
-
tooltip.innerHTML = html;
|
|
2617
|
-
}
|
|
2618
|
-
tooltip.style.display = "block";
|
|
2619
|
-
const positionId = ++currentPositionId;
|
|
2620
|
-
const virtualRef = {
|
|
2621
|
-
getBoundingClientRect() {
|
|
2622
|
-
const rect = container.getBoundingClientRect();
|
|
2623
|
-
return {
|
|
2624
|
-
x: rect.left + x3,
|
|
2625
|
-
y: rect.top + y3,
|
|
2626
|
-
width: 0,
|
|
2627
|
-
height: 0,
|
|
2628
|
-
top: rect.top + y3,
|
|
2629
|
-
left: rect.left + x3,
|
|
2630
|
-
right: rect.left + x3,
|
|
2631
|
-
bottom: rect.top + y3
|
|
2632
|
-
};
|
|
2633
|
-
}
|
|
2634
|
-
};
|
|
2635
|
-
computePosition(virtualRef, tooltip, {
|
|
2636
|
-
placement: "bottom-start",
|
|
2637
|
-
middleware: [offset(TOOLTIP_OFFSET), flip(), shift({ padding: 5 })]
|
|
2638
|
-
}).then(({ x: fx, y: fy }) => {
|
|
2639
|
-
if (positionId !== currentPositionId) return;
|
|
2640
|
-
tooltip.style.left = `${fx}px`;
|
|
2641
|
-
tooltip.style.top = `${fy}px`;
|
|
2642
|
-
});
|
|
2643
|
-
}
|
|
2644
|
-
function hide() {
|
|
2645
|
-
tooltip.style.display = "none";
|
|
2646
|
-
lastContentKey = "";
|
|
2647
|
-
}
|
|
2648
|
-
function destroy() {
|
|
2649
|
-
document.removeEventListener("touchstart", handleDocumentTouch);
|
|
2650
|
-
if (tooltip.parentNode) {
|
|
2651
|
-
tooltip.parentNode.removeChild(tooltip);
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
return { show, hide, destroy };
|
|
2655
|
-
}
|
|
2656
|
-
function esc(str) {
|
|
2657
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
3121
|
// src/graph-mount.ts
|
|
2661
|
-
function
|
|
3122
|
+
function resolveDarkMode2(mode) {
|
|
2662
3123
|
if (mode === "force") return true;
|
|
2663
3124
|
if (mode === "off" || mode === void 0) return false;
|
|
2664
3125
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
@@ -2714,7 +3175,7 @@ function createGraph(container, spec, options) {
|
|
|
2714
3175
|
}
|
|
2715
3176
|
function compile() {
|
|
2716
3177
|
const { width, height } = getContainerDimensions();
|
|
2717
|
-
const darkMode =
|
|
3178
|
+
const darkMode = resolveDarkMode2(options?.darkMode);
|
|
2718
3179
|
const compileOpts = {
|
|
2719
3180
|
width,
|
|
2720
3181
|
height,
|
|
@@ -2786,7 +3247,7 @@ function createGraph(container, spec, options) {
|
|
|
2786
3247
|
}
|
|
2787
3248
|
function createDOM() {
|
|
2788
3249
|
const { width, height } = getContainerDimensions();
|
|
2789
|
-
const isDark =
|
|
3250
|
+
const isDark = resolveDarkMode2(options?.darkMode);
|
|
2790
3251
|
wrapper = document.createElement("div");
|
|
2791
3252
|
wrapper.className = isDark ? "oc-graph-wrapper oc-dark" : "oc-graph-wrapper";
|
|
2792
3253
|
if (isDark) {
|
|
@@ -2805,7 +3266,7 @@ function createGraph(container, spec, options) {
|
|
|
2805
3266
|
}
|
|
2806
3267
|
chromeEl = document.createElement("div");
|
|
2807
3268
|
chromeEl.className = "oc-graph-chrome";
|
|
2808
|
-
|
|
3269
|
+
renderChrome5();
|
|
2809
3270
|
wrapper.appendChild(chromeEl);
|
|
2810
3271
|
canvas = document.createElement("canvas");
|
|
2811
3272
|
canvas.className = "oc-graph-canvas";
|
|
@@ -2825,7 +3286,7 @@ function createGraph(container, spec, options) {
|
|
|
2825
3286
|
renderer = new GraphCanvasRenderer(canvas);
|
|
2826
3287
|
renderer.resize(width, canvasHeight);
|
|
2827
3288
|
}
|
|
2828
|
-
function
|
|
3289
|
+
function renderChrome5() {
|
|
2829
3290
|
if (!chromeEl) return;
|
|
2830
3291
|
let html = "";
|
|
2831
3292
|
if (compilation.chrome.title) {
|
|
@@ -3139,7 +3600,7 @@ function createGraph(container, spec, options) {
|
|
|
3139
3600
|
compilation = compile();
|
|
3140
3601
|
adjacencyMap = buildAdjacencyMap(compilation.edges);
|
|
3141
3602
|
buildDataMaps();
|
|
3142
|
-
|
|
3603
|
+
renderChrome5();
|
|
3143
3604
|
renderLegend3();
|
|
3144
3605
|
initSimulation();
|
|
3145
3606
|
initInteraction();
|
|
@@ -3174,7 +3635,7 @@ function createGraph(container, spec, options) {
|
|
|
3174
3635
|
};
|
|
3175
3636
|
});
|
|
3176
3637
|
spatialIndex.rebuild(positionedNodes);
|
|
3177
|
-
|
|
3638
|
+
renderChrome5();
|
|
3178
3639
|
renderLegend3();
|
|
3179
3640
|
needsRender = true;
|
|
3180
3641
|
scheduleRender();
|
|
@@ -3217,123 +3678,62 @@ function createGraph(container, spec, options) {
|
|
|
3217
3678
|
renderer = null;
|
|
3218
3679
|
container.classList.remove("oc-dark");
|
|
3219
3680
|
}
|
|
3220
|
-
try {
|
|
3221
|
-
compilation = compile();
|
|
3222
|
-
adjacencyMap = buildAdjacencyMap(compilation.edges);
|
|
3223
|
-
buildDataMaps();
|
|
3224
|
-
createDOM();
|
|
3225
|
-
initSimulation();
|
|
3226
|
-
initInteraction();
|
|
3227
|
-
} catch (err) {
|
|
3228
|
-
console.error("[viz] Graph mount failed:", err);
|
|
3229
|
-
return {
|
|
3230
|
-
update() {
|
|
3231
|
-
},
|
|
3232
|
-
updateVisuals() {
|
|
3233
|
-
},
|
|
3234
|
-
search() {
|
|
3235
|
-
},
|
|
3236
|
-
clearSearch() {
|
|
3237
|
-
},
|
|
3238
|
-
zoomToFit() {
|
|
3239
|
-
},
|
|
3240
|
-
zoomToNode() {
|
|
3241
|
-
},
|
|
3242
|
-
selectNode() {
|
|
3243
|
-
},
|
|
3244
|
-
getSelectedNodes: () => [],
|
|
3245
|
-
resize() {
|
|
3246
|
-
},
|
|
3247
|
-
destroy() {
|
|
3248
|
-
}
|
|
3249
|
-
};
|
|
3250
|
-
}
|
|
3251
|
-
if (options?.responsive !== false) {
|
|
3252
|
-
disconnectResize = observeResize(container, () => {
|
|
3253
|
-
doResize();
|
|
3254
|
-
});
|
|
3255
|
-
}
|
|
3256
|
-
return {
|
|
3257
|
-
update,
|
|
3258
|
-
updateVisuals,
|
|
3259
|
-
search,
|
|
3260
|
-
clearSearch,
|
|
3261
|
-
zoomToFit,
|
|
3262
|
-
zoomToNode,
|
|
3263
|
-
selectNode,
|
|
3264
|
-
getSelectedNodes,
|
|
3265
|
-
resize: doResize,
|
|
3266
|
-
destroy
|
|
3267
|
-
};
|
|
3268
|
-
}
|
|
3269
|
-
function escapeHtml(str) {
|
|
3270
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3271
|
-
}
|
|
3272
|
-
|
|
3273
|
-
// src/mount.ts
|
|
3274
|
-
import { elementRef, getRepresentativeColor, isLayerSpec } from "@opendata-ai/openchart-core";
|
|
3275
|
-
import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
|
|
3276
|
-
|
|
3277
|
-
// src/animation.ts
|
|
3278
|
-
function cancelAnimations(svg) {
|
|
3279
|
-
if (svg) {
|
|
3280
|
-
svg.classList.remove("oc-animate");
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
function setupAnimationCleanup(svg, onComplete) {
|
|
3284
|
-
const style = svg.style;
|
|
3285
|
-
const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 600;
|
|
3286
|
-
const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
|
|
3287
|
-
const annotationDelay = parseFloat(style.getPropertyValue("--oc-annotation-delay")) || 200;
|
|
3288
|
-
const animatedElements = svg.querySelectorAll("[data-animation-index]").length;
|
|
3289
|
-
const totalStagger = stagger * Math.max(0, animatedElements - 1);
|
|
3290
|
-
const totalTime = totalStagger + duration + annotationDelay + 500;
|
|
3291
|
-
const timer2 = setTimeout(() => {
|
|
3292
|
-
svg.classList.remove("oc-animate");
|
|
3293
|
-
onComplete?.();
|
|
3294
|
-
}, totalTime);
|
|
3295
|
-
return () => {
|
|
3296
|
-
clearTimeout(timer2);
|
|
3297
|
-
cancelAnimations(svg);
|
|
3298
|
-
};
|
|
3299
|
-
}
|
|
3300
|
-
function setupTableAnimationCleanup(wrapper) {
|
|
3301
|
-
const style = wrapper.style;
|
|
3302
|
-
const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 500;
|
|
3303
|
-
const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
|
|
3304
|
-
const rows = wrapper.querySelectorAll("tbody tr").length;
|
|
3305
|
-
const totalStagger = stagger * Math.max(0, rows - 1);
|
|
3306
|
-
const totalTime = totalStagger + duration + 300;
|
|
3307
|
-
const timer2 = setTimeout(() => {
|
|
3308
|
-
wrapper.classList.remove("oc-animate");
|
|
3309
|
-
}, totalTime);
|
|
3310
|
-
return () => {
|
|
3311
|
-
clearTimeout(timer2);
|
|
3312
|
-
wrapper.classList.remove("oc-animate");
|
|
3313
|
-
};
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
// src/measure-text.ts
|
|
3317
|
-
function createMeasureText() {
|
|
3318
|
-
let canvas = null;
|
|
3319
|
-
let ctx = null;
|
|
3320
|
-
return (text, fontSize, fontWeight) => {
|
|
3321
|
-
if (!canvas) {
|
|
3322
|
-
canvas = document.createElement("canvas");
|
|
3323
|
-
ctx = canvas.getContext("2d");
|
|
3324
|
-
}
|
|
3325
|
-
if (!ctx) {
|
|
3326
|
-
return { width: text.length * fontSize * 0.6, height: fontSize * 1.2 };
|
|
3327
|
-
}
|
|
3328
|
-
const weight = fontWeight ?? 400;
|
|
3329
|
-
ctx.font = `${weight} ${fontSize}px Inter, sans-serif`;
|
|
3330
|
-
const metrics = ctx.measureText(text);
|
|
3681
|
+
try {
|
|
3682
|
+
compilation = compile();
|
|
3683
|
+
adjacencyMap = buildAdjacencyMap(compilation.edges);
|
|
3684
|
+
buildDataMaps();
|
|
3685
|
+
createDOM();
|
|
3686
|
+
initSimulation();
|
|
3687
|
+
initInteraction();
|
|
3688
|
+
} catch (err) {
|
|
3689
|
+
console.error("[viz] Graph mount failed:", err);
|
|
3331
3690
|
return {
|
|
3332
|
-
|
|
3333
|
-
|
|
3691
|
+
update() {
|
|
3692
|
+
},
|
|
3693
|
+
updateVisuals() {
|
|
3694
|
+
},
|
|
3695
|
+
search() {
|
|
3696
|
+
},
|
|
3697
|
+
clearSearch() {
|
|
3698
|
+
},
|
|
3699
|
+
zoomToFit() {
|
|
3700
|
+
},
|
|
3701
|
+
zoomToNode() {
|
|
3702
|
+
},
|
|
3703
|
+
selectNode() {
|
|
3704
|
+
},
|
|
3705
|
+
getSelectedNodes: () => [],
|
|
3706
|
+
resize() {
|
|
3707
|
+
},
|
|
3708
|
+
destroy() {
|
|
3709
|
+
}
|
|
3334
3710
|
};
|
|
3711
|
+
}
|
|
3712
|
+
if (options?.responsive !== false) {
|
|
3713
|
+
disconnectResize = observeResize(container, () => {
|
|
3714
|
+
doResize();
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
return {
|
|
3718
|
+
update,
|
|
3719
|
+
updateVisuals,
|
|
3720
|
+
search,
|
|
3721
|
+
clearSearch,
|
|
3722
|
+
zoomToFit,
|
|
3723
|
+
zoomToNode,
|
|
3724
|
+
selectNode,
|
|
3725
|
+
getSelectedNodes,
|
|
3726
|
+
resize: doResize,
|
|
3727
|
+
destroy
|
|
3335
3728
|
};
|
|
3336
3729
|
}
|
|
3730
|
+
function escapeHtml(str) {
|
|
3731
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
// src/mount.ts
|
|
3735
|
+
import { elementRef, getRepresentativeColor, isLayerSpec } from "@opendata-ai/openchart-core";
|
|
3736
|
+
import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
|
|
3337
3737
|
|
|
3338
3738
|
// src/svg-renderer.ts
|
|
3339
3739
|
import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
|
|
@@ -3348,7 +3748,7 @@ function nextSvgId(prefix) {
|
|
|
3348
3748
|
}
|
|
3349
3749
|
|
|
3350
3750
|
// src/gradient-utils.ts
|
|
3351
|
-
var
|
|
3751
|
+
var SVG_NS2 = "http://www.w3.org/2000/svg";
|
|
3352
3752
|
function gradientKey(def) {
|
|
3353
3753
|
return sortedStringify(def);
|
|
3354
3754
|
}
|
|
@@ -3368,7 +3768,7 @@ function createGradientElement(def, id) {
|
|
|
3368
3768
|
return createRadialGradient(def, id);
|
|
3369
3769
|
}
|
|
3370
3770
|
function createLinearGradient(def, id) {
|
|
3371
|
-
const el = document.createElementNS(
|
|
3771
|
+
const el = document.createElementNS(SVG_NS2, "linearGradient");
|
|
3372
3772
|
el.setAttribute("id", id);
|
|
3373
3773
|
el.setAttribute("gradientUnits", "objectBoundingBox");
|
|
3374
3774
|
el.setAttribute("x1", String(def.x1 ?? 0));
|
|
@@ -3381,7 +3781,7 @@ function createLinearGradient(def, id) {
|
|
|
3381
3781
|
return el;
|
|
3382
3782
|
}
|
|
3383
3783
|
function createRadialGradient(def, id) {
|
|
3384
|
-
const el = document.createElementNS(
|
|
3784
|
+
const el = document.createElementNS(SVG_NS2, "radialGradient");
|
|
3385
3785
|
el.setAttribute("id", id);
|
|
3386
3786
|
el.setAttribute("gradientUnits", "objectBoundingBox");
|
|
3387
3787
|
el.setAttribute("cx", String(def.x2 ?? 0.5));
|
|
@@ -3396,7 +3796,7 @@ function createRadialGradient(def, id) {
|
|
|
3396
3796
|
return el;
|
|
3397
3797
|
}
|
|
3398
3798
|
function appendStop(parent, stop) {
|
|
3399
|
-
const stopEl = document.createElementNS(
|
|
3799
|
+
const stopEl = document.createElementNS(SVG_NS2, "stop");
|
|
3400
3800
|
stopEl.setAttribute("offset", String(stop.offset));
|
|
3401
3801
|
stopEl.setAttribute("stop-color", stop.color);
|
|
3402
3802
|
if (stop.opacity !== void 0) {
|
|
@@ -3429,12 +3829,12 @@ function resolveMarkFill(fill, gradientMap) {
|
|
|
3429
3829
|
|
|
3430
3830
|
// src/renderers/svg-dom.ts
|
|
3431
3831
|
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
3432
|
-
var
|
|
3433
|
-
var
|
|
3434
|
-
function
|
|
3435
|
-
return document.createElementNS(
|
|
3832
|
+
var SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
3833
|
+
var XLINK_NS2 = "http://www.w3.org/1999/xlink";
|
|
3834
|
+
function createSVGElement2(tag) {
|
|
3835
|
+
return document.createElementNS(SVG_NS3, tag);
|
|
3436
3836
|
}
|
|
3437
|
-
function
|
|
3837
|
+
function setAttrs2(el, attrs) {
|
|
3438
3838
|
for (const [key, value] of Object.entries(attrs)) {
|
|
3439
3839
|
el.setAttribute(key, String(value));
|
|
3440
3840
|
}
|
|
@@ -3493,9 +3893,9 @@ function renderCurvedArrow(parent, from, to, stroke) {
|
|
|
3493
3893
|
const uy = ty / tLen;
|
|
3494
3894
|
const baseX = to.x - ux * arrowLen;
|
|
3495
3895
|
const baseY = tipY - uy * arrowLen;
|
|
3496
|
-
const path =
|
|
3896
|
+
const path = createSVGElement2("path");
|
|
3497
3897
|
path.setAttribute("class", "oc-annotation-connector");
|
|
3498
|
-
|
|
3898
|
+
setAttrs2(path, {
|
|
3499
3899
|
d: `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${baseX} ${baseY}`,
|
|
3500
3900
|
fill: "none",
|
|
3501
3901
|
stroke,
|
|
@@ -3504,9 +3904,9 @@ function renderCurvedArrow(parent, from, to, stroke) {
|
|
|
3504
3904
|
parent.appendChild(path);
|
|
3505
3905
|
const px = -uy;
|
|
3506
3906
|
const py = ux;
|
|
3507
|
-
const arrow =
|
|
3907
|
+
const arrow = createSVGElement2("polygon");
|
|
3508
3908
|
arrow.setAttribute("class", "oc-annotation-connector");
|
|
3509
|
-
|
|
3909
|
+
setAttrs2(arrow, {
|
|
3510
3910
|
points: [
|
|
3511
3911
|
`${to.x},${tipY}`,
|
|
3512
3912
|
`${baseX + px * arrowWidth},${baseY + py * arrowWidth}`,
|
|
@@ -3517,16 +3917,16 @@ function renderCurvedArrow(parent, from, to, stroke) {
|
|
|
3517
3917
|
parent.appendChild(arrow);
|
|
3518
3918
|
}
|
|
3519
3919
|
function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
3520
|
-
const g =
|
|
3920
|
+
const g = createSVGElement2("g");
|
|
3521
3921
|
g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
|
|
3522
3922
|
g.setAttribute("data-annotation-index", String(index2));
|
|
3523
3923
|
if (annotation.id) {
|
|
3524
3924
|
g.setAttribute("data-annotation-id", annotation.id);
|
|
3525
3925
|
}
|
|
3526
3926
|
if (annotation.rect) {
|
|
3527
|
-
const rect =
|
|
3927
|
+
const rect = createSVGElement2("rect");
|
|
3528
3928
|
rect.setAttribute("class", "oc-annotation-range");
|
|
3529
|
-
|
|
3929
|
+
setAttrs2(rect, {
|
|
3530
3930
|
x: annotation.rect.x,
|
|
3531
3931
|
y: annotation.rect.y,
|
|
3532
3932
|
width: annotation.rect.width,
|
|
@@ -3539,9 +3939,9 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
|
3539
3939
|
g.appendChild(rect);
|
|
3540
3940
|
}
|
|
3541
3941
|
if (annotation.line) {
|
|
3542
|
-
const line =
|
|
3942
|
+
const line = createSVGElement2("line");
|
|
3543
3943
|
line.setAttribute("class", "oc-annotation-line");
|
|
3544
|
-
|
|
3944
|
+
setAttrs2(line, {
|
|
3545
3945
|
x1: annotation.line.start.x,
|
|
3546
3946
|
y1: annotation.line.start.y,
|
|
3547
3947
|
x2: annotation.line.end.x,
|
|
@@ -3560,9 +3960,9 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
|
3560
3960
|
if (c2.style === "curve") {
|
|
3561
3961
|
renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
|
|
3562
3962
|
} else {
|
|
3563
|
-
const connector =
|
|
3963
|
+
const connector = createSVGElement2("line");
|
|
3564
3964
|
connector.setAttribute("class", "oc-annotation-connector");
|
|
3565
|
-
|
|
3965
|
+
setAttrs2(connector, {
|
|
3566
3966
|
x1: c2.from.x,
|
|
3567
3967
|
y1: c2.from.y,
|
|
3568
3968
|
x2: c2.to.x,
|
|
@@ -3574,9 +3974,9 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
|
3574
3974
|
g.appendChild(connector);
|
|
3575
3975
|
}
|
|
3576
3976
|
}
|
|
3577
|
-
const text =
|
|
3977
|
+
const text = createSVGElement2("text");
|
|
3578
3978
|
text.setAttribute("class", "oc-annotation-label");
|
|
3579
|
-
|
|
3979
|
+
setAttrs2(text, { x: annotation.label.x, y: annotation.label.y });
|
|
3580
3980
|
applyTextStyle(text, annotation.label.style);
|
|
3581
3981
|
const lines = annotation.label.text.split("\n");
|
|
3582
3982
|
const fontSize = annotation.label.style.fontSize ?? 12;
|
|
@@ -3585,8 +3985,8 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
|
3585
3985
|
if (isMultiLine) {
|
|
3586
3986
|
text.setAttribute("text-anchor", "middle");
|
|
3587
3987
|
for (let i = 0; i < lines.length; i++) {
|
|
3588
|
-
const tspan =
|
|
3589
|
-
|
|
3988
|
+
const tspan = createSVGElement2("tspan");
|
|
3989
|
+
setAttrs2(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
|
|
3590
3990
|
tspan.textContent = lines[i];
|
|
3591
3991
|
text.appendChild(tspan);
|
|
3592
3992
|
}
|
|
@@ -3599,9 +3999,9 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
|
3599
3999
|
const totalHeight = lines.length * lineHeight;
|
|
3600
4000
|
const pad = 3;
|
|
3601
4001
|
const bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
|
|
3602
|
-
const bgRect =
|
|
4002
|
+
const bgRect = createSVGElement2("rect");
|
|
3603
4003
|
bgRect.setAttribute("class", "oc-annotation-bg");
|
|
3604
|
-
|
|
4004
|
+
setAttrs2(bgRect, {
|
|
3605
4005
|
x: bgX,
|
|
3606
4006
|
y: annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad,
|
|
3607
4007
|
width: maxLineWidth + pad * 2,
|
|
@@ -3622,7 +4022,7 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
|
3622
4022
|
}
|
|
3623
4023
|
function renderAnnotations(parent, layout) {
|
|
3624
4024
|
if (layout.annotations.length === 0) return;
|
|
3625
|
-
const g =
|
|
4025
|
+
const g = createSVGElement2("g");
|
|
3626
4026
|
g.setAttribute("class", "oc-annotations");
|
|
3627
4027
|
const bgColor = layout.theme.colors.background;
|
|
3628
4028
|
for (let i = 0; i < layout.annotations.length; i++) {
|
|
@@ -3638,11 +4038,11 @@ import {
|
|
|
3638
4038
|
TICK_LABEL_OFFSET
|
|
3639
4039
|
} from "@opendata-ai/openchart-core";
|
|
3640
4040
|
function appendCompoundLabel(parent, primaryText, subtitle, fontWeight) {
|
|
3641
|
-
const primarySpan =
|
|
4041
|
+
const primarySpan = createSVGElement2("tspan");
|
|
3642
4042
|
primarySpan.setAttribute("font-weight", String(fontWeight));
|
|
3643
4043
|
primarySpan.textContent = primaryText;
|
|
3644
4044
|
parent.appendChild(primarySpan);
|
|
3645
|
-
const subtitleSpan =
|
|
4045
|
+
const subtitleSpan = createSVGElement2("tspan");
|
|
3646
4046
|
subtitleSpan.setAttribute("dx", "0.5em");
|
|
3647
4047
|
subtitleSpan.textContent = subtitle;
|
|
3648
4048
|
subtitleSpan.setAttribute("font-weight", "400");
|
|
@@ -3650,14 +4050,14 @@ function appendCompoundLabel(parent, primaryText, subtitle, fontWeight) {
|
|
|
3650
4050
|
parent.appendChild(subtitleSpan);
|
|
3651
4051
|
}
|
|
3652
4052
|
function renderAxis(parent, axis, orientation, layout) {
|
|
3653
|
-
const g =
|
|
4053
|
+
const g = createSVGElement2("g");
|
|
3654
4054
|
const isRight = orientation === "y" && axis.orient === "right";
|
|
3655
4055
|
g.setAttribute("class", `oc-axis oc-axis-${isRight ? "y2" : orientation}`);
|
|
3656
4056
|
const { area } = layout;
|
|
3657
|
-
if (orientation === "x") {
|
|
3658
|
-
const line =
|
|
4057
|
+
if (orientation === "x" && axis.domainLine !== false) {
|
|
4058
|
+
const line = createSVGElement2("line");
|
|
3659
4059
|
line.setAttribute("class", "oc-axis-line");
|
|
3660
|
-
|
|
4060
|
+
setAttrs2(line, {
|
|
3661
4061
|
x1: axis.start.x,
|
|
3662
4062
|
y1: axis.start.y,
|
|
3663
4063
|
x2: axis.end.x,
|
|
@@ -3669,12 +4069,12 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3669
4069
|
}
|
|
3670
4070
|
for (const tick of axis.ticks) {
|
|
3671
4071
|
if (orientation === "x") {
|
|
3672
|
-
const label =
|
|
4072
|
+
const label = createSVGElement2("text");
|
|
3673
4073
|
label.setAttribute("class", "oc-axis-tick");
|
|
3674
4074
|
if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
|
|
3675
4075
|
const labelX = tick.position;
|
|
3676
4076
|
const labelY = area.y + area.height + 6;
|
|
3677
|
-
|
|
4077
|
+
setAttrs2(label, {
|
|
3678
4078
|
x: labelX,
|
|
3679
4079
|
y: labelY,
|
|
3680
4080
|
"text-anchor": axis.tickAngle < 0 ? "end" : "start",
|
|
@@ -3682,7 +4082,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3682
4082
|
transform: `rotate(${axis.tickAngle}, ${labelX}, ${labelY})`
|
|
3683
4083
|
});
|
|
3684
4084
|
} else {
|
|
3685
|
-
|
|
4085
|
+
setAttrs2(label, {
|
|
3686
4086
|
x: tick.position,
|
|
3687
4087
|
y: area.y + area.height + 14,
|
|
3688
4088
|
"text-anchor": "middle"
|
|
@@ -3692,9 +4092,9 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3692
4092
|
label.textContent = tick.label;
|
|
3693
4093
|
g.appendChild(label);
|
|
3694
4094
|
} else {
|
|
3695
|
-
const label =
|
|
4095
|
+
const label = createSVGElement2("text");
|
|
3696
4096
|
label.setAttribute("class", "oc-axis-tick");
|
|
3697
|
-
|
|
4097
|
+
setAttrs2(label, {
|
|
3698
4098
|
x: isRight ? area.x + area.width + TICK_LABEL_OFFSET : area.x - TICK_LABEL_OFFSET,
|
|
3699
4099
|
y: tick.position,
|
|
3700
4100
|
"text-anchor": isRight ? "start" : "end",
|
|
@@ -3732,7 +4132,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3732
4132
|
primaryText = ellipsis;
|
|
3733
4133
|
}
|
|
3734
4134
|
appendCompoundLabel(label, primaryText, tick.subtitle, fontWeight);
|
|
3735
|
-
const titleEl =
|
|
4135
|
+
const titleEl = createSVGElement2("title");
|
|
3736
4136
|
titleEl.textContent = `${tick.label} ${tick.subtitle}`;
|
|
3737
4137
|
label.appendChild(titleEl);
|
|
3738
4138
|
} else {
|
|
@@ -3755,7 +4155,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3755
4155
|
}
|
|
3756
4156
|
}
|
|
3757
4157
|
label.textContent = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;
|
|
3758
|
-
const titleEl =
|
|
4158
|
+
const titleEl = createSVGElement2("title");
|
|
3759
4159
|
titleEl.textContent = tick.label;
|
|
3760
4160
|
label.appendChild(titleEl);
|
|
3761
4161
|
} else {
|
|
@@ -3770,10 +4170,10 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3770
4170
|
}
|
|
3771
4171
|
if (!isRight) {
|
|
3772
4172
|
for (const gridline of axis.gridlines) {
|
|
3773
|
-
const gl =
|
|
4173
|
+
const gl = createSVGElement2("line");
|
|
3774
4174
|
gl.setAttribute("class", "oc-gridline");
|
|
3775
4175
|
if (orientation === "y") {
|
|
3776
|
-
|
|
4176
|
+
setAttrs2(gl, {
|
|
3777
4177
|
x1: area.x,
|
|
3778
4178
|
y1: gridline.position,
|
|
3779
4179
|
x2: area.x + area.width,
|
|
@@ -3783,7 +4183,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3783
4183
|
"stroke-opacity": 0.6
|
|
3784
4184
|
});
|
|
3785
4185
|
} else {
|
|
3786
|
-
|
|
4186
|
+
setAttrs2(gl, {
|
|
3787
4187
|
x1: gridline.position,
|
|
3788
4188
|
y1: area.y,
|
|
3789
4189
|
x2: gridline.position,
|
|
@@ -3797,7 +4197,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3797
4197
|
}
|
|
3798
4198
|
}
|
|
3799
4199
|
if (axis.label && axis.labelStyle) {
|
|
3800
|
-
const axisLabel =
|
|
4200
|
+
const axisLabel = createSVGElement2("text");
|
|
3801
4201
|
axisLabel.setAttribute("class", "oc-axis-title");
|
|
3802
4202
|
applyTextStyle(axisLabel, axis.labelStyle);
|
|
3803
4203
|
axisLabel.textContent = axis.label;
|
|
@@ -3817,7 +4217,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3817
4217
|
const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
3818
4218
|
titleY = area.y + area.height + rotatedHeight + 14;
|
|
3819
4219
|
}
|
|
3820
|
-
|
|
4220
|
+
setAttrs2(axisLabel, {
|
|
3821
4221
|
x: area.x + area.width / 2,
|
|
3822
4222
|
y: titleY,
|
|
3823
4223
|
"text-anchor": "middle"
|
|
@@ -3825,7 +4225,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3825
4225
|
} else if (isRight) {
|
|
3826
4226
|
const titleOffset = getAxisTitleOffset(layout.dimensions.width);
|
|
3827
4227
|
const titleX = area.x + area.width + titleOffset;
|
|
3828
|
-
|
|
4228
|
+
setAttrs2(axisLabel, {
|
|
3829
4229
|
x: titleX,
|
|
3830
4230
|
y: area.y + area.height / 2,
|
|
3831
4231
|
"text-anchor": "middle",
|
|
@@ -3833,7 +4233,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3833
4233
|
});
|
|
3834
4234
|
} else {
|
|
3835
4235
|
const titleOffset = getAxisTitleOffset(layout.dimensions.width);
|
|
3836
|
-
|
|
4236
|
+
setAttrs2(axisLabel, {
|
|
3837
4237
|
x: area.x - titleOffset,
|
|
3838
4238
|
y: area.y + area.height / 2,
|
|
3839
4239
|
"text-anchor": "middle",
|
|
@@ -3858,7 +4258,7 @@ function renderAxes(parent, layout) {
|
|
|
3858
4258
|
|
|
3859
4259
|
// src/renderers/brand.ts
|
|
3860
4260
|
import { BRAND_FONT_SIZE as BRAND_FONT_SIZE2, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2 } from "@opendata-ai/openchart-core";
|
|
3861
|
-
var
|
|
4261
|
+
var BRAND_URL2 = "https://tryopendata.ai";
|
|
3862
4262
|
function renderBrand(parent, layout) {
|
|
3863
4263
|
if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
|
|
3864
4264
|
const { width } = layout.dimensions;
|
|
@@ -3870,15 +4270,15 @@ function renderBrand(parent, layout) {
|
|
|
3870
4270
|
const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
|
|
3871
4271
|
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
3872
4272
|
const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
|
|
3873
|
-
const a2 =
|
|
3874
|
-
a2.setAttribute("href",
|
|
3875
|
-
a2.setAttributeNS(
|
|
4273
|
+
const a2 = createSVGElement2("a");
|
|
4274
|
+
a2.setAttribute("href", BRAND_URL2);
|
|
4275
|
+
a2.setAttributeNS(XLINK_NS2, "xlink:href", BRAND_URL2);
|
|
3876
4276
|
a2.setAttribute("target", "_blank");
|
|
3877
4277
|
a2.setAttribute("rel", "noopener");
|
|
3878
4278
|
a2.setAttribute("class", "oc-chrome-ref");
|
|
3879
4279
|
const BRAND_LARGE = 16;
|
|
3880
|
-
const text =
|
|
3881
|
-
|
|
4280
|
+
const text = createSVGElement2("text");
|
|
4281
|
+
setAttrs2(text, {
|
|
3882
4282
|
x: rightEdge,
|
|
3883
4283
|
y: chromeY + BRAND_LARGE,
|
|
3884
4284
|
"dominant-baseline": "alphabetic",
|
|
@@ -3888,16 +4288,16 @@ function renderBrand(parent, layout) {
|
|
|
3888
4288
|
"fill-opacity": 0.55
|
|
3889
4289
|
});
|
|
3890
4290
|
text.style.setProperty("fill", fill);
|
|
3891
|
-
const trySpan =
|
|
4291
|
+
const trySpan = createSVGElement2("tspan");
|
|
3892
4292
|
trySpan.setAttribute("font-weight", "500");
|
|
3893
4293
|
trySpan.textContent = "try";
|
|
3894
4294
|
text.appendChild(trySpan);
|
|
3895
|
-
const openDataSpan =
|
|
4295
|
+
const openDataSpan = createSVGElement2("tspan");
|
|
3896
4296
|
openDataSpan.setAttribute("font-weight", "600");
|
|
3897
4297
|
openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
|
|
3898
4298
|
openDataSpan.textContent = "OpenData";
|
|
3899
4299
|
text.appendChild(openDataSpan);
|
|
3900
|
-
const aiSpan =
|
|
4300
|
+
const aiSpan = createSVGElement2("tspan");
|
|
3901
4301
|
aiSpan.setAttribute("font-weight", "500");
|
|
3902
4302
|
aiSpan.textContent = ".ai";
|
|
3903
4303
|
text.appendChild(aiSpan);
|
|
@@ -3908,8 +4308,8 @@ function renderBrand(parent, layout) {
|
|
|
3908
4308
|
// src/renderers/chrome.ts
|
|
3909
4309
|
import { wrapText } from "@opendata-ai/openchart-core";
|
|
3910
4310
|
function renderChromeElement(parent, element, className, chromeKey, measureText) {
|
|
3911
|
-
const text =
|
|
3912
|
-
|
|
4311
|
+
const text = createSVGElement2("text");
|
|
4312
|
+
setAttrs2(text, { x: element.x, y: element.y });
|
|
3913
4313
|
applyTextStyle(text, element.style);
|
|
3914
4314
|
text.setAttribute("class", className);
|
|
3915
4315
|
text.setAttribute("data-chrome-key", chromeKey);
|
|
@@ -3925,16 +4325,16 @@ function renderChromeElement(parent, element, className, chromeKey, measureText)
|
|
|
3925
4325
|
} else {
|
|
3926
4326
|
const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
|
|
3927
4327
|
for (let i = 0; i < lines.length; i++) {
|
|
3928
|
-
const tspan =
|
|
3929
|
-
|
|
4328
|
+
const tspan = createSVGElement2("tspan");
|
|
4329
|
+
setAttrs2(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
|
|
3930
4330
|
tspan.textContent = lines[i];
|
|
3931
4331
|
text.appendChild(tspan);
|
|
3932
4332
|
}
|
|
3933
4333
|
}
|
|
3934
4334
|
parent.appendChild(text);
|
|
3935
4335
|
}
|
|
3936
|
-
function
|
|
3937
|
-
const g =
|
|
4336
|
+
function renderChrome2(parent, layout) {
|
|
4337
|
+
const g = createSVGElement2("g");
|
|
3938
4338
|
g.setAttribute("class", "oc-chrome");
|
|
3939
4339
|
const { chrome, measureText } = layout;
|
|
3940
4340
|
if (chrome.title) {
|
|
@@ -3982,7 +4382,7 @@ function isCategorical(legend) {
|
|
|
3982
4382
|
}
|
|
3983
4383
|
function renderLegend(parent, legend) {
|
|
3984
4384
|
if (!isCategorical(legend) || legend.entries.length === 0) return;
|
|
3985
|
-
const g =
|
|
4385
|
+
const g = createSVGElement2("g");
|
|
3986
4386
|
g.setAttribute("class", "oc-legend");
|
|
3987
4387
|
g.setAttribute("role", "list");
|
|
3988
4388
|
g.setAttribute("aria-label", "Chart legend");
|
|
@@ -4003,7 +4403,7 @@ function renderLegend(parent, legend) {
|
|
|
4003
4403
|
offsetY += legend.swatchSize + 6;
|
|
4004
4404
|
}
|
|
4005
4405
|
}
|
|
4006
|
-
const entryG =
|
|
4406
|
+
const entryG = createSVGElement2("g");
|
|
4007
4407
|
entryG.setAttribute("class", "oc-legend-entry");
|
|
4008
4408
|
entryG.setAttribute("role", "listitem");
|
|
4009
4409
|
entryG.setAttribute("data-legend-index", String(i));
|
|
@@ -4023,8 +4423,8 @@ function renderLegend(parent, legend) {
|
|
|
4023
4423
|
}
|
|
4024
4424
|
}
|
|
4025
4425
|
if (entry.shape === "circle") {
|
|
4026
|
-
const circle =
|
|
4027
|
-
|
|
4426
|
+
const circle = createSVGElement2("circle");
|
|
4427
|
+
setAttrs2(circle, {
|
|
4028
4428
|
cx: offsetX + legend.swatchSize / 2,
|
|
4029
4429
|
cy: offsetY + legend.swatchSize / 2,
|
|
4030
4430
|
r: legend.swatchSize / 2,
|
|
@@ -4032,8 +4432,8 @@ function renderLegend(parent, legend) {
|
|
|
4032
4432
|
});
|
|
4033
4433
|
entryG.appendChild(circle);
|
|
4034
4434
|
} else if (entry.shape === "line") {
|
|
4035
|
-
const line =
|
|
4036
|
-
|
|
4435
|
+
const line = createSVGElement2("line");
|
|
4436
|
+
setAttrs2(line, {
|
|
4037
4437
|
x1: offsetX,
|
|
4038
4438
|
y1: offsetY + legend.swatchSize / 2,
|
|
4039
4439
|
x2: offsetX + legend.swatchSize,
|
|
@@ -4042,8 +4442,8 @@ function renderLegend(parent, legend) {
|
|
|
4042
4442
|
"stroke-width": 2
|
|
4043
4443
|
});
|
|
4044
4444
|
entryG.appendChild(line);
|
|
4045
|
-
const dot =
|
|
4046
|
-
|
|
4445
|
+
const dot = createSVGElement2("circle");
|
|
4446
|
+
setAttrs2(dot, {
|
|
4047
4447
|
cx: offsetX + legend.swatchSize / 2,
|
|
4048
4448
|
cy: offsetY + legend.swatchSize / 2,
|
|
4049
4449
|
r: 2.5,
|
|
@@ -4051,8 +4451,8 @@ function renderLegend(parent, legend) {
|
|
|
4051
4451
|
});
|
|
4052
4452
|
entryG.appendChild(dot);
|
|
4053
4453
|
} else {
|
|
4054
|
-
const rect =
|
|
4055
|
-
|
|
4454
|
+
const rect = createSVGElement2("rect");
|
|
4455
|
+
setAttrs2(rect, {
|
|
4056
4456
|
x: offsetX,
|
|
4057
4457
|
y: offsetY,
|
|
4058
4458
|
width: legend.swatchSize,
|
|
@@ -4062,8 +4462,8 @@ function renderLegend(parent, legend) {
|
|
|
4062
4462
|
});
|
|
4063
4463
|
entryG.appendChild(rect);
|
|
4064
4464
|
}
|
|
4065
|
-
const label =
|
|
4066
|
-
|
|
4465
|
+
const label = createSVGElement2("text");
|
|
4466
|
+
setAttrs2(label, {
|
|
4067
4467
|
x: offsetX + legend.swatchSize + legend.swatchGap,
|
|
4068
4468
|
y: offsetY + legend.swatchSize / 2,
|
|
4069
4469
|
"dominant-baseline": "central"
|
|
@@ -4109,14 +4509,14 @@ function registerMarkRenderer(type, renderer) {
|
|
|
4109
4509
|
markRenderers[type] = renderer;
|
|
4110
4510
|
}
|
|
4111
4511
|
function renderLineMark(mark, index2) {
|
|
4112
|
-
const g =
|
|
4512
|
+
const g = createSVGElement2("g");
|
|
4113
4513
|
g.setAttribute("data-mark-id", `line-${mark.seriesKey ?? index2}`);
|
|
4114
4514
|
g.setAttribute("class", "oc-mark oc-mark-line");
|
|
4115
4515
|
stampAnimationAttrs(g, mark, index2);
|
|
4116
4516
|
if (mark.points.length > 1) {
|
|
4117
|
-
const path =
|
|
4517
|
+
const path = createSVGElement2("path");
|
|
4118
4518
|
const d = mark.path ?? mark.points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
|
|
4119
|
-
|
|
4519
|
+
setAttrs2(path, {
|
|
4120
4520
|
d,
|
|
4121
4521
|
fill: "none",
|
|
4122
4522
|
stroke: mark.stroke,
|
|
@@ -4131,19 +4531,19 @@ function renderLineMark(mark, index2) {
|
|
|
4131
4531
|
g.appendChild(path);
|
|
4132
4532
|
}
|
|
4133
4533
|
if (mark.label?.visible) {
|
|
4134
|
-
const label =
|
|
4534
|
+
const label = createSVGElement2("text");
|
|
4135
4535
|
label.setAttribute("class", "oc-mark-label");
|
|
4136
4536
|
if (mark.seriesKey) {
|
|
4137
4537
|
label.setAttribute("data-series", mark.seriesKey);
|
|
4138
4538
|
}
|
|
4139
|
-
|
|
4539
|
+
setAttrs2(label, { x: mark.label.x, y: mark.label.y });
|
|
4140
4540
|
applyTextStyle(label, mark.label.style);
|
|
4141
4541
|
label.textContent = mark.label.text;
|
|
4142
4542
|
g.appendChild(label);
|
|
4143
4543
|
if (mark.label.connector) {
|
|
4144
|
-
const connector =
|
|
4544
|
+
const connector = createSVGElement2("line");
|
|
4145
4545
|
connector.setAttribute("class", "oc-mark-connector");
|
|
4146
|
-
|
|
4546
|
+
setAttrs2(connector, {
|
|
4147
4547
|
x1: mark.label.connector.from.x,
|
|
4148
4548
|
y1: mark.label.connector.from.y,
|
|
4149
4549
|
x2: mark.label.connector.to.x,
|
|
@@ -4158,13 +4558,13 @@ function renderLineMark(mark, index2) {
|
|
|
4158
4558
|
return g;
|
|
4159
4559
|
}
|
|
4160
4560
|
function renderAreaMark(mark, index2) {
|
|
4161
|
-
const g =
|
|
4561
|
+
const g = createSVGElement2("g");
|
|
4162
4562
|
g.setAttribute("data-mark-id", `area-${mark.seriesKey ?? index2}`);
|
|
4163
4563
|
g.setAttribute("class", "oc-mark oc-mark-area");
|
|
4164
4564
|
stampAnimationAttrs(g, mark, index2);
|
|
4165
4565
|
if (mark.path) {
|
|
4166
|
-
const fill =
|
|
4167
|
-
|
|
4566
|
+
const fill = createSVGElement2("path");
|
|
4567
|
+
setAttrs2(fill, {
|
|
4168
4568
|
d: mark.path,
|
|
4169
4569
|
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
4170
4570
|
"fill-opacity": mark.fillOpacity,
|
|
@@ -4172,9 +4572,9 @@ function renderAreaMark(mark, index2) {
|
|
|
4172
4572
|
});
|
|
4173
4573
|
g.appendChild(fill);
|
|
4174
4574
|
if (mark.stroke && mark.topPath) {
|
|
4175
|
-
const strokePath =
|
|
4575
|
+
const strokePath = createSVGElement2("path");
|
|
4176
4576
|
strokePath.setAttribute("class", "oc-area-top");
|
|
4177
|
-
|
|
4577
|
+
setAttrs2(strokePath, {
|
|
4178
4578
|
d: mark.topPath,
|
|
4179
4579
|
fill: "none",
|
|
4180
4580
|
stroke: mark.stroke,
|
|
@@ -4186,15 +4586,15 @@ function renderAreaMark(mark, index2) {
|
|
|
4186
4586
|
return g;
|
|
4187
4587
|
}
|
|
4188
4588
|
function renderRectMark(mark, index2) {
|
|
4189
|
-
const g =
|
|
4589
|
+
const g = createSVGElement2("g");
|
|
4190
4590
|
g.setAttribute("data-mark-id", `rect-${index2}`);
|
|
4191
4591
|
g.setAttribute("class", "oc-mark oc-mark-rect");
|
|
4192
4592
|
stampAnimationAttrs(g, mark, index2);
|
|
4193
4593
|
if (currentAnimation?.enabled && mark.orient === "horizontal") {
|
|
4194
4594
|
g.setAttribute("data-orient", "horizontal");
|
|
4195
4595
|
}
|
|
4196
|
-
const rect =
|
|
4197
|
-
|
|
4596
|
+
const rect = createSVGElement2("rect");
|
|
4597
|
+
setAttrs2(rect, {
|
|
4198
4598
|
x: mark.x,
|
|
4199
4599
|
y: mark.y,
|
|
4200
4600
|
width: mark.width,
|
|
@@ -4208,13 +4608,13 @@ function renderRectMark(mark, index2) {
|
|
|
4208
4608
|
rect.setAttribute("stroke-width", String(mark.strokeWidth));
|
|
4209
4609
|
}
|
|
4210
4610
|
if (mark.cornerRadius) {
|
|
4211
|
-
|
|
4611
|
+
setAttrs2(rect, { rx: mark.cornerRadius, ry: mark.cornerRadius });
|
|
4212
4612
|
}
|
|
4213
4613
|
g.appendChild(rect);
|
|
4214
4614
|
if (mark.label?.visible) {
|
|
4215
|
-
const label =
|
|
4615
|
+
const label = createSVGElement2("text");
|
|
4216
4616
|
label.setAttribute("class", "oc-mark-label");
|
|
4217
|
-
|
|
4617
|
+
setAttrs2(label, { x: mark.label.x, y: mark.label.y });
|
|
4218
4618
|
applyTextStyle(label, mark.label.style);
|
|
4219
4619
|
label.textContent = mark.label.text;
|
|
4220
4620
|
g.appendChild(label);
|
|
@@ -4222,13 +4622,13 @@ function renderRectMark(mark, index2) {
|
|
|
4222
4622
|
return g;
|
|
4223
4623
|
}
|
|
4224
4624
|
function renderArcMark(mark, index2) {
|
|
4225
|
-
const g =
|
|
4625
|
+
const g = createSVGElement2("g");
|
|
4226
4626
|
g.setAttribute("data-mark-id", `arc-${index2}`);
|
|
4227
4627
|
g.setAttribute("class", "oc-mark oc-mark-arc");
|
|
4228
4628
|
g.setAttribute("transform", `translate(${mark.center.x},${mark.center.y})`);
|
|
4229
4629
|
stampAnimationAttrs(g, mark, index2);
|
|
4230
|
-
const path =
|
|
4231
|
-
|
|
4630
|
+
const path = createSVGElement2("path");
|
|
4631
|
+
setAttrs2(path, {
|
|
4232
4632
|
d: mark.path,
|
|
4233
4633
|
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
4234
4634
|
stroke: mark.stroke,
|
|
@@ -4236,9 +4636,9 @@ function renderArcMark(mark, index2) {
|
|
|
4236
4636
|
});
|
|
4237
4637
|
g.appendChild(path);
|
|
4238
4638
|
if (mark.label?.visible) {
|
|
4239
|
-
const label =
|
|
4639
|
+
const label = createSVGElement2("text");
|
|
4240
4640
|
label.setAttribute("class", "oc-mark-label");
|
|
4241
|
-
|
|
4641
|
+
setAttrs2(label, {
|
|
4242
4642
|
x: mark.label.x - mark.center.x,
|
|
4243
4643
|
y: mark.label.y - mark.center.y
|
|
4244
4644
|
});
|
|
@@ -4249,11 +4649,11 @@ function renderArcMark(mark, index2) {
|
|
|
4249
4649
|
return g;
|
|
4250
4650
|
}
|
|
4251
4651
|
function renderPointMark(mark, index2) {
|
|
4252
|
-
const circle =
|
|
4652
|
+
const circle = createSVGElement2("circle");
|
|
4253
4653
|
circle.setAttribute("data-mark-id", `point-${index2}`);
|
|
4254
4654
|
circle.setAttribute("class", "oc-mark oc-mark-point");
|
|
4255
4655
|
stampAnimationAttrs(circle, mark, index2);
|
|
4256
|
-
|
|
4656
|
+
setAttrs2(circle, {
|
|
4257
4657
|
cx: mark.cx,
|
|
4258
4658
|
cy: mark.cy,
|
|
4259
4659
|
r: mark.r,
|
|
@@ -4267,11 +4667,11 @@ function renderPointMark(mark, index2) {
|
|
|
4267
4667
|
return circle;
|
|
4268
4668
|
}
|
|
4269
4669
|
function renderTextMark(mark, index2) {
|
|
4270
|
-
const text =
|
|
4670
|
+
const text = createSVGElement2("text");
|
|
4271
4671
|
text.setAttribute("data-mark-id", `textMark-${index2}`);
|
|
4272
4672
|
text.setAttribute("class", "oc-mark oc-mark-text");
|
|
4273
4673
|
stampAnimationAttrs(text, mark, index2);
|
|
4274
|
-
|
|
4674
|
+
setAttrs2(text, {
|
|
4275
4675
|
x: mark.x,
|
|
4276
4676
|
y: mark.y,
|
|
4277
4677
|
"font-size": mark.fontSize,
|
|
@@ -4291,11 +4691,11 @@ function renderTextMark(mark, index2) {
|
|
|
4291
4691
|
return text;
|
|
4292
4692
|
}
|
|
4293
4693
|
function renderRuleMark(mark, index2) {
|
|
4294
|
-
const line =
|
|
4694
|
+
const line = createSVGElement2("line");
|
|
4295
4695
|
line.setAttribute("data-mark-id", `rule-${index2}`);
|
|
4296
4696
|
line.setAttribute("class", "oc-mark oc-mark-rule");
|
|
4297
4697
|
stampAnimationAttrs(line, mark, index2);
|
|
4298
|
-
|
|
4698
|
+
setAttrs2(line, {
|
|
4299
4699
|
x1: mark.x1,
|
|
4300
4700
|
y1: mark.y1,
|
|
4301
4701
|
x2: mark.x2,
|
|
@@ -4312,13 +4712,13 @@ function renderRuleMark(mark, index2) {
|
|
|
4312
4712
|
return line;
|
|
4313
4713
|
}
|
|
4314
4714
|
function renderTickMark(mark, index2) {
|
|
4315
|
-
const line =
|
|
4715
|
+
const line = createSVGElement2("line");
|
|
4316
4716
|
line.setAttribute("data-mark-id", `tick-${index2}`);
|
|
4317
4717
|
line.setAttribute("class", "oc-mark oc-mark-tick");
|
|
4318
4718
|
stampAnimationAttrs(line, mark, index2);
|
|
4319
4719
|
const half = mark.length / 2;
|
|
4320
4720
|
if (mark.orient === "vertical") {
|
|
4321
|
-
|
|
4721
|
+
setAttrs2(line, {
|
|
4322
4722
|
x1: mark.x,
|
|
4323
4723
|
y1: mark.y - half,
|
|
4324
4724
|
x2: mark.x,
|
|
@@ -4327,7 +4727,7 @@ function renderTickMark(mark, index2) {
|
|
|
4327
4727
|
"stroke-width": mark.strokeWidth
|
|
4328
4728
|
});
|
|
4329
4729
|
} else {
|
|
4330
|
-
|
|
4730
|
+
setAttrs2(line, {
|
|
4331
4731
|
x1: mark.x - half,
|
|
4332
4732
|
y1: mark.y,
|
|
4333
4733
|
x2: mark.x + half,
|
|
@@ -4363,7 +4763,7 @@ function getMarkSeries(mark) {
|
|
|
4363
4763
|
return void 0;
|
|
4364
4764
|
}
|
|
4365
4765
|
function renderMarks(parent, layout) {
|
|
4366
|
-
const g =
|
|
4766
|
+
const g = createSVGElement2("g");
|
|
4367
4767
|
g.setAttribute("class", "oc-marks");
|
|
4368
4768
|
for (let i = 0; i < layout.marks.length; i++) {
|
|
4369
4769
|
const mark = layout.marks[i];
|
|
@@ -4400,10 +4800,10 @@ var EASE_VAR_MAP = {
|
|
|
4400
4800
|
function renderChartSVG(layout, container, opts) {
|
|
4401
4801
|
const { width, height } = layout.dimensions;
|
|
4402
4802
|
const animation = layout.animation;
|
|
4403
|
-
const svg =
|
|
4404
|
-
|
|
4803
|
+
const svg = createSVGElement2("svg");
|
|
4804
|
+
setAttrs2(svg, {
|
|
4405
4805
|
viewBox: `0 0 ${width} ${height}`,
|
|
4406
|
-
xmlns:
|
|
4806
|
+
xmlns: SVG_NS3,
|
|
4407
4807
|
// WebKit/iOS Safari getBBox() bug: text with dominant-baseline:hanging
|
|
4408
4808
|
// reports bounding boxes extending above y=0. The SVG spec default
|
|
4409
4809
|
// overflow is "hidden", which clips this phantom extent. Setting
|
|
@@ -4441,8 +4841,8 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4441
4841
|
svg.style.setProperty("--oc-stack-segment-duration", `${segDuration}ms`);
|
|
4442
4842
|
}
|
|
4443
4843
|
}
|
|
4444
|
-
const bg =
|
|
4445
|
-
|
|
4844
|
+
const bg = createSVGElement2("rect");
|
|
4845
|
+
setAttrs2(bg, {
|
|
4446
4846
|
x: 0,
|
|
4447
4847
|
y: 0,
|
|
4448
4848
|
width,
|
|
@@ -4451,15 +4851,20 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4451
4851
|
});
|
|
4452
4852
|
svg.appendChild(bg);
|
|
4453
4853
|
const clipId = nextSvgId("oc-clip");
|
|
4454
|
-
const defs =
|
|
4455
|
-
const clipPath =
|
|
4854
|
+
const defs = createSVGElement2("defs");
|
|
4855
|
+
const clipPath = createSVGElement2("clipPath");
|
|
4456
4856
|
clipPath.setAttribute("id", clipId);
|
|
4457
|
-
const
|
|
4458
|
-
|
|
4857
|
+
const maxPointR = layout.marks.reduce(
|
|
4858
|
+
(max, m2) => m2.type === "point" && m2.r ? Math.max(max, m2.r) : max,
|
|
4859
|
+
0
|
|
4860
|
+
);
|
|
4861
|
+
const clipPad = Math.max(maxPointR, 2);
|
|
4862
|
+
const clipRect = createSVGElement2("rect");
|
|
4863
|
+
setAttrs2(clipRect, {
|
|
4459
4864
|
x: 0,
|
|
4460
|
-
y: layout.area.y,
|
|
4865
|
+
y: layout.area.y - clipPad,
|
|
4461
4866
|
width,
|
|
4462
|
-
height: layout.area.height + 2
|
|
4867
|
+
height: layout.area.height + clipPad * 2
|
|
4463
4868
|
});
|
|
4464
4869
|
clipPath.appendChild(clipRect);
|
|
4465
4870
|
defs.appendChild(clipPath);
|
|
@@ -4468,7 +4873,7 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4468
4873
|
setMarkRenderState({ animation, gradientMap });
|
|
4469
4874
|
try {
|
|
4470
4875
|
renderAxes(svg, layout);
|
|
4471
|
-
const clippedGroup =
|
|
4876
|
+
const clippedGroup = createSVGElement2("g");
|
|
4472
4877
|
clippedGroup.setAttribute("clip-path", `url(#${clipId})`);
|
|
4473
4878
|
renderMarks(clippedGroup, layout);
|
|
4474
4879
|
const hasLineOrAreaWithDataPoints = layout.marks.some(
|
|
@@ -4476,8 +4881,8 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4476
4881
|
);
|
|
4477
4882
|
const hasPointMarks = layout.marks.some((m2) => m2.type === "point");
|
|
4478
4883
|
if (hasLineOrAreaWithDataPoints && !hasPointMarks) {
|
|
4479
|
-
const overlay =
|
|
4480
|
-
|
|
4884
|
+
const overlay = createSVGElement2("rect");
|
|
4885
|
+
setAttrs2(overlay, {
|
|
4481
4886
|
x: layout.area.x,
|
|
4482
4887
|
y: layout.area.y,
|
|
4483
4888
|
width: layout.area.width,
|
|
@@ -4488,10 +4893,10 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4488
4893
|
overlay.setAttribute("data-voronoi-overlay", "true");
|
|
4489
4894
|
clippedGroup.appendChild(overlay);
|
|
4490
4895
|
if (opts?.crosshair) {
|
|
4491
|
-
const crosshairLine =
|
|
4896
|
+
const crosshairLine = createSVGElement2("line");
|
|
4492
4897
|
crosshairLine.setAttribute("data-crosshair", "true");
|
|
4493
4898
|
crosshairLine.setAttribute("class", "oc-crosshair");
|
|
4494
|
-
|
|
4899
|
+
setAttrs2(crosshairLine, {
|
|
4495
4900
|
x1: 0,
|
|
4496
4901
|
y1: layout.area.y,
|
|
4497
4902
|
x2: 0,
|
|
@@ -4509,7 +4914,7 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4509
4914
|
svg.appendChild(clippedGroup);
|
|
4510
4915
|
renderAnnotations(svg, layout);
|
|
4511
4916
|
renderLegend(svg, layout.legend);
|
|
4512
|
-
|
|
4917
|
+
renderChrome2(svg, layout);
|
|
4513
4918
|
if (layout.watermark) {
|
|
4514
4919
|
renderBrand(svg, layout);
|
|
4515
4920
|
}
|
|
@@ -4674,7 +5079,7 @@ function createTextEditOverlay(config) {
|
|
|
4674
5079
|
}
|
|
4675
5080
|
|
|
4676
5081
|
// src/mount.ts
|
|
4677
|
-
function
|
|
5082
|
+
function resolveDarkMode3(mode) {
|
|
4678
5083
|
if (mode === "force") return true;
|
|
4679
5084
|
if (mode === "off" || mode === void 0) return false;
|
|
4680
5085
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
@@ -5141,7 +5546,7 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
|
|
|
5141
5546
|
};
|
|
5142
5547
|
}
|
|
5143
5548
|
function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
5144
|
-
const
|
|
5549
|
+
const SVG_NS6 = "http://www.w3.org/2000/svg";
|
|
5145
5550
|
const cleanups = [];
|
|
5146
5551
|
const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
|
|
5147
5552
|
for (const el of annotationGroups) {
|
|
@@ -5180,7 +5585,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
|
|
|
5180
5585
|
const createdHandles = [];
|
|
5181
5586
|
for (const ep of endpoints) {
|
|
5182
5587
|
if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
|
|
5183
|
-
const handleEl = document.createElementNS(
|
|
5588
|
+
const handleEl = document.createElementNS(SVG_NS6, "circle");
|
|
5184
5589
|
handleEl.setAttribute("class", "oc-connector-handle");
|
|
5185
5590
|
handleEl.setAttribute("data-endpoint", ep.name);
|
|
5186
5591
|
handleEl.setAttribute("cx", String(ep.cx));
|
|
@@ -5800,7 +6205,7 @@ function createChart(container, spec, options) {
|
|
|
5800
6205
|
const measureText = createMeasureText();
|
|
5801
6206
|
function compile() {
|
|
5802
6207
|
const { width, height } = getContainerDimensions();
|
|
5803
|
-
const darkMode =
|
|
6208
|
+
const darkMode = resolveDarkMode3(options?.darkMode);
|
|
5804
6209
|
const compileOpts = {
|
|
5805
6210
|
width,
|
|
5806
6211
|
height,
|
|
@@ -6171,7 +6576,7 @@ function createChart(container, spec, options) {
|
|
|
6171
6576
|
}
|
|
6172
6577
|
srTable = createScreenReaderTable(currentLayout, container);
|
|
6173
6578
|
container.classList.add("oc-root");
|
|
6174
|
-
const isDark =
|
|
6579
|
+
const isDark = resolveDarkMode3(options?.darkMode);
|
|
6175
6580
|
if (isDark) {
|
|
6176
6581
|
container.classList.add("oc-dark");
|
|
6177
6582
|
} else {
|
|
@@ -6660,23 +7065,23 @@ import {
|
|
|
6660
7065
|
wrapText as wrapText2
|
|
6661
7066
|
} from "@opendata-ai/openchart-core";
|
|
6662
7067
|
import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
|
|
6663
|
-
var
|
|
6664
|
-
var
|
|
6665
|
-
var
|
|
7068
|
+
var SVG_NS4 = "http://www.w3.org/2000/svg";
|
|
7069
|
+
var XLINK_NS3 = "http://www.w3.org/1999/xlink";
|
|
7070
|
+
var BRAND_URL3 = "https://tryopendata.ai";
|
|
6666
7071
|
var EASE_VAR_MAP2 = {
|
|
6667
7072
|
smooth: "var(--oc-ease-smooth)",
|
|
6668
7073
|
snappy: "var(--oc-ease-snappy)"
|
|
6669
7074
|
};
|
|
6670
|
-
function
|
|
6671
|
-
return document.createElementNS(
|
|
7075
|
+
function createSVGElement3(tag) {
|
|
7076
|
+
return document.createElementNS(SVG_NS4, tag);
|
|
6672
7077
|
}
|
|
6673
|
-
function
|
|
7078
|
+
function setAttrs3(el, attrs) {
|
|
6674
7079
|
for (const [key, value] of Object.entries(attrs)) {
|
|
6675
7080
|
el.setAttribute(key, String(value));
|
|
6676
7081
|
}
|
|
6677
7082
|
}
|
|
6678
7083
|
function applyTextStyle2(el, style) {
|
|
6679
|
-
|
|
7084
|
+
setAttrs3(el, {
|
|
6680
7085
|
"font-family": style.fontFamily,
|
|
6681
7086
|
"font-size": style.fontSize,
|
|
6682
7087
|
"font-weight": style.fontWeight
|
|
@@ -6699,8 +7104,8 @@ function stampAnimationAttrs2(el, mark, fallbackIndex, animation) {
|
|
|
6699
7104
|
el.style.setProperty("--oc-mark-index", String(idx));
|
|
6700
7105
|
}
|
|
6701
7106
|
function renderChromeElement2(parent, element, className, chromeKey, measureText) {
|
|
6702
|
-
const text =
|
|
6703
|
-
|
|
7107
|
+
const text = createSVGElement3("text");
|
|
7108
|
+
setAttrs3(text, { x: element.x, y: element.y });
|
|
6704
7109
|
applyTextStyle2(text, element.style);
|
|
6705
7110
|
text.setAttribute("class", className);
|
|
6706
7111
|
text.setAttribute("data-chrome-key", chromeKey);
|
|
@@ -6716,16 +7121,16 @@ function renderChromeElement2(parent, element, className, chromeKey, measureText
|
|
|
6716
7121
|
} else {
|
|
6717
7122
|
const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
|
|
6718
7123
|
for (let i = 0; i < lines.length; i++) {
|
|
6719
|
-
const tspan =
|
|
6720
|
-
|
|
7124
|
+
const tspan = createSVGElement3("tspan");
|
|
7125
|
+
setAttrs3(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
|
|
6721
7126
|
tspan.textContent = lines[i];
|
|
6722
7127
|
text.appendChild(tspan);
|
|
6723
7128
|
}
|
|
6724
7129
|
}
|
|
6725
7130
|
parent.appendChild(text);
|
|
6726
7131
|
}
|
|
6727
|
-
function
|
|
6728
|
-
const g =
|
|
7132
|
+
function renderChrome3(parent, layout) {
|
|
7133
|
+
const g = createSVGElement3("g");
|
|
6729
7134
|
g.setAttribute("class", "oc-chrome");
|
|
6730
7135
|
const { chrome, measureText } = layout;
|
|
6731
7136
|
if (chrome.title) {
|
|
@@ -6774,15 +7179,15 @@ function renderBrand2(parent, layout) {
|
|
|
6774
7179
|
const { chrome } = layout;
|
|
6775
7180
|
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
6776
7181
|
const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
|
|
6777
|
-
const a2 =
|
|
6778
|
-
a2.setAttribute("href",
|
|
6779
|
-
a2.setAttributeNS(
|
|
7182
|
+
const a2 = createSVGElement3("a");
|
|
7183
|
+
a2.setAttribute("href", BRAND_URL3);
|
|
7184
|
+
a2.setAttributeNS(XLINK_NS3, "xlink:href", BRAND_URL3);
|
|
6780
7185
|
a2.setAttribute("target", "_blank");
|
|
6781
7186
|
a2.setAttribute("rel", "noopener");
|
|
6782
7187
|
a2.setAttribute("class", "oc-chrome-ref");
|
|
6783
7188
|
const BRAND_LARGE = 16;
|
|
6784
|
-
const text =
|
|
6785
|
-
|
|
7189
|
+
const text = createSVGElement3("text");
|
|
7190
|
+
setAttrs3(text, {
|
|
6786
7191
|
x: rightEdge,
|
|
6787
7192
|
y: chromeY + BRAND_LARGE,
|
|
6788
7193
|
"dominant-baseline": "alphabetic",
|
|
@@ -6792,14 +7197,14 @@ function renderBrand2(parent, layout) {
|
|
|
6792
7197
|
"fill-opacity": 0.55
|
|
6793
7198
|
});
|
|
6794
7199
|
text.style.setProperty("fill", fill);
|
|
6795
|
-
const trySpan =
|
|
6796
|
-
|
|
7200
|
+
const trySpan = createSVGElement3("tspan");
|
|
7201
|
+
setAttrs3(trySpan, { "font-weight": 500 });
|
|
6797
7202
|
trySpan.textContent = "try";
|
|
6798
|
-
const openDataSpan =
|
|
6799
|
-
|
|
7203
|
+
const openDataSpan = createSVGElement3("tspan");
|
|
7204
|
+
setAttrs3(openDataSpan, { "font-weight": 600, "font-size": BRAND_LARGE });
|
|
6800
7205
|
openDataSpan.textContent = "OpenData";
|
|
6801
|
-
const aiSpan =
|
|
6802
|
-
|
|
7206
|
+
const aiSpan = createSVGElement3("tspan");
|
|
7207
|
+
setAttrs3(aiSpan, { "font-weight": 500 });
|
|
6803
7208
|
aiSpan.textContent = ".ai";
|
|
6804
7209
|
text.appendChild(trySpan);
|
|
6805
7210
|
text.appendChild(openDataSpan);
|
|
@@ -6810,7 +7215,7 @@ function renderBrand2(parent, layout) {
|
|
|
6810
7215
|
function renderLegend2(parent, legendLayout) {
|
|
6811
7216
|
if (!("entries" in legendLayout) || legendLayout.entries.length === 0) return;
|
|
6812
7217
|
const legend = legendLayout;
|
|
6813
|
-
const g =
|
|
7218
|
+
const g = createSVGElement3("g");
|
|
6814
7219
|
g.setAttribute("class", "oc-legend");
|
|
6815
7220
|
g.setAttribute("role", "list");
|
|
6816
7221
|
g.setAttribute("aria-label", "Chart legend");
|
|
@@ -6831,7 +7236,7 @@ function renderLegend2(parent, legendLayout) {
|
|
|
6831
7236
|
offsetY += legend.swatchSize + 6;
|
|
6832
7237
|
}
|
|
6833
7238
|
}
|
|
6834
|
-
const entryG =
|
|
7239
|
+
const entryG = createSVGElement3("g");
|
|
6835
7240
|
entryG.setAttribute("class", "oc-legend-entry");
|
|
6836
7241
|
entryG.setAttribute("role", "listitem");
|
|
6837
7242
|
entryG.setAttribute("data-legend-index", String(i));
|
|
@@ -6850,8 +7255,8 @@ function renderLegend2(parent, legendLayout) {
|
|
|
6850
7255
|
entryG.setAttribute("opacity", "0.3");
|
|
6851
7256
|
}
|
|
6852
7257
|
}
|
|
6853
|
-
const rect =
|
|
6854
|
-
|
|
7258
|
+
const rect = createSVGElement3("rect");
|
|
7259
|
+
setAttrs3(rect, {
|
|
6855
7260
|
x: offsetX,
|
|
6856
7261
|
y: offsetY,
|
|
6857
7262
|
width: legend.swatchSize,
|
|
@@ -6860,8 +7265,8 @@ function renderLegend2(parent, legendLayout) {
|
|
|
6860
7265
|
rx: 2
|
|
6861
7266
|
});
|
|
6862
7267
|
entryG.appendChild(rect);
|
|
6863
|
-
const label =
|
|
6864
|
-
|
|
7268
|
+
const label = createSVGElement3("text");
|
|
7269
|
+
setAttrs3(label, {
|
|
6865
7270
|
x: offsetX + legend.swatchSize + legend.swatchGap,
|
|
6866
7271
|
y: offsetY + legend.swatchSize / 2,
|
|
6867
7272
|
"dominant-baseline": "central"
|
|
@@ -6899,7 +7304,7 @@ function renderGradientDefs(defs, links, nodePositions) {
|
|
|
6899
7304
|
const link = links[i];
|
|
6900
7305
|
if (link.sourceColor === link.targetColor) continue;
|
|
6901
7306
|
const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;
|
|
6902
|
-
const gradient =
|
|
7307
|
+
const gradient = createSVGElement3("linearGradient");
|
|
6903
7308
|
gradient.setAttribute("id", gradId);
|
|
6904
7309
|
gradient.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
6905
7310
|
const sourcePos = nodePositions.get(link.sourceId);
|
|
@@ -6908,21 +7313,21 @@ function renderGradientDefs(defs, links, nodePositions) {
|
|
|
6908
7313
|
const x22 = targetPos ? targetPos.x : 0;
|
|
6909
7314
|
gradient.setAttribute("x1", String(x1));
|
|
6910
7315
|
gradient.setAttribute("x2", String(x22));
|
|
6911
|
-
const stop0 =
|
|
6912
|
-
|
|
7316
|
+
const stop0 = createSVGElement3("stop");
|
|
7317
|
+
setAttrs3(stop0, { offset: "0%", "stop-color": link.sourceColor });
|
|
6913
7318
|
gradient.appendChild(stop0);
|
|
6914
|
-
const stop1 =
|
|
6915
|
-
|
|
7319
|
+
const stop1 = createSVGElement3("stop");
|
|
7320
|
+
setAttrs3(stop1, { offset: "100%", "stop-color": link.targetColor });
|
|
6916
7321
|
gradient.appendChild(stop1);
|
|
6917
7322
|
defs.appendChild(gradient);
|
|
6918
7323
|
}
|
|
6919
7324
|
}
|
|
6920
7325
|
function renderLinks(parent, links, _nodePositions, animation) {
|
|
6921
|
-
const g =
|
|
7326
|
+
const g = createSVGElement3("g");
|
|
6922
7327
|
g.setAttribute("class", "oc-sankey-links");
|
|
6923
7328
|
for (let i = 0; i < links.length; i++) {
|
|
6924
7329
|
const link = links[i];
|
|
6925
|
-
const linkG =
|
|
7330
|
+
const linkG = createSVGElement3("g");
|
|
6926
7331
|
linkG.setAttribute("class", "oc-sankey-link");
|
|
6927
7332
|
linkG.setAttribute("data-mark-id", `link-${link.sourceId}-${link.targetId}-${i}`);
|
|
6928
7333
|
linkG.setAttribute("data-source", link.sourceId);
|
|
@@ -6931,7 +7336,7 @@ function renderLinks(parent, links, _nodePositions, animation) {
|
|
|
6931
7336
|
linkG.setAttribute("aria-label", link.aria.label);
|
|
6932
7337
|
}
|
|
6933
7338
|
stampAnimationAttrs2(linkG, link, i, animation);
|
|
6934
|
-
const path =
|
|
7339
|
+
const path = createSVGElement3("path");
|
|
6935
7340
|
path.setAttribute("d", link.path);
|
|
6936
7341
|
path.setAttribute("stroke", "none");
|
|
6937
7342
|
path.setAttribute("fill-opacity", String(link.fillOpacity));
|
|
@@ -6947,11 +7352,11 @@ function renderLinks(parent, links, _nodePositions, animation) {
|
|
|
6947
7352
|
parent.appendChild(g);
|
|
6948
7353
|
}
|
|
6949
7354
|
function renderNodes(parent, nodes, animation) {
|
|
6950
|
-
const g =
|
|
7355
|
+
const g = createSVGElement3("g");
|
|
6951
7356
|
g.setAttribute("class", "oc-sankey-nodes");
|
|
6952
7357
|
for (let i = 0; i < nodes.length; i++) {
|
|
6953
7358
|
const node = nodes[i];
|
|
6954
|
-
const nodeG =
|
|
7359
|
+
const nodeG = createSVGElement3("g");
|
|
6955
7360
|
nodeG.setAttribute("class", "oc-sankey-node");
|
|
6956
7361
|
nodeG.setAttribute("data-mark-id", `node-${node.nodeId}`);
|
|
6957
7362
|
nodeG.setAttribute("data-node-id", node.nodeId);
|
|
@@ -6959,8 +7364,8 @@ function renderNodes(parent, nodes, animation) {
|
|
|
6959
7364
|
nodeG.setAttribute("aria-label", node.aria.label);
|
|
6960
7365
|
}
|
|
6961
7366
|
stampAnimationAttrs2(nodeG, node, i, animation);
|
|
6962
|
-
const rect =
|
|
6963
|
-
|
|
7367
|
+
const rect = createSVGElement3("rect");
|
|
7368
|
+
setAttrs3(rect, {
|
|
6964
7369
|
x: node.x,
|
|
6965
7370
|
y: node.y,
|
|
6966
7371
|
width: node.width,
|
|
@@ -6981,13 +7386,13 @@ function renderNodes(parent, nodes, animation) {
|
|
|
6981
7386
|
parent.appendChild(g);
|
|
6982
7387
|
}
|
|
6983
7388
|
function renderLabels(parent, nodes, measureText) {
|
|
6984
|
-
const g =
|
|
7389
|
+
const g = createSVGElement3("g");
|
|
6985
7390
|
g.setAttribute("class", "oc-sankey-labels");
|
|
6986
7391
|
for (const node of nodes) {
|
|
6987
7392
|
const { label } = node;
|
|
6988
7393
|
if (!label.visible) continue;
|
|
6989
|
-
const text =
|
|
6990
|
-
|
|
7394
|
+
const text = createSVGElement3("text");
|
|
7395
|
+
setAttrs3(text, { x: label.x, y: label.y });
|
|
6991
7396
|
applyTextStyle2(text, label.style);
|
|
6992
7397
|
if (label.maxWidth !== void 0 && label.maxWidth > 0) {
|
|
6993
7398
|
const fontSize = label.style.fontSize ?? 12;
|
|
@@ -6998,7 +7403,7 @@ function renderLabels(parent, nodes, measureText) {
|
|
|
6998
7403
|
const totalHeight = (lines.length - 1) * lineHeight;
|
|
6999
7404
|
const startY = label.y - totalHeight / 2;
|
|
7000
7405
|
for (let i = 0; i < lines.length; i++) {
|
|
7001
|
-
const tspan =
|
|
7406
|
+
const tspan = createSVGElement3("tspan");
|
|
7002
7407
|
tspan.setAttribute("x", String(label.x));
|
|
7003
7408
|
tspan.setAttribute("y", String(startY + i * lineHeight));
|
|
7004
7409
|
tspan.textContent = lines[i];
|
|
@@ -7016,10 +7421,10 @@ function renderLabels(parent, nodes, measureText) {
|
|
|
7016
7421
|
}
|
|
7017
7422
|
function renderSankeySVG(layout, animation) {
|
|
7018
7423
|
const { width, height } = layout.dimensions;
|
|
7019
|
-
const svg =
|
|
7020
|
-
|
|
7424
|
+
const svg = createSVGElement3("svg");
|
|
7425
|
+
setAttrs3(svg, {
|
|
7021
7426
|
viewBox: `0 0 ${width} ${height}`,
|
|
7022
|
-
xmlns:
|
|
7427
|
+
xmlns: SVG_NS4,
|
|
7023
7428
|
overflow: "visible"
|
|
7024
7429
|
});
|
|
7025
7430
|
svg.style.height = `${height}px`;
|
|
@@ -7037,9 +7442,9 @@ function renderSankeySVG(layout, animation) {
|
|
|
7037
7442
|
const easeVar = EASE_VAR_MAP2[animation.ease] || EASE_VAR_MAP2.smooth;
|
|
7038
7443
|
svg.style.setProperty("--oc-animation-ease", easeVar);
|
|
7039
7444
|
}
|
|
7040
|
-
const bg =
|
|
7445
|
+
const bg = createSVGElement3("rect");
|
|
7041
7446
|
bg.setAttribute("class", "oc-background");
|
|
7042
|
-
|
|
7447
|
+
setAttrs3(bg, {
|
|
7043
7448
|
x: 0,
|
|
7044
7449
|
y: 0,
|
|
7045
7450
|
width,
|
|
@@ -7048,14 +7453,14 @@ function renderSankeySVG(layout, animation) {
|
|
|
7048
7453
|
});
|
|
7049
7454
|
svg.appendChild(bg);
|
|
7050
7455
|
const nodePositions = buildNodePositionMap(layout.nodes);
|
|
7051
|
-
const defs =
|
|
7456
|
+
const defs = createSVGElement3("defs");
|
|
7052
7457
|
renderGradientDefs(defs, layout.links, nodePositions);
|
|
7053
7458
|
svg.appendChild(defs);
|
|
7054
7459
|
renderLinks(svg, layout.links, nodePositions, animation);
|
|
7055
7460
|
renderNodes(svg, layout.nodes, animation);
|
|
7056
7461
|
renderLabels(svg, layout.nodes, layout.measureText);
|
|
7057
7462
|
renderLegend2(svg, layout.legend);
|
|
7058
|
-
|
|
7463
|
+
renderChrome3(svg, layout);
|
|
7059
7464
|
if (layout.watermark) {
|
|
7060
7465
|
renderBrand2(svg, layout);
|
|
7061
7466
|
}
|
|
@@ -7063,7 +7468,7 @@ function renderSankeySVG(layout, animation) {
|
|
|
7063
7468
|
}
|
|
7064
7469
|
|
|
7065
7470
|
// src/sankey-mount.ts
|
|
7066
|
-
function
|
|
7471
|
+
function resolveDarkMode4(mode) {
|
|
7067
7472
|
if (mode === "force") return true;
|
|
7068
7473
|
if (mode === "off" || mode === void 0) return false;
|
|
7069
7474
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
@@ -7095,7 +7500,7 @@ function createSankey(container, spec, options) {
|
|
|
7095
7500
|
}
|
|
7096
7501
|
function compile() {
|
|
7097
7502
|
const { width, height } = getContainerDimensions();
|
|
7098
|
-
const darkMode =
|
|
7503
|
+
const darkMode = resolveDarkMode4(options?.darkMode);
|
|
7099
7504
|
const compileOpts = {
|
|
7100
7505
|
width,
|
|
7101
7506
|
height,
|
|
@@ -7270,7 +7675,7 @@ function createSankey(container, spec, options) {
|
|
|
7270
7675
|
const animation = shouldAnimate ? currentLayout.animation : void 0;
|
|
7271
7676
|
svgElement = renderSankeySVG(currentLayout, animation);
|
|
7272
7677
|
container.appendChild(svgElement);
|
|
7273
|
-
const isDark =
|
|
7678
|
+
const isDark = resolveDarkMode4(options?.darkMode);
|
|
7274
7679
|
if (isDark) {
|
|
7275
7680
|
container.classList.add("oc-dark");
|
|
7276
7681
|
} else {
|
|
@@ -7359,7 +7764,7 @@ function createSankey(container, spec, options) {
|
|
|
7359
7764
|
const animation = shouldAnimate ? currentLayout.animation : void 0;
|
|
7360
7765
|
svgElement = renderSankeySVG(currentLayout, animation);
|
|
7361
7766
|
container.appendChild(svgElement);
|
|
7362
|
-
const isDark =
|
|
7767
|
+
const isDark = resolveDarkMode4(options?.darkMode);
|
|
7363
7768
|
if (isDark) {
|
|
7364
7769
|
container.classList.add("oc-dark");
|
|
7365
7770
|
} else {
|
|
@@ -7596,7 +8001,7 @@ var EASE_VAR_MAP3 = {
|
|
|
7596
8001
|
smooth: "var(--oc-ease-smooth)",
|
|
7597
8002
|
snappy: "var(--oc-ease-snappy)"
|
|
7598
8003
|
};
|
|
7599
|
-
var
|
|
8004
|
+
var BRAND_URL4 = "https://tryopendata.ai";
|
|
7600
8005
|
function renderChromeBlock(layout, position) {
|
|
7601
8006
|
const chrome = layout.chrome;
|
|
7602
8007
|
if (position === "header") {
|
|
@@ -7842,7 +8247,7 @@ function renderTable(layout, container, opts) {
|
|
|
7842
8247
|
brand.className = "oc-table-ref";
|
|
7843
8248
|
brand.style.cssText = "text-align: right; padding: 4px 8px;";
|
|
7844
8249
|
const brandLink = document.createElement("a");
|
|
7845
|
-
brandLink.href =
|
|
8250
|
+
brandLink.href = BRAND_URL4;
|
|
7846
8251
|
brandLink.target = "_blank";
|
|
7847
8252
|
brandLink.rel = "noopener";
|
|
7848
8253
|
brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE4}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : "sans-serif"};`;
|
|
@@ -7865,7 +8270,7 @@ function renderTable(layout, container, opts) {
|
|
|
7865
8270
|
}
|
|
7866
8271
|
|
|
7867
8272
|
// src/table-mount.ts
|
|
7868
|
-
function
|
|
8273
|
+
function resolveDarkMode5(mode) {
|
|
7869
8274
|
if (mode === "force") return true;
|
|
7870
8275
|
if (mode === "off" || mode === void 0) return false;
|
|
7871
8276
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
@@ -7933,7 +8338,7 @@ function createTable(container, spec, options) {
|
|
|
7933
8338
|
}
|
|
7934
8339
|
function compile() {
|
|
7935
8340
|
const state = getState();
|
|
7936
|
-
const darkMode =
|
|
8341
|
+
const darkMode = resolveDarkMode5(options?.darkMode);
|
|
7937
8342
|
const { width } = getContainerDimensions();
|
|
7938
8343
|
const compileOpts = {
|
|
7939
8344
|
width,
|
|
@@ -7995,7 +8400,7 @@ function createTable(container, spec, options) {
|
|
|
7995
8400
|
if (isFirstRender) {
|
|
7996
8401
|
isFirstRender = false;
|
|
7997
8402
|
}
|
|
7998
|
-
const isDark =
|
|
8403
|
+
const isDark = resolveDarkMode5(options?.darkMode);
|
|
7999
8404
|
if (isDark) {
|
|
8000
8405
|
container.classList.add("oc-dark");
|
|
8001
8406
|
} else {
|
|
@@ -8150,7 +8555,7 @@ function createTable(container, spec, options) {
|
|
|
8150
8555
|
throw new Error(`Unsupported export format: ${format}`);
|
|
8151
8556
|
}
|
|
8152
8557
|
const state = getState();
|
|
8153
|
-
const darkMode =
|
|
8558
|
+
const darkMode = resolveDarkMode5(options?.darkMode);
|
|
8154
8559
|
const { width } = getContainerDimensions();
|
|
8155
8560
|
const fullLayout = compileTable(currentSpec, {
|
|
8156
8561
|
width,
|
|
@@ -8232,30 +8637,30 @@ function createTable(container, spec, options) {
|
|
|
8232
8637
|
import { compileTileMap } from "@opendata-ai/openchart-engine";
|
|
8233
8638
|
|
|
8234
8639
|
// src/tilemap-renderer.ts
|
|
8235
|
-
var
|
|
8236
|
-
var
|
|
8237
|
-
var
|
|
8640
|
+
var SVG_NS5 = "http://www.w3.org/2000/svg";
|
|
8641
|
+
var XLINK_NS4 = "http://www.w3.org/1999/xlink";
|
|
8642
|
+
var BRAND_URL5 = "https://tryopendata.ai";
|
|
8238
8643
|
var EASE_VAR_MAP4 = {
|
|
8239
8644
|
smooth: "var(--oc-ease-smooth)",
|
|
8240
8645
|
snappy: "var(--oc-ease-snappy)"
|
|
8241
8646
|
};
|
|
8242
8647
|
var gradientIdCounter = 0;
|
|
8243
|
-
function
|
|
8244
|
-
return document.createElementNS(
|
|
8648
|
+
function createSVGElement4(tag) {
|
|
8649
|
+
return document.createElementNS(SVG_NS5, tag);
|
|
8245
8650
|
}
|
|
8246
|
-
function
|
|
8651
|
+
function setAttrs4(el, attrs) {
|
|
8247
8652
|
for (const [key, value] of Object.entries(attrs)) {
|
|
8248
8653
|
el.setAttribute(key, String(value));
|
|
8249
8654
|
}
|
|
8250
8655
|
}
|
|
8251
|
-
function
|
|
8252
|
-
const g =
|
|
8656
|
+
function renderChrome4(parent, layout) {
|
|
8657
|
+
const g = createSVGElement4("g");
|
|
8253
8658
|
g.setAttribute("class", "oc-chrome");
|
|
8254
8659
|
const { chrome } = layout;
|
|
8255
8660
|
const bottomOffset = layout.area.y + layout.area.height;
|
|
8256
8661
|
if (chrome.title) {
|
|
8257
|
-
const text =
|
|
8258
|
-
|
|
8662
|
+
const text = createSVGElement4("text");
|
|
8663
|
+
setAttrs4(text, { x: chrome.title.x, y: chrome.title.y });
|
|
8259
8664
|
text.setAttribute("class", "oc-title");
|
|
8260
8665
|
text.setAttribute("font-family", chrome.title.style.fontFamily);
|
|
8261
8666
|
text.setAttribute("font-size", String(chrome.title.style.fontSize));
|
|
@@ -8265,8 +8670,8 @@ function renderChrome3(parent, layout) {
|
|
|
8265
8670
|
g.appendChild(text);
|
|
8266
8671
|
}
|
|
8267
8672
|
if (chrome.subtitle) {
|
|
8268
|
-
const text =
|
|
8269
|
-
|
|
8673
|
+
const text = createSVGElement4("text");
|
|
8674
|
+
setAttrs4(text, { x: chrome.subtitle.x, y: chrome.subtitle.y });
|
|
8270
8675
|
text.setAttribute("class", "oc-subtitle");
|
|
8271
8676
|
text.setAttribute("font-family", chrome.subtitle.style.fontFamily);
|
|
8272
8677
|
text.setAttribute("font-size", String(chrome.subtitle.style.fontSize));
|
|
@@ -8279,8 +8684,8 @@ function renderChrome3(parent, layout) {
|
|
|
8279
8684
|
g.appendChild(text);
|
|
8280
8685
|
}
|
|
8281
8686
|
if (chrome.source) {
|
|
8282
|
-
const text =
|
|
8283
|
-
|
|
8687
|
+
const text = createSVGElement4("text");
|
|
8688
|
+
setAttrs4(text, { x: chrome.source.x, y: bottomOffset + chrome.source.y });
|
|
8284
8689
|
text.setAttribute("class", "oc-source");
|
|
8285
8690
|
text.setAttribute("font-family", chrome.source.style.fontFamily);
|
|
8286
8691
|
text.setAttribute("font-size", String(chrome.source.style.fontSize));
|
|
@@ -8293,8 +8698,8 @@ function renderChrome3(parent, layout) {
|
|
|
8293
8698
|
g.appendChild(text);
|
|
8294
8699
|
}
|
|
8295
8700
|
if (chrome.byline) {
|
|
8296
|
-
const text =
|
|
8297
|
-
|
|
8701
|
+
const text = createSVGElement4("text");
|
|
8702
|
+
setAttrs4(text, { x: chrome.byline.x, y: bottomOffset + chrome.byline.y });
|
|
8298
8703
|
text.setAttribute("class", "oc-byline");
|
|
8299
8704
|
text.setAttribute("font-family", chrome.byline.style.fontFamily);
|
|
8300
8705
|
text.setAttribute("font-size", String(chrome.byline.style.fontSize));
|
|
@@ -8307,8 +8712,8 @@ function renderChrome3(parent, layout) {
|
|
|
8307
8712
|
g.appendChild(text);
|
|
8308
8713
|
}
|
|
8309
8714
|
if (chrome.footer) {
|
|
8310
|
-
const text =
|
|
8311
|
-
|
|
8715
|
+
const text = createSVGElement4("text");
|
|
8716
|
+
setAttrs4(text, { x: chrome.footer.x, y: bottomOffset + chrome.footer.y });
|
|
8312
8717
|
text.setAttribute("class", "oc-footer");
|
|
8313
8718
|
text.setAttribute("font-family", chrome.footer.style.fontFamily);
|
|
8314
8719
|
text.setAttribute("font-size", String(chrome.footer.style.fontSize));
|
|
@@ -8322,7 +8727,7 @@ function renderChrome3(parent, layout) {
|
|
|
8322
8727
|
}
|
|
8323
8728
|
parent.appendChild(g);
|
|
8324
8729
|
}
|
|
8325
|
-
function
|
|
8730
|
+
function renderWatermark2(parent, layout) {
|
|
8326
8731
|
if (layout.width < 480) return;
|
|
8327
8732
|
const { width, height } = layout;
|
|
8328
8733
|
const { theme } = layout;
|
|
@@ -8330,14 +8735,14 @@ function renderWatermark(parent, layout) {
|
|
|
8330
8735
|
const rightEdge = width - padding;
|
|
8331
8736
|
const bottomEdge = height - padding;
|
|
8332
8737
|
const fill = theme.colors.axis;
|
|
8333
|
-
const a2 =
|
|
8334
|
-
a2.setAttribute("href",
|
|
8335
|
-
a2.setAttributeNS(
|
|
8738
|
+
const a2 = createSVGElement4("a");
|
|
8739
|
+
a2.setAttribute("href", BRAND_URL5);
|
|
8740
|
+
a2.setAttributeNS(XLINK_NS4, "xlink:href", BRAND_URL5);
|
|
8336
8741
|
a2.setAttribute("target", "_blank");
|
|
8337
8742
|
a2.setAttribute("rel", "noopener");
|
|
8338
8743
|
a2.setAttribute("class", "oc-chrome-ref");
|
|
8339
|
-
const text =
|
|
8340
|
-
|
|
8744
|
+
const text = createSVGElement4("text");
|
|
8745
|
+
setAttrs4(text, {
|
|
8341
8746
|
x: rightEdge,
|
|
8342
8747
|
y: bottomEdge,
|
|
8343
8748
|
"dominant-baseline": "alphabetic",
|
|
@@ -8347,14 +8752,14 @@ function renderWatermark(parent, layout) {
|
|
|
8347
8752
|
"fill-opacity": 0.55
|
|
8348
8753
|
});
|
|
8349
8754
|
text.style.setProperty("fill", fill);
|
|
8350
|
-
const trySpan =
|
|
8351
|
-
|
|
8755
|
+
const trySpan = createSVGElement4("tspan");
|
|
8756
|
+
setAttrs4(trySpan, { "font-weight": 500 });
|
|
8352
8757
|
trySpan.textContent = "try";
|
|
8353
|
-
const openDataSpan =
|
|
8354
|
-
|
|
8758
|
+
const openDataSpan = createSVGElement4("tspan");
|
|
8759
|
+
setAttrs4(openDataSpan, { "font-weight": 600, "font-size": 16 });
|
|
8355
8760
|
openDataSpan.textContent = "OpenData";
|
|
8356
|
-
const aiSpan =
|
|
8357
|
-
|
|
8761
|
+
const aiSpan = createSVGElement4("tspan");
|
|
8762
|
+
setAttrs4(aiSpan, { "font-weight": 500 });
|
|
8358
8763
|
aiSpan.textContent = ".ai";
|
|
8359
8764
|
text.appendChild(trySpan);
|
|
8360
8765
|
text.appendChild(openDataSpan);
|
|
@@ -8363,7 +8768,7 @@ function renderWatermark(parent, layout) {
|
|
|
8363
8768
|
parent.appendChild(a2);
|
|
8364
8769
|
}
|
|
8365
8770
|
function renderTiles(parent, tiles, animation) {
|
|
8366
|
-
const g =
|
|
8771
|
+
const g = createSVGElement4("g");
|
|
8367
8772
|
g.setAttribute("class", "oc-tilemap-tiles");
|
|
8368
8773
|
g.setAttribute("role", "list");
|
|
8369
8774
|
const tileDelays = [];
|
|
@@ -8379,7 +8784,7 @@ function renderTiles(parent, tiles, animation) {
|
|
|
8379
8784
|
}
|
|
8380
8785
|
for (let i = 0; i < tiles.length; i++) {
|
|
8381
8786
|
const tile = tiles[i];
|
|
8382
|
-
const tileGroup =
|
|
8787
|
+
const tileGroup = createSVGElement4("g");
|
|
8383
8788
|
tileGroup.setAttribute("class", "oc-tilemap-tile");
|
|
8384
8789
|
tileGroup.setAttribute("data-state", tile.stateCode);
|
|
8385
8790
|
tileGroup.setAttribute("role", "listitem");
|
|
@@ -8398,20 +8803,21 @@ function renderTiles(parent, tiles, animation) {
|
|
|
8398
8803
|
`${tileDelays[i]}ms`
|
|
8399
8804
|
);
|
|
8400
8805
|
}
|
|
8401
|
-
const rect =
|
|
8402
|
-
|
|
8806
|
+
const rect = createSVGElement4("rect");
|
|
8807
|
+
setAttrs4(rect, {
|
|
8403
8808
|
x: tile.x,
|
|
8404
8809
|
y: tile.y,
|
|
8405
8810
|
width: tile.size,
|
|
8406
8811
|
height: tile.size,
|
|
8407
8812
|
rx: tile.cornerRadius,
|
|
8408
8813
|
fill: tile.fill,
|
|
8814
|
+
"fill-opacity": tile.fillOpacity ?? 1,
|
|
8409
8815
|
stroke: tile.stroke,
|
|
8410
8816
|
"stroke-width": tile.strokeWidth
|
|
8411
8817
|
});
|
|
8412
8818
|
tileGroup.appendChild(rect);
|
|
8413
|
-
const codeLabel =
|
|
8414
|
-
|
|
8819
|
+
const codeLabel = createSVGElement4("text");
|
|
8820
|
+
setAttrs4(codeLabel, {
|
|
8415
8821
|
x: tile.label.x,
|
|
8416
8822
|
y: tile.label.y,
|
|
8417
8823
|
"text-anchor": "middle",
|
|
@@ -8427,8 +8833,8 @@ function renderTiles(parent, tiles, animation) {
|
|
|
8427
8833
|
codeLabel.textContent = tile.label.text;
|
|
8428
8834
|
tileGroup.appendChild(codeLabel);
|
|
8429
8835
|
if (tile.valueLabel.visible && tile.valueLabel.text) {
|
|
8430
|
-
const valueLabel =
|
|
8431
|
-
|
|
8836
|
+
const valueLabel = createSVGElement4("text");
|
|
8837
|
+
setAttrs4(valueLabel, {
|
|
8432
8838
|
x: tile.valueLabel.x,
|
|
8433
8839
|
y: tile.valueLabel.y,
|
|
8434
8840
|
"text-anchor": "middle",
|
|
@@ -8451,38 +8857,46 @@ function renderTiles(parent, tiles, animation) {
|
|
|
8451
8857
|
function renderGradientLegend(parent, layout) {
|
|
8452
8858
|
if (!layout.gradientLegend) return;
|
|
8453
8859
|
const { gradientLegend } = layout;
|
|
8454
|
-
const g =
|
|
8860
|
+
const g = createSVGElement4("g");
|
|
8455
8861
|
g.setAttribute("class", "oc-tilemap-legend");
|
|
8456
|
-
const defs = parent.querySelector("defs") ||
|
|
8862
|
+
const defs = parent.querySelector("defs") || createSVGElement4("defs");
|
|
8457
8863
|
const exists = parent.querySelector("defs");
|
|
8458
8864
|
if (!exists) {
|
|
8459
8865
|
parent.insertBefore(defs, parent.firstChild);
|
|
8460
8866
|
}
|
|
8461
8867
|
const gradientId = `oc-tilemap-legend-gradient-${gradientIdCounter++}`;
|
|
8462
|
-
const grad =
|
|
8868
|
+
const grad = createSVGElement4("linearGradient");
|
|
8463
8869
|
grad.id = gradientId;
|
|
8464
8870
|
grad.setAttribute("x1", "0%");
|
|
8465
8871
|
grad.setAttribute("y1", "0%");
|
|
8466
8872
|
grad.setAttribute("x2", "100%");
|
|
8467
8873
|
grad.setAttribute("y2", "0%");
|
|
8468
8874
|
for (const stop of gradientLegend.colorStops) {
|
|
8469
|
-
const s =
|
|
8470
|
-
|
|
8875
|
+
const s = createSVGElement4("stop");
|
|
8876
|
+
const attrs = {
|
|
8877
|
+
offset: `${stop.offset * 100}%`,
|
|
8878
|
+
"stop-color": stop.color
|
|
8879
|
+
};
|
|
8880
|
+
if (stop.opacity !== void 0) {
|
|
8881
|
+
attrs["stop-opacity"] = stop.opacity;
|
|
8882
|
+
}
|
|
8883
|
+
setAttrs4(s, attrs);
|
|
8471
8884
|
grad.appendChild(s);
|
|
8472
8885
|
}
|
|
8473
8886
|
defs.appendChild(grad);
|
|
8474
|
-
const
|
|
8475
|
-
|
|
8887
|
+
const barHeight = gradientLegend.bounds.height;
|
|
8888
|
+
const bar = createSVGElement4("rect");
|
|
8889
|
+
setAttrs4(bar, {
|
|
8476
8890
|
x: gradientLegend.bounds.x,
|
|
8477
8891
|
y: gradientLegend.bounds.y,
|
|
8478
8892
|
width: gradientLegend.bounds.width,
|
|
8479
|
-
height:
|
|
8480
|
-
rx:
|
|
8893
|
+
height: barHeight,
|
|
8894
|
+
rx: barHeight / 2,
|
|
8481
8895
|
fill: `url(#${gradientId})`
|
|
8482
8896
|
});
|
|
8483
8897
|
g.appendChild(bar);
|
|
8484
|
-
const minText =
|
|
8485
|
-
|
|
8898
|
+
const minText = createSVGElement4("text");
|
|
8899
|
+
setAttrs4(minText, {
|
|
8486
8900
|
x: gradientLegend.bounds.x,
|
|
8487
8901
|
y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
|
|
8488
8902
|
"text-anchor": "start",
|
|
@@ -8496,8 +8910,8 @@ function renderGradientLegend(parent, layout) {
|
|
|
8496
8910
|
);
|
|
8497
8911
|
minText.textContent = gradientLegend.minLabel;
|
|
8498
8912
|
g.appendChild(minText);
|
|
8499
|
-
const maxText =
|
|
8500
|
-
|
|
8913
|
+
const maxText = createSVGElement4("text");
|
|
8914
|
+
setAttrs4(maxText, {
|
|
8501
8915
|
x: gradientLegend.bounds.x + gradientLegend.bounds.width,
|
|
8502
8916
|
y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
|
|
8503
8917
|
"text-anchor": "end",
|
|
@@ -8516,7 +8930,7 @@ function renderGradientLegend(parent, layout) {
|
|
|
8516
8930
|
function renderTileMapSVG(layout, opts) {
|
|
8517
8931
|
const { width, height, tiles, a11y, watermark, animation } = layout;
|
|
8518
8932
|
const animate = opts?.animate && animation?.enabled;
|
|
8519
|
-
const svg =
|
|
8933
|
+
const svg = createSVGElement4("svg");
|
|
8520
8934
|
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
8521
8935
|
svg.setAttribute("width", String(width));
|
|
8522
8936
|
svg.setAttribute("height", String(height));
|
|
@@ -8534,19 +8948,19 @@ function renderTileMapSVG(layout, opts) {
|
|
|
8534
8948
|
const easeVar = EASE_VAR_MAP4[animation.ease] || EASE_VAR_MAP4.smooth;
|
|
8535
8949
|
svg.style.setProperty("--oc-animation-ease", easeVar);
|
|
8536
8950
|
}
|
|
8537
|
-
const defs =
|
|
8951
|
+
const defs = createSVGElement4("defs");
|
|
8538
8952
|
svg.appendChild(defs);
|
|
8539
|
-
|
|
8953
|
+
renderChrome4(svg, layout);
|
|
8540
8954
|
renderTiles(svg, tiles, animate ? animation : void 0);
|
|
8541
8955
|
renderGradientLegend(svg, layout);
|
|
8542
8956
|
if (watermark) {
|
|
8543
|
-
|
|
8957
|
+
renderWatermark2(svg, layout);
|
|
8544
8958
|
}
|
|
8545
8959
|
return svg;
|
|
8546
8960
|
}
|
|
8547
8961
|
|
|
8548
8962
|
// src/tilemap-mount.ts
|
|
8549
|
-
function
|
|
8963
|
+
function resolveDarkMode6(mode) {
|
|
8550
8964
|
if (mode === "force") return true;
|
|
8551
8965
|
if (mode === "off" || mode === void 0) return false;
|
|
8552
8966
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
@@ -8574,7 +8988,7 @@ function createTileMap(container, spec, options) {
|
|
|
8574
8988
|
}
|
|
8575
8989
|
function compile() {
|
|
8576
8990
|
const { width, height } = getContainerDimensions();
|
|
8577
|
-
const darkMode =
|
|
8991
|
+
const darkMode = resolveDarkMode6(options?.darkMode);
|
|
8578
8992
|
const compileOpts = {
|
|
8579
8993
|
width,
|
|
8580
8994
|
height,
|
|
@@ -8736,7 +9150,7 @@ function createTileMap(container, spec, options) {
|
|
|
8736
9150
|
container.classList.remove("oc-tilemap-root", "oc-dark");
|
|
8737
9151
|
}
|
|
8738
9152
|
container.classList.add("oc-tilemap-root");
|
|
8739
|
-
if (
|
|
9153
|
+
if (resolveDarkMode6(options?.darkMode)) {
|
|
8740
9154
|
container.classList.add("oc-dark");
|
|
8741
9155
|
}
|
|
8742
9156
|
currentLayout = compile();
|
|
@@ -8758,6 +9172,7 @@ function createTileMap(container, spec, options) {
|
|
|
8758
9172
|
}
|
|
8759
9173
|
export {
|
|
8760
9174
|
attachKeyboardNav,
|
|
9175
|
+
createBarList,
|
|
8761
9176
|
createChart,
|
|
8762
9177
|
createGraph,
|
|
8763
9178
|
createSankey,
|