From 635fb667e15d4dfa0d9a0b35b1fe11d273bd1c3d Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Sat, 26 Aug 2023 08:47:02 +0200 Subject: [PATCH] Can customize shown depth --- .../routes/family/FamilyMemberTreeRoute.tsx | 58 ++++++++++--- geneit_app/src/utils/family_tree.ts | 71 ++++++++++++++++ geneit_app/src/widgets/BasicFamilyTree.tsx | 15 ++-- .../complex_family_tree/ComplexFamilyTree.tsx | 85 +++++++------------ 4 files changed, 157 insertions(+), 72 deletions(-) diff --git a/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx b/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx index 5d2950f..6c8e8c5 100644 --- a/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx +++ b/geneit_app/src/routes/family/FamilyMemberTreeRoute.tsx @@ -5,22 +5,27 @@ import { FormControlLabel, FormLabel, IconButton, + InputLabel, + MenuItem, Paper, Radio, RadioGroup, + Select, Tab, Tabs, } from "@mui/material"; import React from "react"; import { useParams } from "react-router-dom"; import { + FamilyTreeNode, buildAscendingTree, buildDescendingTree, + treeHeight, } from "../../utils/family_tree"; import { useFamily } from "../../widgets/BaseFamilyRoute"; +import { BasicFamilyTree } from "../../widgets/BasicFamilyTree"; import { MemberItem } from "../../widgets/MemberItem"; import { RouterLink } from "../../widgets/RouterLink"; -import { BasicFamilyTree } from "../../widgets/BasicFamilyTree"; import { ComplexFamilyTree } from "../../widgets/complex_family_tree/ComplexFamilyTree"; enum CurrTab { @@ -43,15 +48,17 @@ export function FamilyMemberTreeRoute(): React.ReactElement { const member = family.members.get(Number(memberId)); - const tree = React.useMemo( - () => - !member - ? null - : currMode === TreeMode.Ascending + const memo: [FamilyTreeNode, number] | null = React.useMemo(() => { + if (!member) return null; + const tree = + currMode === TreeMode.Ascending ? buildAscendingTree(member.id, family.members, family.couples) - : buildDescendingTree(member.id, family.members, family.couples), - [member, currMode, family.members, family.couples] - ); + : buildDescendingTree(member.id, family.members, family.couples); + + return [tree, treeHeight(tree)]; + }, [member, currMode, family.members, family.couples]); + + const [currDepth, setCurrDepth] = React.useState(0); if (!member) { return ( @@ -61,6 +68,10 @@ export function FamilyMemberTreeRoute(): React.ReactElement { ); } + const [tree, maxDepth] = memo!; + + if (currDepth === 0) setCurrDepth(maxDepth); + return (
setCurrMode(Number(v))} + onChange={(_e, v) => { + setCurrDepth(0); + setCurrMode(Number(v)); + }} > -
+
+ + + Profondeur + + + +
{currTab === CurrTab.BasicTree ? ( - + ) : ( )} diff --git a/geneit_app/src/utils/family_tree.ts b/geneit_app/src/utils/family_tree.ts index e9dba4d..5444540 100644 --- a/geneit_app/src/utils/family_tree.ts +++ b/geneit_app/src/utils/family_tree.ts @@ -82,6 +82,9 @@ export function buildDescendingTree( }; } +/** + * Sort family tree children per date of birth + */ function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] { n.sort( (a, b) => @@ -89,3 +92,71 @@ function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] { ); 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)); +} diff --git a/geneit_app/src/widgets/BasicFamilyTree.tsx b/geneit_app/src/widgets/BasicFamilyTree.tsx index ad3ef02..4c66ab4 100644 --- a/geneit_app/src/widgets/BasicFamilyTree.tsx +++ b/geneit_app/src/widgets/BasicFamilyTree.tsx @@ -13,6 +13,7 @@ import { MemberPhoto } from "./MemberPhoto"; export function BasicFamilyTree(p: { tree: FamilyTreeNode; + depth: number; }): React.ReactElement { return ( } sx={{ flexGrow: 1 }} > - + ); } -function FamilyTreeItem(p: { n: FamilyTreeNode }): React.ReactElement { +function FamilyTreeItem(p: { + depth: number; + n: FamilyTreeNode; +}): React.ReactElement { let children = p.n.down ?? []; if (p.n.couples) { @@ -55,9 +59,10 @@ function FamilyTreeItem(p: { n: FamilyTreeNode }): React.ReactElement {
} > - {children.map((c) => ( - - ))} + {p.depth >= 2 && + children.map((c) => ( + + ))} ); } diff --git a/geneit_app/src/widgets/complex_family_tree/ComplexFamilyTree.tsx b/geneit_app/src/widgets/complex_family_tree/ComplexFamilyTree.tsx index c948711..0c70308 100644 --- a/geneit_app/src/widgets/complex_family_tree/ComplexFamilyTree.tsx +++ b/geneit_app/src/widgets/complex_family_tree/ComplexFamilyTree.tsx @@ -9,13 +9,19 @@ import "svg2pdf.js"; import { Couple } from "../../api/CoupleApi"; import { Member, fmtDate } from "../../api/MemberApi"; import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider"; -import { FamilyTreeNode } from "../../utils/family_tree"; +import { + FamilyTreeNode, + getAvailableMembers, + treeHeight, + treeWidth, +} from "../../utils/family_tree"; import { downloadBlob } from "../../utils/files_utils"; import "./family-chart.css"; export function ComplexFamilyTree(p: { tree: FamilyTreeNode; isUp: boolean; + depth: number; }): React.ReactElement { const darkTheme = useDarkTheme(); @@ -23,7 +29,7 @@ export function ComplexFamilyTree(p: { if (!container) return; const store = f3.createStore({ - data: treeToF3Data(p.tree, p.isUp), + data: treeToF3Data(p.tree, p.isUp, p.depth), node_separation: 250, level_separation: 150, }); @@ -197,41 +203,12 @@ export function ComplexFamilyTree(p: { ); } -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; -} - -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)) - ); -} - -function treeToF3Data(node: FamilyTreeNode, isUp: boolean): f3Data[] { - const availableMembers = new Set(); - getAvailableMembers(node, availableMembers); +function treeToF3Data( + node: FamilyTreeNode, + isUp: boolean, + depth: number +): f3Data[] { + const availableMembers = getAvailableMembers(node, depth); const list: f3Data[] = []; if (isUp) treeToF3DataUpRecurse(node, list, availableMembers); @@ -239,17 +216,6 @@ function treeToF3Data(node: FamilyTreeNode, isUp: boolean): f3Data[] { return list; } -function getAvailableMembers(t: FamilyTreeNode, s: Set) { - s.add(t.member.id); - - t.couples?.forEach((c) => { - s.add(c.member.id); - c.down.forEach((e) => getAvailableMembers(e, s)); - }); - - t.down?.forEach((e) => getAvailableMembers(e, s)); -} - function memberData(m: Member, c?: Couple): f3.f3DataData { return { first_name: m.first_name ?? "_", @@ -271,6 +237,8 @@ function treeToF3DataUpRecurse( child?: number, spouses?: number[] ) { + if (!availableMembers.has(node.member.id)) return; + array.push({ data: memberData(node.member), id: node.member.id.toString(), @@ -309,11 +277,13 @@ function treeToF3DataDownRecurse( array: f3Data[], availableMembers: Set ) { + if (!availableMembers.has(node.member.id)) return; + // Get all members ids - const children = node?.down?.map((c) => c.member.id.toString()) ?? []; - node.couples?.map((c) => - c.down.forEach((m) => children.push(m.member.id.toString())) - ); + let children = node?.down?.map((c) => c.member.id) ?? []; + node.couples?.map((c) => c.down.forEach((m) => children.push(m.member.id))); + + children = children.filter((c) => availableMembers.has(c)); array.push({ data: memberData(node.member), @@ -328,8 +298,10 @@ function treeToF3DataDownRecurse( ? node.member.mother.toString() : undefined, - spouses: node.couples?.map((c) => c.member.id.toString()), - children: children, + spouses: node.couples + ?.filter((s) => availableMembers.has(s.member.id)) + .map((c) => c.member.id.toString()), + children: children.map((c) => c.toString()), }, }); @@ -339,6 +311,7 @@ function treeToF3DataDownRecurse( if (node.couples) { for (const c of node.couples) { + if (!availableMembers.has(c.member.id)) continue; array.push({ data: memberData(c.member, c.couple), id: c.member.id.toString(), @@ -352,7 +325,9 @@ function treeToF3DataDownRecurse( ? c.member.mother.toString() : undefined, spouses: [node.member.id.toString()], - children: c.down.map((c) => c.member.id.toString()), + children: c.down + .filter((c) => availableMembers.has(c.member.id)) + .map((c) => c.member.id.toString()), }, });