Spaces:
Sleeping
Sleeping
| // 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); | |