mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-06 23:32:08 +00:00
Merge pull request #105 from IliesAllali/fix/placement-trois-correctifs
Placement : rangées stables, couples tournés vers leurs familles, fil libre malgré les conjoints de la fratrie
This commit is contained in:
4 files changed
+84
-3
No files matched your search
@@ -27,12 +27,14 @@ const NODE_SPACING = 30
|
||||
const FAMILY_GAP = 60
|
||||
|
||||
// Le fil d'une filiation union → enfant : une personne d'une autre famille, sur la ligne de l'enfant,
|
||||
// entre l'aplomb de l'union et l'enfant, est croisée. Frères et sœurs et conjoint(e)s de l'enfant ne comptent pas.
|
||||
// entre l'aplomb de l'union et l'enfant, est croisée. Frères et sœurs et leurs conjoint(e)s ne comptent pas
|
||||
// (un petit conjoint collé à un frère faisait passer le fil en pastille).
|
||||
export function filCroiseAutreFamille(nodes, childrenOfUnion, spouseKeysOf, u, c) {
|
||||
const byIdLocal = new Map(nodes.map(n => [n.id, n]))
|
||||
const kid = byIdLocal.get(c)
|
||||
const cx = (id) => byIdLocal.get(id).x + byIdLocal.get(id).width / 2
|
||||
const excluded = new Set([c, ...(childrenOfUnion.get(u) || []), ...(spouseKeysOf.get(c) || [])])
|
||||
const fratrie = [...new Set([c, ...(childrenOfUnion.get(u) || [])])]
|
||||
const excluded = new Set([...fratrie, ...fratrie.flatMap(k => [...(spouseKeysOf.get(k) || [])])])
|
||||
const lo = Math.min(cx(u), cx(c)), hi = Math.max(cx(u), cx(c))
|
||||
return nodes.some(n => n._type === 'person' && n._generation === kid._generation && !excluded.has(n.id)
|
||||
&& n.x + n.width > lo && n.x < hi)
|
||||
@@ -215,6 +217,26 @@ export function computeBlockLayout(graphData = null) {
|
||||
if (!found) { block.place(cursor); cursor += block.width + FAMILY_GAP * 2 }
|
||||
}
|
||||
|
||||
// Couple dont les deux conjoints ont leurs parents dans l'arbre, chacun du côté de la famille de l'autre :
|
||||
// on intervertit les deux conjoints (même place, l'union reste au milieu), les deux fils ne se croisent plus.
|
||||
// Répété : intervertir un couple peut remettre à l'endroit ou à l'envers celui de ses enfants.
|
||||
const centerOf = (id) => pos.get(id) + byId.get(id).width / 2
|
||||
for (let pass = 0; pass < 4; pass++) {
|
||||
let swapped = 0
|
||||
for (const chain of chains) {
|
||||
if (chain.length !== 3 || !pos.has(chain[0])) continue
|
||||
const [a, , b] = chain
|
||||
const pa = parentUnionOf.get(a), pb = parentUnionOf.get(b)
|
||||
if (!pa || !pb || pa === pb || !pos.has(pa) || !pos.has(pb)) continue
|
||||
if ((centerOf(a) - centerOf(b)) * (centerOf(pa) - centerOf(pb)) >= 0) continue
|
||||
const xa = pos.get(a), xb = pos.get(b)
|
||||
pos.set(a, xb + byId.get(b).width - byId.get(a).width)
|
||||
pos.set(b, xa)
|
||||
swapped++
|
||||
}
|
||||
if (!swapped) break
|
||||
}
|
||||
|
||||
// Réinjecter le X, normalisé à 0 ; Y inchangé (générations)
|
||||
let minX = Infinity
|
||||
for (const n of nodes) if (pos.has(n.id)) minX = Math.min(minX, pos.get(n.id))
|
||||
|
||||
@@ -102,8 +102,35 @@ describe('filCroiseAutreFamille', () => {
|
||||
expect(filCroiseAutreFamille(nodes, childrenOfUnion, spouseKeysOf, 'u', 'c')).toBe(false)
|
||||
})
|
||||
|
||||
it('ne croise pas pour le conjoint d\'un frère ou d\'une sœur', () => {
|
||||
const nodes = base().filter(n => n.id !== 'other').concat(at('beaufrere', 420))
|
||||
const spouses = new Map([...spouseKeysOf, ['sib', new Set(['beaufrere'])]])
|
||||
expect(filCroiseAutreFamille(nodes, childrenOfUnion, spouses, 'u', 'c')).toBe(false)
|
||||
})
|
||||
|
||||
it('ignore une personne d\'une autre génération sur la même abscisse', () => {
|
||||
const nodes = base().filter(n => n.id !== 'other').concat(at('x', 400, 'person', 2))
|
||||
expect(filCroiseAutreFamille(nodes, childrenOfUnion, spouseKeysOf, 'u', 'c')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeBlockLayout, couple tourné vers les familles de chacun', () => {
|
||||
// Marc (fratrie de Louis et Hélène) épouse Nadia (fratrie de Pierre et Odile). La fratrie de Nadia est saisie
|
||||
// en premier, Marc avant Nadia : Marc se retrouvait à gauche du couple alors que sa famille est à droite,
|
||||
// et les deux fils se croisaient.
|
||||
const graph = {
|
||||
persons: ['pierre', 'odile', 'louis', 'helene', 'marc', 'claire', 'julien', 'sophie', 'eric', 'luc', 'paul', 'mireille', 'nadia', 'anais'].map(person),
|
||||
unions: [union('uPO', 'pierre', 'odile'), { ...union('uLH', 'louis', 'helene'), displayOrder: 2 }, { ...union('uMN', 'marc', 'nadia'), displayOrder: 3 }],
|
||||
filiations: [
|
||||
...['luc', 'paul', 'mireille', 'nadia'].map((k, i) => ({ ...fil(`f-${k}`, k, 'uPO'), displayOrder: i + 1 })),
|
||||
...['marc', 'claire', 'julien', 'sophie', 'eric'].map((k, i) => ({ ...fil(`f-${k}`, k, 'uLH'), displayOrder: i + 1 })),
|
||||
fil('f-anais', 'anais', 'uMN'),
|
||||
],
|
||||
}
|
||||
|
||||
it('place chaque conjoint du côté de ses propres parents', () => {
|
||||
const { children } = computeBlockLayout(graph)
|
||||
const x = (id) => { const n = children.find(c => c.id === id); return n.x + n.width / 2 }
|
||||
expect(Math.sign(x('p-marc') - x('p-nadia'))).toBe(Math.sign(x('u-uLH') - x('u-uPO')))
|
||||
})
|
||||
})
|
||||
@@ -258,7 +258,10 @@ export function computeLayout(graphData = null) {
|
||||
|
||||
// Ajuster les racines orphelines : si un ancêtre sans parents connus
|
||||
// a des enfants à gen N, il doit être à gen N-1 (pas gen 0).
|
||||
{
|
||||
// Répété jusqu'à stabilité : un enfant descendu par son conjoint fait à son tour descendre ses parents
|
||||
// sans ascendance (sinon ils restent en haut, et l'enfant pend plusieurs rangées sous sa fratrie).
|
||||
for (let pass = 0; pass < 12; pass++) {
|
||||
const before = persons.map(p => genMap.get(p.id)).join()
|
||||
const rootIds = new Set(persons.filter(p => !filiationsByChild.has(p.id)).map(p => p.id))
|
||||
const maxChildGenByRoot = new Map()
|
||||
normalizedFiliations.forEach(f => {
|
||||
@@ -298,6 +301,7 @@ export function computeLayout(graphData = null) {
|
||||
genMap.set(u.partner2Id, maxG)
|
||||
})
|
||||
}
|
||||
if (persons.map(p => genMap.get(p.id)).join() === before) break
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
|
||||
@@ -267,3 +267,31 @@ describe('separated parents', () => {
|
||||
expect(separatedPairUnions([filiations[0]], [])).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('rangées des ancêtres sans parents', () => {
|
||||
// Henri + Isabelle (sans ascendance) ont Joseph et Nicolas. Nicolas épouse Madeleine (sans ascendance) ;
|
||||
// leur fille Anne épouse Jean, d'une lignée qui remonte sur cinq générations. Anne descend au niveau de Jean,
|
||||
// Nicolas avec elle : ses parents et son frère doivent suivre, au lieu de rester tout en haut.
|
||||
const ids = ['henri', 'isabelle', 'joseph', 'nicolas', 'madeleine', 'anne', 'jean', 'l0', 'l0w', 'l1', 'l1w', 'l2', 'l2w', 'l3', 'l3w', 'l4', 'l4w']
|
||||
const persons = ids.map((id) => ({ id }))
|
||||
const unions = [
|
||||
{ id: 'uHI', partner1Id: 'henri', partner2Id: 'isabelle', displayOrder: 1 },
|
||||
{ id: 'uNM', partner1Id: 'nicolas', partner2Id: 'madeleine', displayOrder: 2 },
|
||||
{ id: 'uAJ', partner1Id: 'anne', partner2Id: 'jean', displayOrder: 3 },
|
||||
...[0, 1, 2, 3, 4].map((i) => ({ id: `uL${i}`, partner1Id: `l${i}`, partner2Id: `l${i}w`, displayOrder: 4 + i })),
|
||||
]
|
||||
const link = (id, childId, unionId) => ({ id, childId, unionId, parentId: null, parentageType: 'biologique', displayOrder: 1 })
|
||||
const filiations = [
|
||||
link('f1', 'joseph', 'uHI'), link('f2', 'nicolas', 'uHI'), link('f3', 'anne', 'uNM'),
|
||||
link('f4', 'l1', 'uL0'), link('f5', 'l2', 'uL1'), link('f6', 'l3', 'uL2'), link('f7', 'l4', 'uL3'), link('f8', 'jean', 'uL4'),
|
||||
]
|
||||
|
||||
it('fait descendre les parents et la fratrie de celui qui a suivi son conjoint', () => {
|
||||
const result = computeLayout({ persons, unions, filiations })
|
||||
const gen = new Map(result.children.filter((n) => n._type === 'person').map((n) => [n.id.slice(2), n._generation]))
|
||||
expect(gen.get('anne')).toBe(gen.get('jean'))
|
||||
expect(gen.get('nicolas')).toBe(gen.get('anne') - 1)
|
||||
expect(gen.get('joseph')).toBe(gen.get('nicolas'))
|
||||
expect(gen.get('henri')).toBe(gen.get('nicolas') - 1)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user