diff --git a/frontend/src/components/Legend/index.tsx b/frontend/src/components/Legend/index.tsx
index 5d1891d..e8a68a8 100644
--- a/frontend/src/components/Legend/index.tsx
+++ b/frontend/src/components/Legend/index.tsx
@@ -1,15 +1,27 @@
import { useState } from 'react';
-import { NODE_COLOR_MAP, NODE_TYPE_LABELS } from '../../config/colors';
+
+interface LegendData {
+ type: string;
+ colors: { fill: string; stroke: string };
+ labels: { zh: string; en: string };
+}
+
+interface LegendProps {
+ legendData: LegendData[];
+}
/**
* 图例组件
* 显示知识图谱中不同节点类型及其对应的颜色
* 支持折叠/展开展示
+ * @param {LegendData[]} legendData - 图例数据,包含类型、颜色和标签信息
*/
-export const Legend = () => {
+export const Legend = ({ legendData }: LegendProps) => {
const [isExpanded, setIsExpanded] = useState(true);
- const nodeTypes = Object.entries(NODE_COLOR_MAP);
+ if (!legendData || legendData.length === 0) {
+ return null;
+ }
return (
@@ -33,33 +45,30 @@ export const Legend = () => {
{isExpanded && (
- {nodeTypes.map(([type, colors], index) => {
- const labels = NODE_TYPE_LABELS[type] || { zh: type, en: type };
- return (
+ {legendData.map((item, index) => (
+
-
+ className="w-4 h-4 rounded-full flex-shrink-0 shadow-sm"
+ style={{
+ backgroundColor: item.colors.fill,
+ border: `1px solid ${item.colors.stroke}`,
+ }}
+ />
-
-
- {labels.zh}
-
-
- {labels.en}
-
-
+
+
+ {item.labels.zh}
+
+
+ {item.labels.en}
+
- );
- })}
+
+ ))}
)}
diff --git a/frontend/src/config/colors.ts b/frontend/src/config/colors.ts
index ab0975d..17863ec 100644
--- a/frontend/src/config/colors.ts
+++ b/frontend/src/config/colors.ts
@@ -1,30 +1,133 @@
-import type { Node } from '../types/graph';
+import type { Node, GraphData } from '../types/graph';
-// 节点类型颜色配置
-// 用于统一管理知识图谱中不同类型节点的视觉样式
-export const NODE_COLOR_MAP: Record
= {
- '概念': { fill: '#4ECDC4', stroke: '#45B7B0' },
- '工具': { fill: '#FFA07A', stroke: '#FF7F50' },
- '应用': { fill: '#2ECC71', stroke: '#27AE60' },
-};
+interface ColorConfig {
+ fill: string;
+ stroke: string;
+}
+
+interface NodeTypeInfo {
+ type: string;
+ colors: ColorConfig;
+ labels: { zh: string; en: string };
+}
+
+// 预定义颜色池 - 12种可区分的颜色对
+const COLOR_POOL: ColorConfig[] = [
+ { fill: '#4ECDC4', stroke: '#45B7B0' },
+ { fill: '#FFA07A', stroke: '#FF7F50' },
+ { fill: '#2ECC71', stroke: '#27AE60' },
+ { fill: '#FF6B9D', stroke: '#E84393' },
+ { fill: '#F39C12', stroke: '#D68910' },
+ { fill: '#9B59B6', stroke: '#8E44AD' },
+ { fill: '#3498DB', stroke: '#2980B9' },
+ { fill: '#1ABC9C', stroke: '#16A085' },
+ { fill: '#E74C3C', stroke: '#C0392B' },
+ { fill: '#607D8B', stroke: '#455A64' },
+ { fill: '#FF9800', stroke: '#F57C00' },
+ { fill: '#3F51B5', stroke: '#303F9F' },
+];
// 默认节点颜色(当类型未匹配时)
-export const DEFAULT_NODE_COLORS = { fill: '#C6E5FF', stroke: '#5B8FF9' };
+export const DEFAULT_NODE_COLORS: ColorConfig = { fill: '#C6E5FF', stroke: '#5B8FF9' };
// 节点类型双语标签映射
export const NODE_TYPE_LABELS: Record = {
'概念': { zh: '概念', en: 'Concept' },
'工具': { zh: '工具', en: 'Tool' },
'应用': { zh: '应用', en: 'Application' },
+ '属性': { zh: '属性', en: 'Attribute' },
+ '实体': { zh: '实体', en: 'Entity' },
+ '关系': { zh: '关系', en: 'Relation' },
+ '方法': { zh: '方法', en: 'Method' },
+ '函数': { zh: '函数', en: 'Function' },
+ '类': { zh: '类', en: 'Class' },
+ '模块': { zh: '模块', en: 'Module' },
};
-// 获取节点颜色配置
-export const getNodeColors = (node: Node): { fill: string; stroke: string } => {
+// 动态颜色分配缓存
+const colorAssignmentCache: Map = new Map();
+let colorPoolIndex = 0;
+
+/**
+ * 从颜色池中获取或分配颜色
+ * @param type 节点类型
+ * @returns 颜色配置对象
+ */
+const getOrAssignColor = (type: string): ColorConfig => {
+ if (!type) return DEFAULT_NODE_COLORS;
+
+ // 从缓存中查找
+ if (colorAssignmentCache.has(type)) {
+ return colorAssignmentCache.get(type)!;
+ }
+
+ // 从颜色池中分配新颜色
+ const colorConfig = COLOR_POOL[colorPoolIndex % COLOR_POOL.length];
+ colorAssignmentCache.set(type, colorConfig);
+ colorPoolIndex++;
+
+ return colorConfig;
+};
+
+/**
+ * 重置颜色分配缓存
+ * 用于重新加载图谱数据时清除之前的分配
+ */
+export const resetColorAssignment = (): void => {
+ colorAssignmentCache.clear();
+ colorPoolIndex = 0;
+};
+
+/**
+ * 从图谱数据中提取所有唯一的节点类型
+ * @param graphData 图谱数据
+ * @returns 唯一的节点类型数组
+ */
+export const extractNodeTypes = (graphData: GraphData): string[] => {
+ const typeSet = new Set();
+ graphData.nodes.forEach((node) => {
+ if (node.type) {
+ typeSet.add(node.type);
+ }
+ });
+ return Array.from(typeSet);
+};
+
+/**
+ * 生成图例数据
+ * @param graphData 图谱数据
+ * @returns 图例数据数组
+ */
+export const generateLegendData = (graphData: GraphData): NodeTypeInfo[] => {
+ const nodeTypes = extractNodeTypes(graphData);
+
+ return nodeTypes.map((type) => {
+ const colors = getOrAssignColor(type);
+ const labels = NODE_TYPE_LABELS[type] || { zh: type, en: type };
+
+ return {
+ type,
+ colors,
+ labels,
+ };
+ });
+};
+
+/**
+ * 获取节点颜色配置
+ * @param node 节点对象
+ * @returns 颜色配置对象
+ */
+export const getNodeColors = (node: Node): ColorConfig => {
if (!node.type) return DEFAULT_NODE_COLORS;
- return NODE_COLOR_MAP[node.type] || DEFAULT_NODE_COLORS;
+ return getOrAssignColor(node.type);
};
-// 获取节点双语标签
+/**
+ * 获取节点双语标签
+ * @param type 节点类型
+ * @returns 双语标签对象
+ */
export const getNodeLabels = (type: string): { zh: string; en: string } => {
return NODE_TYPE_LABELS[type] || { zh: type, en: type };
};
diff --git a/frontend/src/hooks/useGraphData.ts b/frontend/src/hooks/useGraphData.ts
index ff97b9d..b626d92 100644
--- a/frontend/src/hooks/useGraphData.ts
+++ b/frontend/src/hooks/useGraphData.ts
@@ -2,6 +2,7 @@ import { useState, useCallback, useEffect } from 'react';
import type { GraphData, Node, SearchNode } from '../types/graph';
import { graphApi } from '../services/graphApi';
import { SAMPLE_GRAPH_DATA } from '../config/sampleData';
+import { resetColorAssignment } from '../config/colors';
/**
* 知识图谱数据管理 Hook
@@ -20,6 +21,9 @@ export const useGraphData = () => {
setLoading(true);
const data = await graphApi.getData();
+ // 重置颜色分配缓存,确保每次加载数据时颜色分配一致
+ resetColorAssignment();
+
// 如果后端返回空数据,使用示例数据
if (data.nodes.length === 0) {
setGraphData(SAMPLE_GRAPH_DATA);
@@ -28,6 +32,8 @@ export const useGraphData = () => {
}
} catch (error) {
console.error('Failed to load graph data:', error);
+ // 重置颜色分配缓存
+ resetColorAssignment();
// API 失败时使用示例数据作为降级方案
setGraphData(SAMPLE_GRAPH_DATA);
} finally {
diff --git a/frontend/src/pages/KnowledgeGraph.tsx b/frontend/src/pages/KnowledgeGraph.tsx
index ff374dc..e4adebd 100644
--- a/frontend/src/pages/KnowledgeGraph.tsx
+++ b/frontend/src/pages/KnowledgeGraph.tsx
@@ -1,9 +1,11 @@
+import { useMemo } from 'react';
import GraphView from '../components/GraphView/index';
import SearchBar from '../components/SearchBar/index';
import NodePanel from '../components/NodePanel/index';
import Legend from '../components/Legend/index';
import { useGraphData } from '../hooks/useGraphData';
import { useGraphLayout } from '../hooks/useGraphLayout';
+import { generateLegendData } from '../config/colors';
/**
* 知识图谱主页面组件
@@ -27,6 +29,13 @@ export const KnowledgeGraph = () => {
const { layout } = useGraphLayout();
+ const legendData = useMemo(() => {
+ if (!graphData || graphData.nodes.length === 0) {
+ return [];
+ }
+ return generateLegendData(graphData);
+ }, [graphData]);
+
return (
{loading ? (
@@ -54,7 +63,7 @@ export const KnowledgeGraph = () => {
-
+ {legendData.length > 0 && }