2025-03-15 00:33:53 +08:00

261 lines
8.5 KiB
TypeScript

import { useRegisterEvents, useSetSettings, useSigma } from '@react-sigma/core'
import { AbstractGraph } from 'graphology-types'
// import { useLayoutCircular } from '@react-sigma/layout-circular'
import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2'
import { useEffect } from 'react'
// import useRandomGraph, { EdgeType, NodeType } from '@/hooks/useRandomGraph'
import { EdgeType, NodeType } from '@/hooks/useLightragGraph'
import useTheme from '@/hooks/useTheme'
import * as Constants from '@/lib/constants'
import { useSettingsStore } from '@/stores/settings'
import { useGraphStore } from '@/stores/graph'
const isButtonPressed = (ev: MouseEvent | TouchEvent) => {
if (ev.type.startsWith('mouse')) {
if ((ev as MouseEvent).buttons !== 0) {
return true
}
}
return false
}
const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean }) => {
const sigma = useSigma<NodeType, EdgeType>()
const registerEvents = useRegisterEvents<NodeType, EdgeType>()
const setSettings = useSetSettings<NodeType, EdgeType>()
const maxIterations = useSettingsStore.use.graphLayoutMaxIterations()
const { assign: assignLayout } = useLayoutForceAtlas2({
iterations: maxIterations
})
const { theme } = useTheme()
const hideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents()
const renderEdgeLabels = useSettingsStore.use.showEdgeLabel()
const renderLabels = useSettingsStore.use.showNodeLabel()
const selectedNode = useGraphStore.use.selectedNode()
const focusedNode = useGraphStore.use.focusedNode()
const selectedEdge = useGraphStore.use.selectedEdge()
const focusedEdge = useGraphStore.use.focusedEdge()
const sigmaGraph = useGraphStore.use.sigmaGraph()
/**
* When component mount or maxIterations changes
* => ensure graph reference and apply layout
*/
useEffect(() => {
if (sigmaGraph && sigma) {
// 确保 sigma 实例内部的 graph 引用被更新
try {
// 尝试直接设置 sigma 实例的 graph 引用
if (typeof sigma.setGraph === 'function') {
sigma.setGraph(sigmaGraph as unknown as AbstractGraph<NodeType, EdgeType>);
console.log('Directly set graph on sigma instance');
} else {
// 如果 setGraph 方法不存在,尝试直接设置 graph 属性
(sigma as any).graph = sigmaGraph;
console.log('Set graph property on sigma instance');
}
} catch (error) {
console.error('Error setting graph on sigma instance:', error);
}
// 应用布局
assignLayout();
console.log('Layout applied to graph');
}
}, [sigma, sigmaGraph, assignLayout, maxIterations])
/**
* Ensure the sigma instance is set in the store
* This provides a backup in case the instance wasn't set in GraphViewer
*/
useEffect(() => {
if (sigma) {
// Double-check that the store has the sigma instance
const currentInstance = useGraphStore.getState().sigmaInstance;
if (!currentInstance) {
console.log('Setting sigma instance from GraphControl (backup)');
useGraphStore.getState().setSigmaInstance(sigma);
}
}
}, [sigma]);
/**
* When component mount
* => register events
*/
useEffect(() => {
const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } =
useGraphStore.getState()
// Define event types
type NodeEvent = { node: string; event: { original: MouseEvent | TouchEvent } }
type EdgeEvent = { edge: string; event: { original: MouseEvent | TouchEvent } }
// Register all events, but edge events will only be processed if enableEdgeEvents is true
const events: Record<string, any> = {
enterNode: (event: NodeEvent) => {
if (!isButtonPressed(event.event.original)) {
setFocusedNode(event.node)
}
},
leaveNode: (event: NodeEvent) => {
if (!isButtonPressed(event.event.original)) {
setFocusedNode(null)
}
},
clickNode: (event: NodeEvent) => {
setSelectedNode(event.node)
setSelectedEdge(null)
},
clickStage: () => clearSelection()
}
// Only add edge event handlers if enableEdgeEvents is true
if (enableEdgeEvents) {
events.clickEdge = (event: EdgeEvent) => {
setSelectedEdge(event.edge)
setSelectedNode(null)
}
events.enterEdge = (event: EdgeEvent) => {
if (!isButtonPressed(event.event.original)) {
setFocusedEdge(event.edge)
}
}
events.leaveEdge = (event: EdgeEvent) => {
if (!isButtonPressed(event.event.original)) {
setFocusedEdge(null)
}
}
}
// Register the events
registerEvents(events)
}, [registerEvents, enableEdgeEvents])
/**
* When component mount or hovered node change
* => Setting the sigma reducers
*/
useEffect(() => {
const isDarkTheme = theme === 'dark'
const labelColor = isDarkTheme ? Constants.labelColorDarkTheme : undefined
const edgeColor = isDarkTheme ? Constants.edgeColorDarkTheme : undefined
// Update all dynamic settings directly without recreating the sigma container
setSettings({
// Update display settings
enableEdgeEvents,
renderEdgeLabels,
renderLabels,
// Node reducer for node appearance
nodeReducer: (node, data) => {
const graph = sigma.getGraph()
const newData: NodeType & {
labelColor?: string
borderColor?: string
} = { ...data, highlighted: data.highlighted || false, labelColor }
if (!disableHoverEffect) {
newData.highlighted = false
const _focusedNode = focusedNode || selectedNode
const _focusedEdge = focusedEdge || selectedEdge
if (_focusedNode && graph.hasNode(_focusedNode)) {
try {
if (node === _focusedNode || graph.neighbors(_focusedNode).includes(node)) {
newData.highlighted = true
if (node === selectedNode) {
newData.borderColor = Constants.nodeBorderColorSelected
}
}
} catch (error) {
console.error('Error in nodeReducer:', error);
}
} else if (_focusedEdge && graph.hasEdge(_focusedEdge)) {
if (graph.extremities(_focusedEdge).includes(node)) {
newData.highlighted = true
newData.size = 3
}
} else {
return newData
}
if (newData.highlighted) {
if (isDarkTheme) {
newData.labelColor = Constants.LabelColorHighlightedDarkTheme
}
} else {
newData.color = Constants.nodeColorDisabled
}
}
return newData
},
// Edge reducer for edge appearance
edgeReducer: (edge, data) => {
const graph = sigma.getGraph()
const newData = { ...data, hidden: false, labelColor, color: edgeColor }
if (!disableHoverEffect) {
const _focusedNode = focusedNode || selectedNode
if (_focusedNode && graph.hasNode(_focusedNode)) {
try {
if (hideUnselectedEdges) {
if (!graph.extremities(edge).includes(_focusedNode)) {
newData.hidden = true
}
} else {
if (graph.extremities(edge).includes(_focusedNode)) {
newData.color = Constants.edgeColorHighlighted
}
}
} catch (error) {
console.error('Error in edgeReducer:', error);
}
} else {
const _selectedEdge = selectedEdge && graph.hasEdge(selectedEdge) ? selectedEdge : null;
const _focusedEdge = focusedEdge && graph.hasEdge(focusedEdge) ? focusedEdge : null;
if (_selectedEdge || _focusedEdge) {
if (edge === _selectedEdge) {
newData.color = Constants.edgeColorSelected
} else if (edge === _focusedEdge) {
newData.color = Constants.edgeColorHighlighted
} else if (hideUnselectedEdges) {
newData.hidden = true
}
}
}
}
return newData
}
})
}, [
selectedNode,
focusedNode,
selectedEdge,
focusedEdge,
setSettings,
sigma,
disableHoverEffect,
theme,
hideUnselectedEdges,
enableEdgeEvents,
renderEdgeLabels,
renderLabels
])
return null
}
export default GraphControl