All checks were successful
continuous-integration/drone/push Build is passing
163 lines
3.7 KiB
TypeScript
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));
|
|
}
|