Files
GeneIT/geneit_app/src/utils/family_tree.ts
Pierre Hubert 635fb667e1
All checks were successful
continuous-integration/drone/push Build is passing
Can customize shown depth
2023-08-26 08:47:02 +02:00

163 lines
3.7 KiB
TypeScript

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<number> {
const s = new Set<number>();
getAvailableMembersRecurse(t, depth, s);
return s;
}
function getAvailableMembersRecurse(
t: FamilyTreeNode,
depth: number,
s: Set<number>
) {
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));
}