// Text sizing, measuring, and truncation helpers. (function (root) { const chartUtils = root.chartUtils; const text = chartUtils.text; text.font = (options = {}) => { const fontWeight = options.fontWeight || options.weight || "normal"; const fontSize = text.fontSize(options.fontSize || options.size || 12); const fontFamily = options.fontFamily || options.family || "Arial"; return `${fontWeight} ${fontSize}px ${fontFamily}`; }; text.fontSize = value => { if (typeof value === "number") return value; const parsed = parseFloat(value); return Number.isFinite(parsed) ? parsed : 12; }; text.estimate = (value, options = {}) => { const valueText = value == null ? "" : String(value); const fontSize = text.fontSize(options.fontSize || options.size || 12); const factor = options.factor == null ? 0.6 : options.factor; return { width: valueText.length * fontSize * factor, height: fontSize * 1.2, }; }; text.measure = (context, value, options = {}) => { const valueText = value == null ? "" : String(value); const fontSize = text.fontSize(options.fontSize || options.size || 12); const fontFamily = options.fontFamily || options.family || null; const fontWeight = options.fontWeight || options.weight || "normal"; if (context && typeof context.append === "function") { const node = context.append("text") .attr("visibility", "hidden") .attr("x", -9999) .attr("y", -9999) .style("font-size", `${fontSize}px`) .style("font-weight", fontWeight) .text(valueText); if (fontFamily) node.style("font-family", fontFamily); const element = node.node(); const width = element.getComputedTextLength ? element.getComputedTextLength() : element.getBBox().width; const height = element.getBBox().height || fontSize * 1.2; node.remove(); return { width, height }; } if (typeof document !== "undefined") { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); ctx.font = text.font({ fontSize, fontFamily, fontWeight }); return { width: ctx.measureText(valueText).width, height: fontSize * 1.2, }; } return text.estimate(valueText, options); }; text.contextSize = (context, value, options = {}) => { const valueText = value == null ? "" : String(value); const fontSize = text.fontSize(options.fontSize || options.size || 12); if (context && typeof context.measureText === "function") { return { width: context.measureText(valueText).width, height: fontSize * 1.2, }; } return text.measure(null, valueText, options); }; text.contextWidth = (context, value, options = {}) => text.contextSize(context, value, options).width; text.nodeSize = (node, options = {}) => { if (!node) return { width: 0, height: 0 }; const method = options.method || "auto"; const canUseComputed = typeof node.getComputedTextLength === "function"; const canUseBBox = typeof node.getBBox === "function"; let bbox = null; let width = null; const readBBox = () => { if (!canUseBBox) return null; try { return node.getBBox(); } catch (error) { return null; } }; if ((method === "computed" || method === "auto") && canUseComputed) { try { width = node.getComputedTextLength(); } catch (error) { width = null; } } if ((method === "bbox" || !Number.isFinite(width)) && canUseBBox) { bbox = readBBox(); if (bbox && Number.isFinite(bbox.width)) { width = bbox.width; } } if (!Number.isFinite(width)) { width = text.estimate(node.textContent || "", options).width; } if (!bbox && canUseBBox) { bbox = readBBox(); } const fallbackHeight = text.fontSize(options.fontSize || options.size || 12) * 1.2; const height = bbox && Number.isFinite(bbox.height) ? bbox.height : fallbackHeight; const x = bbox && Number.isFinite(bbox.x) ? bbox.x : 0; const y = bbox && Number.isFinite(bbox.y) ? bbox.y : 0; return { width, height, x, y }; }; text.nodeWidth = (node, options = {}) => text.nodeSize(node, options).width; text.truncate = (value, maxWidth, options = {}) => { const valueText = value == null ? "" : String(value); const ellipsis = options.ellipsis == null ? "..." : options.ellipsis; const measure = candidate => text.measure(options.context, candidate, options).width; if (measure(valueText) <= maxWidth) return valueText; if (maxWidth <= 0 || measure(ellipsis) > maxWidth) return ""; let low = 0; let high = valueText.length; while (low < high) { const mid = Math.ceil((low + high) / 2); if (measure(valueText.slice(0, mid) + ellipsis) <= maxWidth) { low = mid; } else { high = mid - 1; } } return valueText.slice(0, low) + ellipsis; }; })(globalThis);