Pretext: exacte tekstbreedte voor D3 force-graph nodes
Definitie
@chenglou/pretext is een JavaScript/TypeScript-library die tekst opmeet via canvas zonder DOM-reflows te veroorzaken. In D3 force-directed graphs vervangt het de gangbare heuristiek word.length * N + padding voor node-afmetingen.
Context
In SVG force-graphs op basis van D3 worden nodegroottes doorgaans geschat via tekstlengte of vaste hit areas. Dit leidt tot incorrecte collision-radii (woorden overlappen of staan te ver uit elkaar) en hit areas die niet overeenkomen met de zichtbare tekst. Pretext meet de werkelijke rendered breedte op basis van het canvas-font-systeem, inclusief CSS-eigenschappen zoals letter-spacing en text-transform.
Kernpunten
Basispatroon voor node-meting
import { prepareWithSegments, measureNaturalWidth } from '@chenglou/pretext';
function measureNodes(nodes) {
for (const n of nodes) {
// Uppercase-tekst (CSS text-transform) moet als uppercase gemeten worden
const word = n.type === 'noun' ? n.word.toUpperCase() : n.word;
const px = fontPxForType(n.type);
const letterSpacing = n.type === 'noun' ? 0.08 * px : 0;
const opts = letterSpacing ? { letterSpacing } : {};
const prepared = prepareWithSegments(word, fontStringForType(n.type), opts);
n.textWidth = measureNaturalWidth(prepared);
n.textPx = px;
}
}Font-string moet overeenkomen met CSS
De font-string voor prepareWithSegments volgt de CSS font-shorthand notatie: "italic 700 21px Wavehaus, Futura, sans-serif". Als de font-string afwijkt van wat de browser gebruikt, wijkt de meting af.
Na font-loading meten
Meting moet plaatsvinden nadat webfonts geladen zijn, anders meet Pretext de fallback-font. Gebruik document.fonts.ready:
fetch(DATA_URL)
.then(r => r.json())
.then(data => document.fonts.ready.then(() => data))
.then(data => {
measureNodes(data.nodes);
buildGraph(data);
});Integratie in D3
Gebruik de gemeten breedte voor hit areas en collision-radius:
// Hit area als rechthoek op basis van gemeten breedte
node.append('rect')
.attr('x', d => -(d.textWidth / 2 + PAD_H))
.attr('y', d => -(d.textPx / 2 + PAD_V))
.attr('width', d => d.textWidth + PAD_H * 2)
.attr('height', d => d.textPx + PAD_V * 2);
// Collision force op basis van werkelijke breedte
.force('collision', d3.forceCollide().radius(d => d.textWidth / 2 + PAD_H + 4))Wanneer Pretext niet nodig is
Voor enkelvoudige woorden zonder bijzondere CSS is canvas.measureText(word) functioneel equivalent. Pretext voegt waarde toe bij: complexe scripts (bidirectioneel, CJK), letter-spacing, multi-line tekst, of wanneer meerdere font-varianten in één pass gemeten worden.
Wat Pretext niet is
Pretext is geen CSS-vervanger. Het elimineert DOM-reflows bij tekstmeting (getBoundingClientRect, offsetHeight), niet CSS zelf.
Verbanden
- Zie ook: wiki-hugo-esbuild-npm-integratie (hoe Pretext in Hugo geladen wordt)
- Zie ook: wiki-css-class-animaties-svg-nodes
- Gerelateerd project: jimvandenbreemen.nl (Woordenweb)
Bronnen
Lou, C. (2026). Pretext [Software]. GitHub. https://github.com/chenglou/pretext
Sessie-herkomst
- raw-sessie-2026-04-27-woordenweb-pretext-integratie
- Eerdere research: sessies (research-pretext-chenglou.md, 2026-03-30)