Reputation: 1243
I figured it out using the helper functions. Not sure if there is a better/preferred way to do this, but this seems to work. Here are my recursive functions that get all incoming and outgoing nodes from a selected node.
Upvotes: 0
Views: 60
Reputation: 1243
const getAllIncomers = (node : Node, nodes: Node[], edges:Edge[], prevIncomers: Node[] = []) => {
const incomers = getIncomers(node, nodes, edges);
const result = incomers.reduce(
(memo:Node[], incomer:Node) => {
memo.push(incomer);
if ((prevIncomers.findIndex(n => n.id == incomer.id) == -1)) {
prevIncomers.push(incomer);
getAllIncomers(incomer, nodes, edges, prevIncomers).forEach((foundNode:Node) => {
memo.push(foundNode);
if ((prevIncomers.findIndex(n => n.id == foundNode.id) == -1)) {
prevIncomers.push(incomer);
}
});
}
return memo;
},
[]
);
return result;
}
const getAllOutgoers = (node:Node, nodes:Node[], edges:Edge[], prevOutgoers:Node[] = []) => {
const outgoers = getOutgoers(node, nodes, edges);
return outgoers.reduce(
(memo:Node[], outgoer:Node) => {
memo.push(outgoer);
if ((prevOutgoers.findIndex(n => n.id == outgoer.id) == -1)) {
prevOutgoers.push(outgoer);
getAllOutgoers(outgoer, nodes, edges, prevOutgoers).forEach((foundNode:Node) => {
memo.push(foundNode);
if ((prevOutgoers.findIndex(n => n.id == foundNode.id) == -1)) {
prevOutgoers.push(foundNode);
}
});
}
return memo;
},
[]
)
}
I've also wrote custom getIncomers and getOutgoers in order to use them with custom nodes
const getIncomers = (node:Node, nodes:Node[], edges:Edge[]) => {
if (!isNode(node)) {
return [];
}
const incomersIds = edges
.filter((e) => e.target === node.id)
.map((e) => e.source);
return nodes.filter((e) => incomersIds.map((id) => {
const matches = /([\w-^]+)__([\w-]+)/.exec(id);
if (matches === null) {
return id;
}
return matches[1];
}).includes(e.id));
};
const getOutgoers = (node:Node, nodes:Node[], edges:Edge[]) => {
if (!isNode(node)) {
return [];
}
const outgoerIds = edges
.filter((e) => e.source === node.id)
.map((e) => e.target);
return nodes.filter((n) => outgoerIds.map((id) => {
const matches = /([\w-^]+)__([\w-]+)/.exec(id);
if (matches === null) {
return id;
}
return matches[1];
}).includes(n.id));
};
Now the highlight & Reset function looks like this
const highlightPath = (node:Node, nodes:Node[], edges:Edge[], selection:Boolean) => {
if (node && [...nodes, ...edges]) {
const allIncomers = getAllIncomers(node, nodes, edges)
const allOutgoers = getAllOutgoers(node, nodes, edges)
setNodes((prevElements) => {
return prevElements?.map((elem) => {
const incomerIds = allIncomers.map((i) => i.id)
const outgoerIds = allOutgoers.map((o) => o.id)
if (isNode(elem) && (allOutgoers.length > 0 || allIncomers.length > 0)) {
const highlight = elem.id === node.id || incomerIds.includes(elem.id) || outgoerIds.includes(elem.id)
elem.style = {
...elem.style,
opacity: highlight ? 1 : 0.25,
}
}
if (isEdge(elem)) {
if (selection) {
const animated =
incomerIds.includes(elem.source) && (incomerIds.includes(elem.target) || node.id === elem.target)
elem.animated = animated
elem.style = {
...elem.style,
stroke: animated ? colors.blue[600] : '#b1b1b7',
opacity: animated ? 1 : 0.25,
}
} else {
elem.animated = false
elem.style = {
...elem.style,
stroke: '#b1b1b7',
opacity: 1,
}
}
}
return elem
})
})
}
}
const resetNodeStyles = () => {
setNodes((prevElements) => {
return prevElements?.map((elem) => {
if (isNode(elem)) {
elem.style = {
...elem.style,
opacity: 1,
}
} else {
// elem.animated = false
// elem.style = {
// ...elem.style,
// stroke: '#b1b1b7',
// opacity: 1,
// }
}
return elem
})
})
}
Finally use like this
<ReactFlow
nodes={nodes}
edges={edges}
snapToGrid={true}
preventScrolling={true}
snapGrid={[10, 10]}
elementsSelectable={true}
// onNodeMouseEnter={(_event, node) => !selectedNode && highlightPath(node, [...nodes, ...edges])}
// onNodeMouseLeave={() => !selectedNode && resetNodeStyles()}
onSelectionChange={(selectedElements) => {
const node = selectedElements.nodes[0]
setSelectedNode(node)
highlightPath(node, nodes, edges, true)
}}
onPaneClick={() => {
resetNodeStyles()
setSelectedNode(undefined)
}}
/>
To change init trigger from select to hover - uncomment two lines. And add
const [selectedNode, setSelectedNode] = useState();
Upvotes: 0