@bicharts/chart-host 0.6.83 → 0.6.85
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/README.md +43 -0
- package/dist/adapters.mjs +10 -4
- package/dist/card.mjs +2 -2
- package/dist/{chunk-2XWE6YFO.mjs → chunk-2ZUUIZWL.mjs} +115 -115
- package/dist/chunk-7HKVV5T5.mjs +198 -0
- package/dist/chunk-BQFG5HWZ.mjs +582 -0
- package/dist/{chunk-CJNONH6V.mjs → chunk-CDWNZHIN.mjs} +75 -592
- package/dist/{chunk-XIAL6EEC.mjs → chunk-DTOGTKAJ.mjs} +10 -2
- package/dist/{chunk-4KCSAHL5.mjs → chunk-GG4U66E4.mjs} +9 -9
- package/dist/{chunk-MGAO2DR7.mjs → chunk-IURROJT7.mjs} +95 -193
- package/dist/{chunk-7JWL6LYE.mjs → chunk-MR6TL7QZ.mjs} +1 -1
- package/dist/chunk-QIRLWJBI.mjs +665 -0
- package/dist/chunk-ZGQURBRJ.mjs +265 -0
- package/dist/index.mjs +148 -285
- package/dist/legend-fit.mjs +1613 -0
- package/dist/page.mjs +4 -1
- package/dist/react.mjs +8 -5
- package/dist/testing.mjs +1 -1
- package/dist/types/contract.d.ts +35 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/lasso.d.ts +143 -0
- package/dist/types/legendFit/frameFit.d.ts +89 -0
- package/dist/types/legendFit/frameFitDom.d.ts +70 -0
- package/dist/types/legendFit/index.d.ts +4 -0
- package/dist/types/legendFit/legendReconcile.d.ts +295 -0
- package/dist/types/legendFit/legendReconcileDom.d.ts +109 -0
- package/dist/types/qualifyGroups.d.ts +61 -3
- package/dist/types/selection.d.ts +51 -0
- package/package.json +6 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
|
|
2
|
+
import {
|
|
3
|
+
SNAPSHOT_SVG_ATTR
|
|
4
|
+
} from "./chunk-MR6TL7QZ.mjs";
|
|
5
|
+
|
|
6
|
+
// src/snapshot.ts
|
|
7
|
+
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
8
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
9
|
+
var XHTML_NS = "http://www.w3.org/1999/xhtml";
|
|
10
|
+
function toBase64(str) {
|
|
11
|
+
const bytes = new TextEncoder().encode(str);
|
|
12
|
+
let binary = "";
|
|
13
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
14
|
+
return btoa(binary);
|
|
15
|
+
}
|
|
16
|
+
function viewOf(el) {
|
|
17
|
+
return el.ownerDocument && el.ownerDocument.defaultView || globalThis;
|
|
18
|
+
}
|
|
19
|
+
var MAX_STYLED_ELEMENTS = 4e3;
|
|
20
|
+
var TEXT_FONT_PROPS = ["font-family", "font-size", "font-weight", "font-style"];
|
|
21
|
+
function inlineTextFonts(original, clone) {
|
|
22
|
+
const view = viewOf(original);
|
|
23
|
+
if (!view || typeof view.getComputedStyle !== "function") return;
|
|
24
|
+
const src = original.querySelectorAll("text");
|
|
25
|
+
const dst = clone.querySelectorAll("text");
|
|
26
|
+
if (src.length !== dst.length) return;
|
|
27
|
+
const n = Math.min(src.length, MAX_STYLED_ELEMENTS);
|
|
28
|
+
for (let i = 0; i < n; i++) {
|
|
29
|
+
let cs;
|
|
30
|
+
try {
|
|
31
|
+
cs = view.getComputedStyle(src[i]);
|
|
32
|
+
} catch {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const style = dst[i].style;
|
|
36
|
+
if (!style) continue;
|
|
37
|
+
for (const prop of TEXT_FONT_PROPS) {
|
|
38
|
+
const v = cs.getPropertyValue(prop);
|
|
39
|
+
if (v && !style.getPropertyValue(prop)) style.setProperty(prop, v);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function svgToDataUrl(svg) {
|
|
44
|
+
const cloned = svg.cloneNode(true);
|
|
45
|
+
const w = svg.clientWidth || svg.getBoundingClientRect && svg.getBoundingClientRect().width || 600;
|
|
46
|
+
const h = svg.clientHeight || svg.getBoundingClientRect && svg.getBoundingClientRect().height || 400;
|
|
47
|
+
cloned.setAttribute("width", String(Math.round(w)));
|
|
48
|
+
cloned.setAttribute("height", String(Math.round(h)));
|
|
49
|
+
if (!cloned.getAttribute("xmlns")) cloned.setAttribute("xmlns", SVG_NS);
|
|
50
|
+
try {
|
|
51
|
+
inlineTextFonts(svg, cloned);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
return "data:image/svg+xml;base64," + toBase64(new XMLSerializer().serializeToString(cloned));
|
|
55
|
+
}
|
|
56
|
+
function tableHostOf(root) {
|
|
57
|
+
if (!root || typeof root.querySelector !== "function") return null;
|
|
58
|
+
if (root.namespaceURI === SVG_NS) return null;
|
|
59
|
+
const firstSvg = root.querySelector("svg");
|
|
60
|
+
for (const sel of [".lch-table-frame", "table"]) {
|
|
61
|
+
const host = root.querySelector(sel);
|
|
62
|
+
if (host && (!firstSvg || host.contains(firstSvg))) return host;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function chartSvgOf(root) {
|
|
67
|
+
if (!root || typeof root.querySelector !== "function") return null;
|
|
68
|
+
return root.querySelector(`svg[${SNAPSHOT_SVG_ATTR}]`) ?? root.querySelector("svg");
|
|
69
|
+
}
|
|
70
|
+
function inlinePx(v) {
|
|
71
|
+
const m = /^\s*([0-9.]+)px\s*$/.exec(v || "");
|
|
72
|
+
return m ? parseFloat(m[1]) : 0;
|
|
73
|
+
}
|
|
74
|
+
function htmlNaturalSize(el) {
|
|
75
|
+
const r = el.getBoundingClientRect ? el.getBoundingClientRect() : null;
|
|
76
|
+
const w = el.clientWidth || r && r.width || inlinePx(el.style && el.style.width) || 600;
|
|
77
|
+
const h = el.clientHeight || r && r.height || inlinePx(el.style && el.style.height) || 400;
|
|
78
|
+
return { width: Math.round(w), height: Math.round(h) };
|
|
79
|
+
}
|
|
80
|
+
var HTML_STYLE_PROPS = [
|
|
81
|
+
"display",
|
|
82
|
+
"box-sizing",
|
|
83
|
+
"width",
|
|
84
|
+
"height",
|
|
85
|
+
"margin-top",
|
|
86
|
+
"margin-right",
|
|
87
|
+
"margin-bottom",
|
|
88
|
+
"margin-left",
|
|
89
|
+
"padding-top",
|
|
90
|
+
"padding-right",
|
|
91
|
+
"padding-bottom",
|
|
92
|
+
"padding-left",
|
|
93
|
+
"border-top-width",
|
|
94
|
+
"border-right-width",
|
|
95
|
+
"border-bottom-width",
|
|
96
|
+
"border-left-width",
|
|
97
|
+
"border-top-style",
|
|
98
|
+
"border-right-style",
|
|
99
|
+
"border-bottom-style",
|
|
100
|
+
"border-left-style",
|
|
101
|
+
"border-top-color",
|
|
102
|
+
"border-right-color",
|
|
103
|
+
"border-bottom-color",
|
|
104
|
+
"border-left-color",
|
|
105
|
+
"border-collapse",
|
|
106
|
+
"border-spacing",
|
|
107
|
+
"background-color",
|
|
108
|
+
"color",
|
|
109
|
+
"opacity",
|
|
110
|
+
"font-family",
|
|
111
|
+
"font-size",
|
|
112
|
+
"font-weight",
|
|
113
|
+
"font-style",
|
|
114
|
+
"line-height",
|
|
115
|
+
"letter-spacing",
|
|
116
|
+
"text-align",
|
|
117
|
+
"vertical-align",
|
|
118
|
+
"white-space",
|
|
119
|
+
"text-overflow",
|
|
120
|
+
"text-decoration-line",
|
|
121
|
+
"flex-direction",
|
|
122
|
+
"flex-wrap",
|
|
123
|
+
"flex-grow",
|
|
124
|
+
"flex-shrink",
|
|
125
|
+
"flex-basis",
|
|
126
|
+
"align-items",
|
|
127
|
+
"justify-content",
|
|
128
|
+
"position",
|
|
129
|
+
"top",
|
|
130
|
+
"right",
|
|
131
|
+
"bottom",
|
|
132
|
+
"left"
|
|
133
|
+
];
|
|
134
|
+
function inlineHtmlStyles(original, clone) {
|
|
135
|
+
const view = viewOf(original);
|
|
136
|
+
if (!view || typeof view.getComputedStyle !== "function") return;
|
|
137
|
+
const src = [original, ...Array.from(original.querySelectorAll("*"))];
|
|
138
|
+
const dst = [clone, ...Array.from(clone.querySelectorAll("*"))];
|
|
139
|
+
if (src.length !== dst.length) return;
|
|
140
|
+
let styled = 0;
|
|
141
|
+
for (let i = 0; i < src.length && styled < MAX_STYLED_ELEMENTS; i++) {
|
|
142
|
+
const o = src[i], c = dst[i];
|
|
143
|
+
if (o.namespaceURI !== XHTML_NS || !c.style) continue;
|
|
144
|
+
let cs;
|
|
145
|
+
try {
|
|
146
|
+
cs = view.getComputedStyle(o);
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
for (const prop of HTML_STYLE_PROPS) {
|
|
151
|
+
const v = cs.getPropertyValue(prop);
|
|
152
|
+
if (v) c.style.setProperty(prop, v);
|
|
153
|
+
}
|
|
154
|
+
c.style.setProperty("overflow", "hidden");
|
|
155
|
+
styled++;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
inlineTextFonts(original, clone);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function htmlToSvgDataUrl(el) {
|
|
163
|
+
const { width, height } = htmlNaturalSize(el);
|
|
164
|
+
const clone = el.cloneNode(true);
|
|
165
|
+
inlineHtmlStyles(el, clone);
|
|
166
|
+
clone.style.setProperty("width", `${width}px`);
|
|
167
|
+
clone.style.setProperty("height", `${height}px`);
|
|
168
|
+
clone.style.setProperty("margin", "0");
|
|
169
|
+
let xhtml = new XMLSerializer().serializeToString(clone);
|
|
170
|
+
if (!/^<[^>]*\sxmlns=/.test(xhtml)) xhtml = xhtml.replace(/^<([a-zA-Z0-9-]+)/, `<$1 xmlns="${XHTML_NS}"`);
|
|
171
|
+
const svg = `<svg xmlns="${SVG_NS}" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><foreignObject x="0" y="0" width="${width}" height="${height}">${xhtml}</foreignObject></svg>`;
|
|
172
|
+
return { url: "data:image/svg+xml;base64," + toBase64(svg), width, height };
|
|
173
|
+
}
|
|
174
|
+
function svgNaturalSize(svg) {
|
|
175
|
+
const w = svg.clientWidth || svg.getBoundingClientRect && svg.getBoundingClientRect().width || 600;
|
|
176
|
+
const h = svg.clientHeight || svg.getBoundingClientRect && svg.getBoundingClientRect().height || 400;
|
|
177
|
+
return { width: Math.round(w), height: Math.round(h) };
|
|
178
|
+
}
|
|
179
|
+
function rasterizeSvgToPngDataUrl(svgDataUrl, width, height, opts = {}) {
|
|
180
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
let img;
|
|
183
|
+
try {
|
|
184
|
+
img = new Image();
|
|
185
|
+
} catch (e) {
|
|
186
|
+
opts.onWarn?.("snapshot-no-image", e instanceof Error ? e.message : String(e));
|
|
187
|
+
resolve(null);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
opts.onWarn?.("snapshot-raster-timeout", { width, height, timeoutMs });
|
|
192
|
+
resolve(null);
|
|
193
|
+
}, timeoutMs);
|
|
194
|
+
img.onload = () => {
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
try {
|
|
197
|
+
const cap = opts.maxSide && opts.maxSide > 0 ? opts.maxSide : Math.max(width, height);
|
|
198
|
+
const scale = Math.min(1, cap / Math.max(width, height));
|
|
199
|
+
const cw = Math.max(1, Math.round(width * scale));
|
|
200
|
+
const ch = Math.max(1, Math.round(height * scale));
|
|
201
|
+
const canvas = document.createElement("canvas");
|
|
202
|
+
canvas.width = cw;
|
|
203
|
+
canvas.height = ch;
|
|
204
|
+
const c2d = canvas.getContext("2d");
|
|
205
|
+
if (!c2d) {
|
|
206
|
+
resolve(null);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
c2d.fillStyle = "white";
|
|
210
|
+
c2d.fillRect(0, 0, cw, ch);
|
|
211
|
+
c2d.drawImage(img, 0, 0, cw, ch);
|
|
212
|
+
resolve(canvas.toDataURL("image/png"));
|
|
213
|
+
} catch (e) {
|
|
214
|
+
opts.onWarn?.("snapshot-raster-draw", e instanceof Error ? e.message : String(e));
|
|
215
|
+
resolve(null);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
img.onerror = () => {
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
opts.onWarn?.("snapshot-raster-decode", { width, height });
|
|
221
|
+
resolve(null);
|
|
222
|
+
};
|
|
223
|
+
img.src = svgDataUrl;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async function captureSvgSnapshot(root, opts = {}) {
|
|
227
|
+
if (!root) return null;
|
|
228
|
+
const table = root instanceof SVGSVGElement ? null : tableHostOf(root);
|
|
229
|
+
const svg = table ? null : root instanceof SVGSVGElement ? root : chartSvgOf(root);
|
|
230
|
+
if (!table && !svg) return null;
|
|
231
|
+
let svgUrl;
|
|
232
|
+
let width, height;
|
|
233
|
+
try {
|
|
234
|
+
if (table) {
|
|
235
|
+
const r = htmlToSvgDataUrl(table);
|
|
236
|
+
svgUrl = r.url;
|
|
237
|
+
width = r.width;
|
|
238
|
+
height = r.height;
|
|
239
|
+
} else {
|
|
240
|
+
svgUrl = svgToDataUrl(svg);
|
|
241
|
+
({ width, height } = svgNaturalSize(svg));
|
|
242
|
+
}
|
|
243
|
+
} catch (e) {
|
|
244
|
+
opts.onWarn?.("snapshot-serialize", e instanceof Error ? e.message : String(e));
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
const png = await rasterizeSvgToPngDataUrl(svgUrl, width, height, opts);
|
|
249
|
+
return png || svgUrl;
|
|
250
|
+
} catch (e) {
|
|
251
|
+
opts.onWarn?.("snapshot-raster-unexpected", e instanceof Error ? e.message : String(e));
|
|
252
|
+
return svgUrl;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export {
|
|
257
|
+
svgToDataUrl,
|
|
258
|
+
tableHostOf,
|
|
259
|
+
chartSvgOf,
|
|
260
|
+
htmlNaturalSize,
|
|
261
|
+
htmlToSvgDataUrl,
|
|
262
|
+
svgNaturalSize,
|
|
263
|
+
rasterizeSvgToPngDataUrl,
|
|
264
|
+
captureSvgSnapshot
|
|
265
|
+
};
|