2023-07 Select SVG Text Nodes

Marc has a big collection of SVGs. Can we find a more general-purpose way to enrich them with wiki's internal links?

One collection of Marc's SVGs

We collect various examples.

let base = 'http://marc.tries.fed.wiki/assets/' let example = { mermaid: `${base}mermaiddiagram.svg`, omnigraffle: `${base}OmnigraffleTransit.svg`, opm: `${base}Tripod%20Beta%20-%20SD%20SD_svg.svg`, "mind map":`${base}Mind%20map.svg`, yEd: `${base}yEdTest.svg`, SVGedit: `${base}SVGedit.svg`, Neo4j: `${base}CarLNeighborhoodNeo4j.svg` }

We have devised a generator function to visit every DOM component and emit the elements that contain text.

function* selectTextNodes(el) { if (el.nodeType != Element.ELEMENT_NODE) return for (let item of el.childNodes) { if (item.nodeType == Element.ELEMENT_NODE) { yield* selectTextNodes(item) } else { yield [item.textContent, el] } } }

Since we have more than one challenging example, we will create buttons for each one.

function button(label, url) { return `<button data-url="${url}"> ${label} </button>` }

When the buttons are clicked, we will fetch the diagram, parse it, find the strings, and display both the diagram and the text we found.

async function click(event) { let button = event.target let {url} = button.dataset let payload = await fetch(url).then(res => res.text()) window.diagram.innerHTML = payload let svg = window.diagram.querySelector('svg') rescale(svg) window.log.innerText = "" for (let [text, el] of selectTextNodes(svg)) { if (text.match(/\S/)) { // ignore the child nodes with only whitespace window.log.innerText += `<${el.nodeName}> ${text}\n` } } window.parent.postMessage({ action: "resize", height: document.body.offsetHeight }, "*") }

Some examples don't fit in our narrow pages. Here we have devised a general approach to putting the scalable back into scalable vector graphics.

function rescale (svg) { let {x,y,width,height} = svg.getBBox() svg.setAttribute('viewBox', `${x} ${y} ${width} ${height}`) svg.removeAttribute("width") svg.removeAttribute("height") svg.style.width="100%" return svg }

Here we construct the functions for our general purpose frame script. emit() creates placeholders in the DOM. bind() generates buttons for each of our examples and binds the click handler to each one.

export async function emit(el) { el.innerHTML = ` <div id="buttons"></div> <div id="diagram"></div> <pre id="log"></pre>` } export async function bind(el) { for(let [label, url] of Object.entries(example)) { window.buttons.innerHTML += button(label, url) } for(let button of window.buttons.children) { button.onclick = click } }

In the frame below we can view the results.