Canvas is great for static exports and huge word counts, but SVG shines when you need
crisp text at any zoom level, per-word DOM nodes, or CSS-driven styling.
The layout step is identical — only the renderer changes. Each placement becomes an SVG
<text> element positioned with a transform.
SVG rendering checklist:
viewBox="0 0 width height" to match the layout bounds passed to
layout().text-anchor="middle" and dominant-baseline="central" —
the engine returns bbox centers, same as Canvas center anchoring.transform="translate(x,y) rotate(deg)" on each
<text> (or a wrapping <g>).width: 100% on the SVG scale the cloud responsively; re-run layout
when the container's pixel size changes materially./** @param {SVGElement} svg — empty <svg> in the DOM */ export function drawWordCloudSvg(svg, placements, width, height, palette = ["#1f6feb"]) { svg.setAttribute("viewBox", `0 0 ${width} ${height}`); svg.replaceChildren(); for (let i = 0; i < placements.length; i++) { const p = placements[i]; const el = document.createElementNS("http://www.w3.org/2000/svg", "text"); el.textContent = p.text; el.setAttribute("font-size", String(p.fontSize)); el.setAttribute("fill", palette[i % palette.length]); el.setAttribute("text-anchor", "middle"); el.setAttribute("dominant-baseline", "central"); el.setAttribute( "transform", p.rotation ? `translate(${p.x},${p.y}) rotate(${p.rotation})` : `translate(${p.x},${p.y})`, ); svg.appendChild(el); } }
Because each word is a DOM node, you can target svg text globally or assign
per-word classes. Hover transitions, selection highlights, and theme switches become
ordinary CSS — no redraw loop required.
.word-cloud-svg text { font-family: system-ui, sans-serif; cursor: default; transition: opacity 0.15s; } .word-cloud-svg text:hover { opacity: 0.75; }
Random datasets at 50 or 500 words, rendered as SVG. Resize the browser window — the cloud
scales smoothly because viewBox maps layout coordinates to the container.
Presets tune minFontSize / maxFontSize so words stay readable
at each scale (50 → 16–88px, 500 → 10–52px). Fullscreen lays out against the viewport
so you can see the engine's maximum placement count.