Can customize shown depth
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
d3b1028fe4
commit
635fb667e1
@ -5,22 +5,27 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
Paper,
|
Paper,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
|
Select,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
|
FamilyTreeNode,
|
||||||
buildAscendingTree,
|
buildAscendingTree,
|
||||||
buildDescendingTree,
|
buildDescendingTree,
|
||||||
|
treeHeight,
|
||||||
} from "../../utils/family_tree";
|
} from "../../utils/family_tree";
|
||||||
import { useFamily } from "../../widgets/BaseFamilyRoute";
|
import { useFamily } from "../../widgets/BaseFamilyRoute";
|
||||||
|
import { BasicFamilyTree } from "../../widgets/BasicFamilyTree";
|
||||||
import { MemberItem } from "../../widgets/MemberItem";
|
import { MemberItem } from "../../widgets/MemberItem";
|
||||||
import { RouterLink } from "../../widgets/RouterLink";
|
import { RouterLink } from "../../widgets/RouterLink";
|
||||||
import { BasicFamilyTree } from "../../widgets/BasicFamilyTree";
|
|
||||||
import { ComplexFamilyTree } from "../../widgets/complex_family_tree/ComplexFamilyTree";
|
import { ComplexFamilyTree } from "../../widgets/complex_family_tree/ComplexFamilyTree";
|
||||||
|
|
||||||
enum CurrTab {
|
enum CurrTab {
|
||||||
@ -43,15 +48,17 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
|
|||||||
|
|
||||||
const member = family.members.get(Number(memberId));
|
const member = family.members.get(Number(memberId));
|
||||||
|
|
||||||
const tree = React.useMemo(
|
const memo: [FamilyTreeNode, number] | null = React.useMemo(() => {
|
||||||
() =>
|
if (!member) return null;
|
||||||
!member
|
const tree =
|
||||||
? null
|
currMode === TreeMode.Ascending
|
||||||
: currMode === TreeMode.Ascending
|
|
||||||
? buildAscendingTree(member.id, family.members, family.couples)
|
? buildAscendingTree(member.id, family.members, family.couples)
|
||||||
: buildDescendingTree(member.id, family.members, family.couples),
|
: buildDescendingTree(member.id, family.members, family.couples);
|
||||||
[member, currMode, family.members, family.couples]
|
|
||||||
);
|
return [tree, treeHeight(tree)];
|
||||||
|
}, [member, currMode, family.members, family.couples]);
|
||||||
|
|
||||||
|
const [currDepth, setCurrDepth] = React.useState(0);
|
||||||
|
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return (
|
return (
|
||||||
@ -61,6 +68,10 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [tree, maxDepth] = memo!;
|
||||||
|
|
||||||
|
if (currDepth === 0) setCurrDepth(maxDepth);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -90,7 +101,10 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
|
|||||||
<RadioGroup
|
<RadioGroup
|
||||||
row
|
row
|
||||||
value={currMode}
|
value={currMode}
|
||||||
onChange={(_e, v) => setCurrMode(Number(v))}
|
onChange={(_e, v) => {
|
||||||
|
setCurrDepth(0);
|
||||||
|
setCurrMode(Number(v));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value={TreeMode.Descending}
|
value={TreeMode.Descending}
|
||||||
@ -105,7 +119,26 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
|
|||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<div style={{ flex: "2" }}></div>
|
<div style={{ flex: "1" }}></div>
|
||||||
|
|
||||||
|
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
|
||||||
|
<InputLabel>Profondeur</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={currDepth}
|
||||||
|
onChange={(v) => setCurrDepth(Number(v.target.value))}
|
||||||
|
label="Profondeur"
|
||||||
|
>
|
||||||
|
{Array(maxDepth)
|
||||||
|
.fill(0)
|
||||||
|
.map((_v, index) => (
|
||||||
|
<MenuItem key={index} value={index + 1}>
|
||||||
|
{index + 1}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<div style={{ flex: "1" }}></div>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={currTab}
|
value={currTab}
|
||||||
@ -120,11 +153,12 @@ export function FamilyMemberTreeRoute(): React.ReactElement {
|
|||||||
{/* the tree itself */}
|
{/* the tree itself */}
|
||||||
<Paper style={{ flex: "1" }}>
|
<Paper style={{ flex: "1" }}>
|
||||||
{currTab === CurrTab.BasicTree ? (
|
{currTab === CurrTab.BasicTree ? (
|
||||||
<BasicFamilyTree tree={tree!} />
|
<BasicFamilyTree tree={tree!} depth={currDepth} />
|
||||||
) : (
|
) : (
|
||||||
<ComplexFamilyTree
|
<ComplexFamilyTree
|
||||||
tree={tree!}
|
tree={tree!}
|
||||||
isUp={currMode === TreeMode.Ascending}
|
isUp={currMode === TreeMode.Ascending}
|
||||||
|
depth={currDepth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -82,6 +82,9 @@ export function buildDescendingTree(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort family tree children per date of birth
|
||||||
|
*/
|
||||||
function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] {
|
function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] {
|
||||||
n.sort(
|
n.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
@ -89,3 +92,71 @@ function sortChildren(n: FamilyTreeNode[]): FamilyTreeNode[] {
|
|||||||
);
|
);
|
||||||
return n;
|
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));
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { MemberPhoto } from "./MemberPhoto";
|
|||||||
|
|
||||||
export function BasicFamilyTree(p: {
|
export function BasicFamilyTree(p: {
|
||||||
tree: FamilyTreeNode;
|
tree: FamilyTreeNode;
|
||||||
|
depth: number;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<TreeView
|
<TreeView
|
||||||
@ -20,12 +21,15 @@ export function BasicFamilyTree(p: {
|
|||||||
defaultExpandIcon={<ChevronRightIcon />}
|
defaultExpandIcon={<ChevronRightIcon />}
|
||||||
sx={{ flexGrow: 1 }}
|
sx={{ flexGrow: 1 }}
|
||||||
>
|
>
|
||||||
<FamilyTreeItem n={p.tree} />
|
<FamilyTreeItem n={p.tree} depth={p.depth} />
|
||||||
</TreeView>
|
</TreeView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function FamilyTreeItem(p: { n: FamilyTreeNode }): React.ReactElement {
|
function FamilyTreeItem(p: {
|
||||||
|
depth: number;
|
||||||
|
n: FamilyTreeNode;
|
||||||
|
}): React.ReactElement {
|
||||||
let children = p.n.down ?? [];
|
let children = p.n.down ?? [];
|
||||||
|
|
||||||
if (p.n.couples) {
|
if (p.n.couples) {
|
||||||
@ -55,9 +59,10 @@ function FamilyTreeItem(p: { n: FamilyTreeNode }): React.ReactElement {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{children.map((c) => (
|
{p.depth >= 2 &&
|
||||||
<FamilyTreeItem key={c.member.id} n={c} />
|
children.map((c) => (
|
||||||
))}
|
<FamilyTreeItem depth={p.depth - 1} key={c.member.id} n={c} />
|
||||||
|
))}
|
||||||
</TreeItem>
|
</TreeItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,19 @@ import "svg2pdf.js";
|
|||||||
import { Couple } from "../../api/CoupleApi";
|
import { Couple } from "../../api/CoupleApi";
|
||||||
import { Member, fmtDate } from "../../api/MemberApi";
|
import { Member, fmtDate } from "../../api/MemberApi";
|
||||||
import { useDarkTheme } from "../../hooks/context_providers/DarkThemeProvider";
|
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 { downloadBlob } from "../../utils/files_utils";
|
||||||
import "./family-chart.css";
|
import "./family-chart.css";
|
||||||
|
|
||||||
export function ComplexFamilyTree(p: {
|
export function ComplexFamilyTree(p: {
|
||||||
tree: FamilyTreeNode;
|
tree: FamilyTreeNode;
|
||||||
isUp: boolean;
|
isUp: boolean;
|
||||||
|
depth: number;
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const darkTheme = useDarkTheme();
|
const darkTheme = useDarkTheme();
|
||||||
|
|
||||||
@ -23,7 +29,7 @@ export function ComplexFamilyTree(p: {
|
|||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const store = f3.createStore({
|
const store = f3.createStore({
|
||||||
data: treeToF3Data(p.tree, p.isUp),
|
data: treeToF3Data(p.tree, p.isUp, p.depth),
|
||||||
node_separation: 250,
|
node_separation: 250,
|
||||||
level_separation: 150,
|
level_separation: 150,
|
||||||
});
|
});
|
||||||
@ -197,41 +203,12 @@ export function ComplexFamilyTree(p: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function treeHeight(node: FamilyTreeNode): number {
|
function treeToF3Data(
|
||||||
let res =
|
node: FamilyTreeNode,
|
||||||
node.down?.reduce((prev, node) => Math.max(prev, treeHeight(node)), 0) ?? 0;
|
isUp: boolean,
|
||||||
|
depth: number
|
||||||
node.couples?.forEach(
|
): f3Data[] {
|
||||||
(c) =>
|
const availableMembers = getAvailableMembers(node, depth);
|
||||||
(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<number>();
|
|
||||||
getAvailableMembers(node, availableMembers);
|
|
||||||
|
|
||||||
const list: f3Data[] = [];
|
const list: f3Data[] = [];
|
||||||
if (isUp) treeToF3DataUpRecurse(node, list, availableMembers);
|
if (isUp) treeToF3DataUpRecurse(node, list, availableMembers);
|
||||||
@ -239,17 +216,6 @@ function treeToF3Data(node: FamilyTreeNode, isUp: boolean): f3Data[] {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableMembers(t: FamilyTreeNode, s: Set<number>) {
|
|
||||||
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 {
|
function memberData(m: Member, c?: Couple): f3.f3DataData {
|
||||||
return {
|
return {
|
||||||
first_name: m.first_name ?? "_",
|
first_name: m.first_name ?? "_",
|
||||||
@ -271,6 +237,8 @@ function treeToF3DataUpRecurse(
|
|||||||
child?: number,
|
child?: number,
|
||||||
spouses?: number[]
|
spouses?: number[]
|
||||||
) {
|
) {
|
||||||
|
if (!availableMembers.has(node.member.id)) return;
|
||||||
|
|
||||||
array.push({
|
array.push({
|
||||||
data: memberData(node.member),
|
data: memberData(node.member),
|
||||||
id: node.member.id.toString(),
|
id: node.member.id.toString(),
|
||||||
@ -309,11 +277,13 @@ function treeToF3DataDownRecurse(
|
|||||||
array: f3Data[],
|
array: f3Data[],
|
||||||
availableMembers: Set<number>
|
availableMembers: Set<number>
|
||||||
) {
|
) {
|
||||||
|
if (!availableMembers.has(node.member.id)) return;
|
||||||
|
|
||||||
// Get all members ids
|
// Get all members ids
|
||||||
const children = node?.down?.map((c) => c.member.id.toString()) ?? [];
|
let children = node?.down?.map((c) => c.member.id) ?? [];
|
||||||
node.couples?.map((c) =>
|
node.couples?.map((c) => c.down.forEach((m) => children.push(m.member.id)));
|
||||||
c.down.forEach((m) => children.push(m.member.id.toString()))
|
|
||||||
);
|
children = children.filter((c) => availableMembers.has(c));
|
||||||
|
|
||||||
array.push({
|
array.push({
|
||||||
data: memberData(node.member),
|
data: memberData(node.member),
|
||||||
@ -328,8 +298,10 @@ function treeToF3DataDownRecurse(
|
|||||||
? node.member.mother.toString()
|
? node.member.mother.toString()
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
spouses: node.couples?.map((c) => c.member.id.toString()),
|
spouses: node.couples
|
||||||
children: children,
|
?.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) {
|
if (node.couples) {
|
||||||
for (const c of node.couples) {
|
for (const c of node.couples) {
|
||||||
|
if (!availableMembers.has(c.member.id)) continue;
|
||||||
array.push({
|
array.push({
|
||||||
data: memberData(c.member, c.couple),
|
data: memberData(c.member, c.couple),
|
||||||
id: c.member.id.toString(),
|
id: c.member.id.toString(),
|
||||||
@ -352,7 +325,9 @@ function treeToF3DataDownRecurse(
|
|||||||
? c.member.mother.toString()
|
? c.member.mother.toString()
|
||||||
: undefined,
|
: undefined,
|
||||||
spouses: [node.member.id.toString()],
|
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()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user