All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Start our journey into turning GeneIT as afully featured family intranet by making genealogy a feature that can be disabled by family admins Reviewed-on: #175
		
			
				
	
	
		
			163 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { Couple, CouplesList } from "../api/genealogy/CoupleApi";
 | 
						|
import { Member, MembersList, dateTimestamp } from "../api/genealogy/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));
 | 
						|
}
 |