mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-10 15:52:14 +00:00
Le banc mesure maintenant le code de production (computeBlockLayout), sans écran, en quatre étages lus dans l'ordre et jamais additionnés : - invariants qui doivent valoir zéro (chevauchement, couple non adjacent, fratrie coupée ou hors de l'ordre choisi, rangée différente de la génération de parenté recalculée ici, années de naissance qui redescendent) ; - défauts comptés sur la géométrie vraiment dessinée (renvois, lignes de fratrie au-dessus d'un portrait étranger, croisements, lignes et foyers plus larges qu'un écran, proches de la référence visibles dans un écran) ; - quatorze archétypes (ordre d'une fratrie, issue #67, branche courte, Marc et Nadia, petit conjoint, fratrie de douze, remariage, cousins, oncle et nièce, parent seul, adoption, deux familles, belle-famille, trois familles enchaînées) rendus en texte, témoins dans __snapshots__/archetypes, testés par vitest ; deux échecs connus (belle-famille, trois-belles-familles) en attendant la règle « le sang de la référence garde son enfant » ; - stabilité : retirer une feuille ne devrait déplacer que son foyer. Les modèles ELK écartés en septembre, elk-common et l'ancienne mesure sur segments droits sont supprimés, avec la dépendance elkjs. Le générateur d'arbres fictifs (gen-fr.mjs) entre dans le dépôt ; les arbres réels restent dans fixtures/, ignoré par git. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
37 lines
2.0 KiB
JavaScript
37 lines
2.0 KiB
JavaScript
// Stabilité : retirer une feuille (personne sans enfant) ne devrait déplacer que son foyer.
|
|
// On retire tour à tour quelques feuilles prises à des positions réparties, on recalcule, et on compte
|
|
// les nœuds qui bougent de plus de 40 px une fois la translation d'ensemble retirée.
|
|
const SEUIL = 40
|
|
|
|
export function perturbation(layoutFn, graph, referenceId = null, { picks = 5 } = {}) {
|
|
const base = layoutFn(graph, referenceId)
|
|
const before = new Map(base.children.map((n) => [n.id, n.x]))
|
|
const hasKids = new Set()
|
|
for (const e of base.edges) {
|
|
const u = base.children.find((n) => n.id === e.sources[0])
|
|
if (u?._data?.p1Key) { hasKids.add(u._data.p1Key); hasKids.add(u._data.p2Key) }
|
|
}
|
|
const leaves = base.children.filter((n) => n._type === 'person' && !n._compact && !hasKids.has(n.id))
|
|
if (!leaves.length) return { feuilles: 0, max_bouges: 0, max_deplacement_px: 0 }
|
|
const chosen = [...new Set(Array.from({ length: picks }, (_, i) => leaves[Math.floor((i / Math.max(1, picks - 1)) * (leaves.length - 1))]))]
|
|
let maxMoved = 0, maxShift = 0
|
|
for (const leaf of chosen) {
|
|
const pid = leaf._data.id
|
|
const g = {
|
|
...graph,
|
|
persons: graph.persons.filter((p) => p.id !== pid),
|
|
unions: graph.unions.filter((u) => u.partner1Id !== pid && u.partner2Id !== pid),
|
|
filiations: graph.filiations.filter((f) => f.childId !== pid && f.parentId !== pid),
|
|
}
|
|
const after = layoutFn(g, referenceId === pid ? null : referenceId)
|
|
const common = after.children.filter((n) => before.has(n.id))
|
|
if (!common.length) continue
|
|
const shift = common.reduce((s, n) => s + (n.x - before.get(n.id)), 0) / common.length
|
|
const moved = common.filter((n) => Math.abs(n.x - before.get(n.id) - shift) > SEUIL).length
|
|
const far = Math.max(...common.map((n) => Math.abs(n.x - before.get(n.id) - shift)))
|
|
maxMoved = Math.max(maxMoved, moved)
|
|
maxShift = Math.max(maxShift, far)
|
|
}
|
|
return { feuilles: chosen.length, max_bouges: maxMoved, max_deplacement_px: Math.round(maxShift) }
|
|
}
|