// Rendu SVG minimal d'un layout (même vocabulaire visuel que la galaxie : cercle, prénom, barre de couple, filiation) import { writeFileSync } from 'node:fs' const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(/ [n.id, n])) const pad = 60 const minX = Math.min(...nodes.map(n => n.x)) - pad const minY = Math.min(...nodes.map(n => n.y)) - pad const maxX = Math.max(...nodes.map(n => n.x + n.width)) + pad const maxY = Math.max(...nodes.map(n => n.y + n.height)) + pad const W = maxX - minX, H = maxY - minY const out = [] out.push(``) if (title) out.push(`${esc(title)}`) // Filiations for (const e of result.edges) { const s = byId.get(e.sources[0]); const t = byId.get(e.targets[0]) if (!s || !t) continue const sx = s.x + s.width / 2, sy = s.y + s.height / 2 const tx = t.x + t.width / 2, ty = t.y + t.height / 2 - 30 const dy = ty - sy, dx = tx - sx out.push(``) } // Couples for (const c of coupleBarMeta) { const p1 = byId.get(c.p1Key); const p2 = byId.get(c.p2Key) if (!p1 || !p2) continue const y = p1.y + p1.height / 2 out.push(``) } // Noeuds for (const n of nodes) { const cx = n.x + n.width / 2, cy = n.y + n.height / 2 if (n._type === 'person') { const p = n._data const fill = p.isAlive === false ? '#E8DCC8' : '#F7ECD9' out.push(``) out.push(`${esc((p.firstName || '').slice(0, 14))}`) out.push(`${esc(p.birthYear ?? '')}`) } else if (n._type === 'union') { out.push(``) } else if (n._type === 'unknown') { out.push(``) out.push(`?`) } } out.push('') writeFileSync(file, out.join('\n')) return { W, H } }