"use client"; import { useCallback, useState } from "react"; import dynamic from "next/dynamic"; import { DOMAIN_LABELS } from "~/components/analytics/computation-graph/domain-colors"; import { createNodeSprite, createDimmedNodeSprite, getLinkColor } from "~/components/analytics/computation-graph/node-renderer"; import type { PositionedNode } from "~/components/analytics/computation-graph/graph-data"; import type { ComputationGraphState } from "~/components/analytics/computation-graph/useComputationGraphData"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const ForceGraph3D = dynamic(() => import("react-force-graph-3d"), { ssr: false }) as any; interface Props { state: ComputationGraphState; } export default function ComputationGraph3DView({ state }: Props) { const { graphData, highlightedNodes, handleNodeClick, setHoveredNode } = state; const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); // eslint-disable-next-line @typescript-eslint/no-explicit-any const onNodeClick = useCallback((node: any) => { handleNodeClick((node as PositionedNode).id); }, [handleNodeClick]); // eslint-disable-next-line @typescript-eslint/no-explicit-any const onNodeHover = useCallback((node: any) => { setHoveredNode(node as PositionedNode | null); }, [setHoveredNode]); const nodeThreeObject = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (node: any) => { const n = node as PositionedNode; if (highlightedNodes && !highlightedNodes.has(n.id)) { return createDimmedNodeSprite(n); } return createNodeSprite(n); }, [highlightedNodes], ); const linkColorFn = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (link: any) => { if (!highlightedNodes) return getLinkColor(link, 0.4); const sourceId = typeof link.source === "object" ? link.source.id : link.source; const targetId = typeof link.target === "object" ? link.target.id : link.target; if (highlightedNodes.has(sourceId) && highlightedNodes.has(targetId)) { return getLinkColor(link, 0.9); } return getLinkColor(link, 0.08); }, [highlightedNodes], ); const linkWidthFn = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (link: any) => { if (!highlightedNodes) return link.weight ?? 1; const sourceId = typeof link.source === "object" ? link.source.id : link.source; const targetId = typeof link.target === "object" ? link.target.id : link.target; if (highlightedNodes.has(sourceId) && highlightedNodes.has(targetId)) { return (link.weight ?? 1) * 2.5; } return 0.3; }, [highlightedNodes], ); const handleMouseMove = useCallback((e: React.MouseEvent) => { setMousePos({ x: e.clientX, y: e.clientY }); }, []); if (graphData.nodes.length === 0) { return (
{state.viewMode === "resource" ? "Select a resource and month" : "Select a project"}
); } return (
{/* Hover Tooltip */} {state.hoveredNode && (
{state.hoveredNode.label} {DOMAIN_LABELS[state.hoveredNode.domain]}
{state.hoveredNode.value} {state.hoveredNode.unit}
{state.hoveredNode.description}
{state.hoveredNode.formula && (
{state.hoveredNode.formula}
)}
)}
); }