fix: rewrite hover state to single object, fix link dimming on node hover

This commit is contained in:
Usman Baig
2026-03-12 22:56:13 +01:00
parent e7debdeb41
commit 9c8943d1e3

View File

@@ -152,9 +152,7 @@ export default function SankeyDiagram({
}: SankeyDiagramProps) { }: SankeyDiagramProps) {
const { resolvedTheme } = useTheme() const { resolvedTheme } = useTheme()
const isDark = resolvedTheme === 'dark' const isDark = resolvedTheme === 'dark'
const [hoveredLink, setHoveredLink] = useState<string | null>(null) const [hovered, setHovered] = useState<{ type: 'link' | 'node'; id: string } | null>(null)
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
const hasHover = hoveredLink !== null || hoveredNode !== null
const data = useMemo( const data = useMemo(
() => buildSankeyData(transitions, depth), () => buildSankeyData(transitions, depth),
@@ -215,30 +213,30 @@ export default function SankeyDiagram({
{layout.links.map((link, i) => { {layout.links.map((link, i) => {
const src = link.source as LayoutNode const src = link.source as LayoutNode
const tgt = link.target as LayoutNode const tgt = link.target as LayoutNode
const linkId = `${src.id}->${tgt.id}` const linkId = `${String(src.id)}->${String(tgt.id)}`
const isLinkHovered = hoveredLink === linkId
const srcId = String(src.id)
const tgtId = String(tgt.id)
const isConnectedToNode =
hoveredNode !== null &&
(srcId === hoveredNode || tgtId === hoveredNode)
const isHighlighted = isLinkHovered || isConnectedToNode
const linkColor = src.color let isHighlighted = false
if (hovered?.type === 'link') {
isHighlighted = hovered.id === linkId
} else if (hovered?.type === 'node') {
isHighlighted =
String(src.id) === hovered.id || String(tgt.id) === hovered.id
}
let opacity = isDark ? 0.45 : 0.5 let opacity = isDark ? 0.45 : 0.5
if (isHighlighted) opacity = 0.75 if (hovered) {
else if (hasHover) opacity = 0.08 opacity = isHighlighted ? 0.75 : 0.08
}
return ( return (
<path <path
key={i} key={i}
d={ribbonPath(link)} d={ribbonPath(link)}
fill={linkColor} fill={src.color}
opacity={opacity} opacity={opacity}
style={{ transition: 'opacity 0.15s ease' }} style={{ transition: 'opacity 0.15s ease' }}
onMouseEnter={() => setHoveredLink(linkId)} onMouseEnter={() => setHovered({ type: 'link', id: linkId })}
onMouseLeave={() => setHoveredLink(null)} onMouseLeave={() => setHovered(null)}
> >
<title> <title>
{src.label} {tgt.label}:{' '} {src.label} {tgt.label}:{' '}
@@ -253,7 +251,7 @@ export default function SankeyDiagram({
<g> <g>
{layout.nodes.map((node) => { {layout.nodes.map((node) => {
const nodeId = String(node.id) const nodeId = String(node.id)
const isExit = nodeId.startsWith('exit-') const isExit = nodeId.startsWith('exit')
const w = (node.x1 ?? 0) - (node.x0 ?? 0) const w = (node.x1 ?? 0) - (node.x0 ?? 0)
const h = (node.y1 ?? 0) - (node.y0 ?? 0) const h = (node.y1 ?? 0) - (node.y0 ?? 0)
@@ -271,8 +269,8 @@ export default function SankeyDiagram({
className={ className={
onNodeClick && !isExit ? 'cursor-pointer' : 'cursor-default' onNodeClick && !isExit ? 'cursor-pointer' : 'cursor-default'
} }
onMouseEnter={() => setHoveredNode(nodeId)} onMouseEnter={() => setHovered({ type: 'node', id: nodeId })}
onMouseLeave={() => setHoveredNode(null)} onMouseLeave={() => setHovered(null)}
onClick={() => { onClick={() => {
if (onNodeClick && !isExit) onNodeClick(node.label) if (onNodeClick && !isExit) onNodeClick(node.label)
}} }}
@@ -311,13 +309,13 @@ export default function SankeyDiagram({
const bgY = textY - rectH / 2 const bgY = textY - rectH / 2
const nodeId = String(node.id) const nodeId = String(node.id)
const isExit = nodeId.startsWith('exit-') const isExit = nodeId.startsWith('exit')
return ( return (
<g <g
key={`label-${nodeId}`} key={`label-${nodeId}`}
onMouseEnter={() => setHoveredNode(nodeId)} onMouseEnter={() => setHovered({ type: 'node', id: nodeId })}
onMouseLeave={() => setHoveredNode(null)} onMouseLeave={() => setHovered(null)}
> >
<rect <rect
x={bgX} x={bgX}