import { Couple, CouplesList } from "../api/CoupleApi"; import { Member, MembersList, dateTimestamp } from "../api/MemberApi"; export interface CoupleInformation { couple: Couple; member: Member; down: FamilyTreeNode[]; } export interface FamilyTreeNode { down?: FamilyTreeNode[]; member: Member; couples?: CoupleInformation[]; } export function buildAscendingTree( memberId: number, members: MembersList, couples: CouplesList ): FamilyTreeNode { const member = members.get(memberId)!; const parents = []; if (member.father) parents.push(buildAscendingTree(member.father, members, couples)); if (member.mother) parents.push(buildAscendingTree(member.mother, members, couples)); return { member: member, down: parents, }; } export function buildDescendingTree( memberId: number, members: MembersList, couples: CouplesList ): FamilyTreeNode { const member = members.get(memberId)!; const children = members.children(member.id); // Process user couples const member_couples: CoupleInformation[] = []; for (const c of couples.getAllOf(member)) { // Currently, couples with missing pair information are not supported const pairId = c.otherPersonID(member.id); if (!pairId) continue; const pair = members.get(pairId); const c_children: Member[] = []; // Determine children associated with each couple for (let index = 0; index < children.length; ) { const c = children[index]; if (c.mother !== pairId && c.father !== pairId) { index++; continue; } children.splice(index, 1); c_children.push(c); } member_couples.push({ couple: c, member: pair!, down: sortChildren( c_children.map((c) => buildDescendingTree(c.id, members, couples)) ), }); } return { member: member, down: sortChildren( children.map((c) => buildDescendingTree(c.id, members, couples)) ), couples: member_couples, }; } /** * Sort family tree children per date of birth */ function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] { n.sort( (a, b) => dateTimestamp(a.member.dateOfBirth) - dateTimestamp(b.member.dateOfBirth) ); return n; } /** * Compute family tree height */ export function treeHeight(node: FamilyTreeNode): number { let res = node.down?.reduce((prev, node) => Math.max(prev, treeHeight(node)), 0) ?? 0; node.couples?.forEach( (c) => (res = Math.max( res, c.down.reduce((prev, node) => Math.max(prev, treeHeight(node)), 0) )) ); return res + 1; } /** * Compute family tree width */ export function treeWidth(node: FamilyTreeNode): number { const values = new Array(treeHeight(node)).fill(0); treeWidthRecurse(node, values, 0); return Math.max(...values); } function treeWidthRecurse(node: FamilyTreeNode, vals: number[], level: number) { vals[level] += 1 + (node.couples?.length ?? 0) + ((node.down?.length ?? 0) > 0 ? 1 : 0); node.down?.forEach((n) => treeWidthRecurse(n, vals, level + 1)); node.couples?.forEach((c) => c.down.forEach((n) => treeWidthRecurse(n, vals, level + 1)) ); } /** * Get the list of members to be shown in a tree, * depending of a specified depth */ export function getAvailableMembers( t: FamilyTreeNode, depth: number ): Set { const s = new Set(); getAvailableMembersRecurse(t, depth, s); return s; } function getAvailableMembersRecurse( t: FamilyTreeNode, depth: number, s: Set ) { if (depth < 1) return; s.add(t.member.id); t.couples?.forEach((c) => { s.add(c.member.id); c.down.forEach((e) => getAvailableMembersRecurse(e, depth - 1, s)); }); t.down?.forEach((e) => getAvailableMembersRecurse(e, depth - 1, s)); }