Files
ragflow/web/src/components/artifact-force-graph/use-graph-highlight.ts

90 lines
2.5 KiB
TypeScript

import { useCallback, useMemo, useState, type ComponentProps } from 'react';
import ForceGraph2D from 'react-force-graph-2d';
import { renderNodeLabel } from './node-label';
import {
DefaultLinkWidth,
DimmedAlpha,
HighlightLinkWidth,
} from './node-style';
import { type ArtifactGraphLink, type ArtifactGraphNode } from './types';
import { getBaseLinkColor, withAlpha } from './utils';
type PaintNodeFn = NonNullable<
ComponentProps<typeof ForceGraph2D>['nodeCanvasObject']
>;
export const useGraphHighlight = (
containerRef: React.RefObject<HTMLElement>,
pinnedNode?: ArtifactGraphNode | null,
) => {
const [hoverNode, setHoverNode] = useState<ArtifactGraphNode | null>(null);
// Real hover takes precedence; falls back to the externally controlled pinned node when not hovering
const activeNode = hoverNode ?? pinnedNode ?? null;
const highlightNodes = useMemo(() => {
const nodes = new Set<ArtifactGraphNode>();
if (activeNode) {
nodes.add(activeNode);
activeNode.__neighbors?.forEach((neighbor) => nodes.add(neighbor));
}
return nodes;
}, [activeNode]);
const highlightLinks = useMemo(
() => new Set<ArtifactGraphLink>(activeNode?.__links ?? []),
[activeNode],
);
const handleNodeHover = useCallback((node: ArtifactGraphNode | null) => {
setHoverNode(node ?? null);
}, []);
const getNodeColor = useCallback(
(node: ArtifactGraphNode) =>
activeNode && !highlightNodes.has(node)
? withAlpha(node.__color, DimmedAlpha)
: node.__color,
[activeNode, highlightNodes],
);
const getLinkColor = useCallback(
(link: ArtifactGraphLink) => {
const baseColor = getBaseLinkColor(containerRef.current);
return activeNode && !highlightLinks.has(link)
? withAlpha(baseColor, DimmedAlpha)
: baseColor;
},
[containerRef, activeNode, highlightLinks],
);
const getLinkWidth = useCallback(
(link: ArtifactGraphLink) =>
highlightLinks.has(link) ? HighlightLinkWidth : DefaultLinkWidth,
[highlightLinks],
);
const paintNode = useCallback<PaintNodeFn>(
(node, ctx, globalScale) => {
const dimmed =
activeNode !== null && !highlightNodes.has(node as ArtifactGraphNode);
if (dimmed) {
ctx.globalAlpha = DimmedAlpha;
}
renderNodeLabel(node, ctx, globalScale);
if (dimmed) {
ctx.globalAlpha = 1;
}
},
[highlightNodes, activeNode],
);
return {
handleNodeHover,
getNodeColor,
getLinkColor,
getLinkWidth,
paintNode,
};
};