Skip to content

Commit

Permalink
Merge branch 'feature/taxa-tree-improvements'
Browse files Browse the repository at this point in the history
  • Loading branch information
gsterjov committed Dec 18, 2024
2 parents 9da1526 + 3875a47 commit 423e5b8
Showing 1 changed file with 111 additions and 77 deletions.
188 changes: 111 additions & 77 deletions src/components/graphing/taxon-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,19 @@ function convertToNode(

// if the node is expanded then we want to convert all the children to nodes as well,
// otherwise we add a stub node that will load the data when expanded
children: shouldExpand ? children || [] : [],
children: shouldExpand
? children || []
: [
{
visible: false,
expanded: false,
pinned: false,
loaded: false,
isLoader: false,
canonicalName: "Loading...",
rank: "",
},
],
};
}

Expand All @@ -151,11 +163,16 @@ export function TaxonomyTree({
initialExpanded?.map((n) => convertToNode(n, [])) || [],
);
const client = useApolloClient();
const [root, setRoot] = useState(convertToNode(data, expanded, pinned));

// we maintain two trees for this graph. the graphql results are cached in tree which
// gets updated when an incremental load of a tree node happens.
// a separate tree converted to interactive nodes is derived from the cached tree.
const [tree, setTree] = useState(data);
const [root, setRoot] = useState(convertToNode(tree, expanded, pinned));

useEffect(() => {
setRoot(convertToNode(data, expanded, pinned));
}, [expanded]);
setRoot(convertToNode(tree, expanded, pinned));
}, [expanded, tree]);

// expand and load children when a node is expanded
function nodeClicked(item: ComputedNode<Node>) {
Expand All @@ -172,16 +189,21 @@ export function TaxonomyTree({
},
});

// slot the loaded data into the tree and kick of a re-render
result.then((res) => {
const children = res.data.stats.taxonBreakdown[0]?.children || [];
const nodes = children.map((n) => convertToNode(n, []));

const parent = root.children?.find(
// clone the underlying data tree and find the taxon node being loaded
let newTree = structuredClone(tree);
let parent = newTree.children?.find(
(node) => node.canonicalName == item.data.canonicalName,
);

// if we found the loaded node we insert the children and reload
// both the data cache and the tree root to effect the display changes
if (parent) {
parent.children = nodes;
setRoot(root);
parent.children = children;
setTree(newTree);
}
});
} else {
Expand All @@ -197,75 +219,86 @@ export function TaxonomyTree({
const width = minWidth > minTreeWidth ? minWidth : minTreeWidth;

return (
<Tree
width={width}
height={800}
layout={layout}
mode="tree"
data={root}
identity="canonicalName"
activeNodeSize={24}
inactiveNodeSize={12}
nodeColor={{ scheme: "tableau10" }}
fixNodeColorAtDepth={2}
linkThickness={2}
activeLinkThickness={8}
inactiveLinkThickness={2}
linkColor={{
from: "target.color",
modifiers: [["opacity", 0.4]],
}}
margin={{
top: layout === "top-to-bottom" ? 100 : 10,
bottom: layout === "top-to-bottom" ? 200 : 10,
right: layout === "right-to-left" ? 100 : 10,
left: layout === "right-to-left" ? 200 : 10,
}}
motionConfig="stiff"
meshDetectionRadius={80}
nodeTooltip={(item) =>
item.node.data.visible && (
<Paper
p="md"
style={{
background: "rgba(255,255,255,0.6)",
backdropFilter: "blur(6px)",
}}
>
<Group align="baseline">
<Text size="xs">{item.node.data.rank}</Text>
<Text fs="italic">{item.node.data.canonicalName}</Text>
</Group>
<Group justify="center" my="xs">
<StatBadge label="Loci" stat={item.node.data.loci} />
<StatBadge label="Genomes" stat={item.node.data.genomes} />
<StatBadge label="Specimens" stat={item.node.data.specimens} />
</Group>
<Group justify="center">
<StatBadge label="Other" stat={item.node.data.other} />
<StatBadge
label="Total genomic"
stat={item.node.data.totalGenomic}
/>
</Group>
</Paper>
)
}
onNodeClick={nodeClicked}
onLinkMouseEnter={() => {}}
onLinkMouseMove={() => {}}
onLinkMouseLeave={() => {}}
onLinkClick={() => {}}
/* @ts-ignore */
linkTooltip={undefined}
linkTooltipAnchor={"center"}
nodeComponent={CustomNode}
linkComponent={CustomLink}
labelComponent={CustomLabel}
debugMesh={false}
isInteractive={true}
useMesh={true}
/>
<>
<svg>
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood floodColor="rgba(255, 255, 255, .7)" />
<feBlend in="SourceGraphic" mode="multiply" />
</filter>
</defs>
</svg>

<Tree
width={width}
height={800}
layout={layout}
mode="tree"
data={root}
identity="canonicalName"
activeNodeSize={24}
inactiveNodeSize={12}
nodeColor={{ scheme: "tableau10" }}
fixNodeColorAtDepth={2}
linkThickness={2}
activeLinkThickness={8}
inactiveLinkThickness={2}
linkColor={{
from: "target.color",
modifiers: [["opacity", 0.4]],
}}
margin={{
top: layout === "top-to-bottom" ? 100 : 10,
bottom: layout === "top-to-bottom" ? 200 : 10,
right: layout === "right-to-left" ? 100 : 10,
left: layout === "right-to-left" ? 200 : 10,
}}
motionConfig="stiff"
meshDetectionRadius={80}
nodeTooltip={(item) =>
item.node.data.visible && (
<Paper
p="md"
style={{
background: "rgba(255,255,255,0.6)",
backdropFilter: "blur(6px)",
}}
>
<Group align="baseline">
<Text size="xs">{item.node.data.rank}</Text>
<Text fs="italic">{item.node.data.canonicalName}</Text>
</Group>
<Group justify="center" my="xs">
<StatBadge label="Loci" stat={item.node.data.loci} />
<StatBadge label="Genomes" stat={item.node.data.genomes} />
<StatBadge label="Specimens" stat={item.node.data.specimens} />
</Group>
<Group justify="center">
<StatBadge label="Other" stat={item.node.data.other} />
<StatBadge
label="Total genomic"
stat={item.node.data.totalGenomic}
/>
</Group>
</Paper>
)
}
onNodeClick={nodeClicked}
onLinkMouseEnter={() => {}}
onLinkMouseMove={() => {}}
onLinkMouseLeave={() => {}}
onLinkClick={() => {}}
/* @ts-ignore */
linkTooltip={undefined}
linkTooltipAnchor={"center"}
nodeComponent={CustomNode}
linkComponent={CustomLink}
labelComponent={CustomLabel}
debugMesh={false}
isInteractive={true}
useMesh={true}
/>
</>
);
}

Expand Down Expand Up @@ -364,6 +397,7 @@ function CustomLabel({ label, animatedProps }: LabelComponentProps<Node>) {
textAnchor={label.textAnchor}
fontWeight={pinned ? 800 : 500}
dominantBaseline={label.baseline}
filter="url(#solid)"
>
{label.label}
</text>
Expand Down

0 comments on commit 423e5b8

Please sign in to comment.