@tanstack/charts
Version:
A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.
137 lines (136 loc) • 4.7 kB
JavaScript
import { renderFocusGuideLayer } from "./svg-renderer.js";
function renderFocusGuideLayerWithRenderer(svg, scene, nodes, placement, options, renderSvg) {
const document = svg.ownerDocument;
const key = `focus-guide-layer:${placement}`;
const wrapper = {
kind: "group",
key,
className: `ts-chart__focus-guide-layer ts-chart__focus-guide-layer--${placement}`,
ariaHidden: true,
children: nodes
};
const markup = renderSvg(
{
...scene,
nodes: [wrapper],
focusGuides: void 0
},
options
);
const root = parseSvgMarkup(document, markup);
const layer = root ? keyedElement(root, key) : void 0;
if (!root || !layer || layer.localName !== "g") {
throw new Error(
`The SVG renderer must preserve a g[data-ts-key="${key}"] element when serializing focus guides.`
);
}
layer.classList.add(
"ts-chart__focus-guide-layer",
`ts-chart__focus-guide-layer--${placement}`
);
layer.setAttribute("data-ts-focus-layer", placement);
layer.setAttribute("data-ts-focus-guide-layer", placement);
layer.setAttribute("aria-hidden", "true");
layer.setAttribute("visibility", nodes.length ? "visible" : "hidden");
mergeFocusGuideClipFallback(
document,
layer,
nodes,
placement,
options.idPrefix ?? ""
);
copyMissingRendererDefinitions(svg, root, layer, key);
return layer.outerHTML;
}
function mergeFocusGuideClipFallback(document, layer, nodes, placement, idPrefix) {
const fallback = parseSvgFragment(
document,
renderFocusGuideLayer(nodes, placement, idPrefix)
);
if (!fallback) return;
for (const source of keyedElements(fallback)) {
const clipPath = source.getAttribute("clip-path");
const key = source.getAttribute("data-ts-key");
if (!clipPath || !key) continue;
const target = keyedElement(layer, key);
if (!target || target.hasAttribute("clip-path")) continue;
target.setAttribute("clip-path", clipPath);
const clipDefinition = keyedElement(fallback, `${key}:clip-defs`);
if (clipDefinition) {
target.insertBefore(clipDefinition.cloneNode(true), target.firstChild);
}
}
}
function copyMissingRendererDefinitions(svg, renderedRoot, layer, layerKey) {
const pending = [...referencedIds(layer)];
const visited = /* @__PURE__ */ new Set();
let definitions;
while (pending.length) {
const id = pending.shift();
if (!id || visited.has(id)) continue;
visited.add(id);
if (elementWithId(layer, id) || baseElementWithId(svg, id)) continue;
const source = elementWithId(renderedRoot, id);
if (!source) continue;
if (!definitions) {
definitions = svg.ownerDocument.createElementNS(
"http://www.w3.org/2000/svg",
"defs"
);
definitions.setAttribute("data-ts-key", `${layerKey}:renderer-defs`);
layer.insertBefore(definitions, layer.firstChild);
}
const clone = source.cloneNode(true);
definitions.append(clone);
pending.push(...referencedIds(clone));
}
}
function baseElementWithId(svg, id) {
const element = elementWithId(svg, id);
return element?.closest("[data-ts-focus-guide-layer]") ? void 0 : element;
}
function referencedIds(root) {
const ids = /* @__PURE__ */ new Set();
for (const element of [root, ...root.querySelectorAll("*")]) {
for (const attribute of element.attributes) {
for (const match of attribute.value.matchAll(/url\(#([^)]+)\)/g)) {
if (match[1]) ids.add(match[1]);
}
if ((attribute.localName === "href" || attribute.name === "xlink:href") && attribute.value.startsWith("#")) {
ids.add(attribute.value.slice(1));
}
}
}
return ids;
}
function elementWithId(root, id) {
return [
...root.getAttribute("id") === id ? [root] : [],
...root.querySelectorAll("[id]")
].find((element) => element.getAttribute("id") === id);
}
function parseSvgMarkup(document, markup) {
const template = document.createElement("template");
template.innerHTML = markup.trim();
const root = template.content.firstElementChild;
return root?.localName === "svg" ? root : void 0;
}
function parseSvgFragment(document, markup) {
const template = document.createElement("template");
template.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg">${markup}</svg>`;
return template.content.firstElementChild?.firstElementChild ?? void 0;
}
function keyedElement(root, key) {
return keyedElements(root).find(
(element) => element.getAttribute("data-ts-key") === key
);
}
function keyedElements(root) {
return [
...root.hasAttribute("data-ts-key") ? [root] : [],
...root.querySelectorAll("[data-ts-key]")
];
}
export {
renderFocusGuideLayerWithRenderer
};