From 59d4618c2e8eda672decfaedd509475389d236cd Mon Sep 17 00:00:00 2001 From: hhs <386998068.com> Date: Mon, 6 Apr 2026 16:07:43 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/GraphView/index.tsx | 45 ++++++------ frontend/src/config/constants.ts | 15 ++++ frontend/src/utils/graphStyle.ts | 76 +++++++++++++++++++++ 3 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 frontend/src/utils/graphStyle.ts diff --git a/frontend/src/components/GraphView/index.tsx b/frontend/src/components/GraphView/index.tsx index 8d51dcf..816df5a 100644 --- a/frontend/src/components/GraphView/index.tsx +++ b/frontend/src/components/GraphView/index.tsx @@ -1,9 +1,10 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useMemo } from 'react'; import { Graph } from '@antv/g6'; import type { GraphData, Node, Edge, GraphLayoutConfig } from '../../types/graph'; import { getNodeColors } from '../../config/colors'; import { FORCE_LAYOUT_CONFIG, GRAPH_BEHAVIORS } from '../../config/layout'; import { GRAPH_DEFAULT_HEIGHT } from '../../config/constants'; +import { getNodeStyle, getEdgeStyle } from '../../utils/graphStyle'; interface GraphViewProps { data: GraphData; @@ -30,6 +31,8 @@ export const GraphView = ({ const onNodeClickRef = useRef(onNodeClick); + const nodeMap = useMemo(() => new Map(data.nodes.map((node: Node) => [node.id, node])), [data.nodes]); + useEffect(() => { onNodeClickRef.current = onNodeClick; }, [onNodeClick]); @@ -69,26 +72,27 @@ export const GraphView = ({ }, }; }), - edges: data.edges.map((edge: Edge) => ({ - id: edge.id, - source: edge.source, - target: edge.target, - label: edge.label, - data: { + edges: data.edges.map((edge: Edge) => { + return { + id: edge.id, source: edge.source, target: edge.target, label: edge.label, - type: edge.type, - properties: edge.properties, - }, - style: { - stroke: '#95A5A6', - lineWidth: 2, - labelText: edge.label, - labelFill: '#666', - labelFontSize: 10, - }, - })), + data: { + source: edge.source, + target: edge.target, + label: edge.label, + type: edge.type, + properties: edge.properties, + }, + style: { + stroke: '#95A5A6', + labelText: edge.label, + labelFill: '#666', + labelFontSize: 10, + }, + }; + }), }; // 创建图谱配置 @@ -101,8 +105,8 @@ export const GraphView = ({ node: { type: 'circle', style: { - size: 50, lineWidth: 2, + ...getNodeStyle(), }, state: { selected: { @@ -117,13 +121,12 @@ export const GraphView = ({ edge: { type: 'line', style: { - lineWidth: 1, endArrow: true, + ...getEdgeStyle(nodeMap), }, state: { selected: { stroke: '#1890FF', - lineWidth: 3, }, }, }, diff --git a/frontend/src/config/constants.ts b/frontend/src/config/constants.ts index 6b07d04..87caa35 100644 --- a/frontend/src/config/constants.ts +++ b/frontend/src/config/constants.ts @@ -5,5 +5,20 @@ export const API_TIMEOUT = 10000; // 图谱渲染配置 export const GRAPH_DEFAULT_HEIGHT = 600; +// 节点大小配置 +export const DEFAULT_NODE_SIZE = 50; +export const NODE_SIZE_MIN = 10; +export const NODE_SIZE_MAX = 60; + +// 边宽度配置 +export const DEFAULT_EDGE_WIDTH = 1.5; +export const EDGE_WIDTH_MIN = 1; +export const EDGE_WIDTH_MAX = 2; + +// Importance 配置 +export const IMPORTANCE_DEFAULT = 0.8; +export const IMPORTANCE_MIN = 0; +export const IMPORTANCE_MAX = 1; + // 搜索相关常量 export const SEARCH_RESULT_MAX_HEIGHT = 256; // 16rem diff --git a/frontend/src/utils/graphStyle.ts b/frontend/src/utils/graphStyle.ts new file mode 100644 index 0000000..09d1894 --- /dev/null +++ b/frontend/src/utils/graphStyle.ts @@ -0,0 +1,76 @@ +import type { Node } from '../types/graph'; +import { + NODE_SIZE_MIN, + NODE_SIZE_MAX, + EDGE_WIDTH_MIN, + EDGE_WIDTH_MAX, + IMPORTANCE_MIN, + IMPORTANCE_MAX, +} from '../config/constants'; + +/** + * 归一化值到 [0,1] 范围 + */ +function normalizeValue(value: number, min: number, max: number): number { + if (isNaN(value) || !isFinite(value)) { + return 0.5; + } + const clamped = Math.max(min, Math.min(max, value)); + return (clamped - min) / (max - min); +} + +/** + * 反归一化值从 [0,1] 到目标范围 + */ +function denormalizeValue(value: number, min: number, max: number): number { + if (isNaN(value) || !isFinite(value)) { + return (min + max) / 2; + } + const clamped = Math.max(0, Math.min(1, value)); + return clamped * (max - min) + min; +} + +/** + * 计算节点大小 + */ +export function calculateNodeSize(properties?: any): number { + const importance = properties?.importance ?? IMPORTANCE_MAX; + const normalized = normalizeValue(importance, IMPORTANCE_MIN, IMPORTANCE_MAX); + const size = denormalizeValue(normalized, NODE_SIZE_MIN, NODE_SIZE_MAX) ?? NODE_SIZE_MIN; + return Math.round(size); +} + +/** + * 计算边的宽度 + */ +export function calculateEdgeWidth(sourceProperties?: any, targetProperties?: any): number { + const sourceImportance = sourceProperties?.importance ?? IMPORTANCE_MAX; + const targetImportance = targetProperties?.importance ?? IMPORTANCE_MAX; + const avgImportance = (sourceImportance + targetImportance) / 2; + const normalized = normalizeValue(avgImportance, IMPORTANCE_MIN, IMPORTANCE_MAX); + const width = denormalizeValue(normalized, EDGE_WIDTH_MIN, EDGE_WIDTH_MAX) ?? EDGE_WIDTH_MIN; + return parseFloat(width.toFixed(2)); +} + +/** + * G6 节点动态样式配置函数 + * 根据 G6 文档推荐的动态配置方式实现 + */ +export const getNodeStyle = () => ({ + size: (datum: any) => { + const size = calculateNodeSize(datum.data?.properties); + return size; + }, +}); + +/** + * G6 边动态样式配置函数 + * 根据 G6 文档推荐的动态配置方式实现 + */ +export const getEdgeStyle = (nodeMap: Map) => ({ + lineWidth: (datum: any) => { + const sourceNode = nodeMap.get(datum.source); + const targetNode = nodeMap.get(datum.target); + return calculateEdgeWidth(sourceNode?.properties, targetNode?.properties); + }, +}); \ No newline at end of file