From 5eb1d9e7bd3232f8e69876a92d97d943769a2c73 Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Sat, 7 Nov 2026 01:14:13 +0100 Subject: [PATCH] =?UTF-8?q?Tout=20l'arbre=20:=20plus=20aucune=20belle-fami?= =?UTF-8?q?lle=20r=C3=A9duite=20derri=C3=A8re=20une=20pastille=20quand=20l?= =?UTF-8?q?'arbre=20est=20dessin=C3=A9=20en=20entier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- frontend/src/components/galaxy/blockLayout.js | 2 + .../components/galaxy/dessinComplet.test.js | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 frontend/src/components/galaxy/dessinComplet.test.js diff --git a/frontend/src/components/galaxy/blockLayout.js b/frontend/src/components/galaxy/blockLayout.js index bb6faf4..a1b3811 100644 --- a/frontend/src/components/galaxy/blockLayout.js +++ b/frontend/src/components/galaxy/blockLayout.js @@ -421,6 +421,8 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) { // dont les parents sont les charnières (badge sur eux), puis les parents seuls sont rebâtis au-dessus du conjoint function reduceToParents(entry) { const { block, anchorId: id, side, pu, pc, snap } = entry + // Arbre dessiné en entier (onglets par nom, ou pas d'onglets) : personne n'est caché derrière une pastille + if (!familyTabs) return block const pcPersons = pc.filter(x => byId.get(x)._type === 'person') const keep = new Set(pcPersons) const extra = [...block.members].filter(m => byId.get(m)?._type === 'person' && !keep.has(m) && chainOf.get(m) !== pc) diff --git a/frontend/src/components/galaxy/dessinComplet.test.js b/frontend/src/components/galaxy/dessinComplet.test.js new file mode 100644 index 0000000..96218cf --- /dev/null +++ b/frontend/src/components/galaxy/dessinComplet.test.js @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest' +import { computeBlockLayout } from './blockLayout' +import { generate } from '../../../../tools/layout-bench/gen-fr.mjs' + +// « Tout l'arbre » (onglets par nom) et « Pas d'onglets » dessinent l'arbre sans onglet par largeur : chaque +// personne est là, aucune pastille, et deux personnes ne se chevauchent pas +function overlaps(people) { + const boxes = people.map((n) => [n.x, n.y, n.x + n.width, n.y + n.height]).sort((a, b) => a[0] - b[0]) + let n = 0 + for (let i = 0; i < boxes.length; i++) { + for (let j = i + 1; j < boxes.length && boxes[j][0] < boxes[i][2] - 1; j++) { + if (boxes[j][1] < boxes[i][3] - 1 && boxes[i][1] < boxes[j][3] - 1) n++ + } + } + return n +} +const cas = [['desc', 3], ['desc', 7], ['asc', 3], ['asc', 7]] +describe('dessin complet, sans onglets par largeur', () => { + for (const [kind, seed] of cas) { + it(`${kind} graine ${seed}`, () => { + const tree = generate(kind, seed) + const { graph } = tree + const result = computeBlockLayout(graph, tree.rootPersonId, { familyTabs: false }) + const people = result.children.filter((n) => n._type === 'person') + expect(people.length).toBe(graph.persons.length) + expect(people.filter((n) => n._familyTab).length).toBe(0) + expect(overlaps(people)).toBe(0) + }) + } +}) + +// Une lignée (Duval) et la très grande famille d'un conjoint (Garnier : onze frères et sœurs en couple, trois +// enfants chacun). Le placement par largeur la réduisait aux parents du conjoint, le reste caché derrière une +// pastille, même en dessin complet (« Tout l'arbre 211 » qui n'était pas tout l'arbre, Patch, 07/11) +function belleFamille() { + const persons = [], unions = [], filiations = [] + const P = (id, firstName, lastName, y) => persons.push({ id, firstName, lastName, birthDate: `${y}-01-01` }) + const U = (id, a, b) => unions.push({ id, partner1Id: a, partner2Id: b }) + const L = (child, unionId) => filiations.push({ childId: child, unionId }) + P('henri', 'Henri', 'Duval', 1920); P('odette', 'Odette', 'Duval', 1924); U('uA', 'henri', 'odette') + for (const k of ['paul', 'marc', 'julie']) { P(k, k, 'Duval', 1948); L(k, 'uA') } + P('jean', 'Jean', 'Garnier', 1925); P('marthe', 'Marthe', 'Garnier', 1927); U('uG', 'jean', 'marthe') + P('aieul', 'Aïeul', 'Garnier', 1895); P('aieule', 'Aïeule', 'Garnier', 1897); U('uAG', 'aieul', 'aieule'); L('jean', 'uAG') + P('oncle', 'Oncle', 'Garnier', 1922); L('oncle', 'uAG') + P('elodie', 'Élodie', 'Garnier', 1952); L('elodie', 'uG'); U('uM', 'marc', 'elodie') + for (const k of ['lola', 'noe']) { P(k, k, 'Duval', 1980); L(k, 'uM') } + for (let i = 0; i < 11; i++) { + P(`g${i}`, `G${i}`, 'Garnier', 1950 + i); L(`g${i}`, 'uG'); P(`c${i}`, `C${i}`, 'Martin', 1950 + i); U(`ug${i}`, `g${i}`, `c${i}`) + for (const k of [1, 2, 3]) { P(`k${i}${k}`, `K${i}${k}`, 'Garnier', 1978 + i); L(`k${i}${k}`, `ug${i}`) } + } + return { persons, unions, filiations } +} +describe('dessin complet, grande belle-famille', () => { + it('tout le monde est dessiné, sans pastille', () => { + const graph = belleFamille() + const result = computeBlockLayout(graph, 'henri', { familyTabs: false }) + const people = result.children.filter((n) => n._type === 'person') + expect(people.filter((n) => n._familyTab).length).toBe(0) + expect(people.length).toBe(graph.persons.length) + expect(overlaps(people)).toBe(0) + }) +})