feat: 前端增加图例
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { NODE_COLOR_MAP, NODE_TYPE_LABELS } from '../../config/colors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图例组件
|
||||||
|
* 显示知识图谱中不同节点类型及其对应的颜色
|
||||||
|
* 支持折叠/展开展示
|
||||||
|
*/
|
||||||
|
export const Legend = () => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
|
||||||
|
const nodeTypes = Object.entries(NODE_COLOR_MAP);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="absolute bottom-4 left-4 bg-white/95 backdrop-blur-sm rounded-lg shadow-lg p-4 transition-all duration-300 hover:shadow-xl z-10">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700">图例</h3>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="text-gray-400 hover:text-gray-600 transition-colors focus:outline-none"
|
||||||
|
aria-label={isExpanded ? '折叠图例' : '展开图例'}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className={`w-4 h-4 transition-transform duration-300 ${isExpanded ? 'rotate-180' : ''}`}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isExpanded && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{nodeTypes.map(([type, colors], index) => {
|
||||||
|
const labels = NODE_TYPE_LABELS[type] || { zh: type, en: type };
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={type}
|
||||||
|
className="flex items-center space-x-3 p-2 rounded hover:bg-gray-50 transition-colors cursor-pointer"
|
||||||
|
style={{ animationDelay: `${index * 50}ms` }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-4 h-4 rounded-full flex-shrink-0 shadow-sm"
|
||||||
|
style={{
|
||||||
|
backgroundColor: colors.fill,
|
||||||
|
border: `1px solid ${colors.stroke}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col min-w-0">
|
||||||
|
<span className="text-xs font-medium text-gray-800 truncate">
|
||||||
|
{labels.zh}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] text-gray-500 truncate">
|
||||||
|
{labels.en}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Legend;
|
||||||
@@ -11,8 +11,20 @@ export const NODE_COLOR_MAP: Record<string, { fill: string; stroke: string }> =
|
|||||||
// 默认节点颜色(当类型未匹配时)
|
// 默认节点颜色(当类型未匹配时)
|
||||||
export const DEFAULT_NODE_COLORS = { fill: '#C6E5FF', stroke: '#5B8FF9' };
|
export const DEFAULT_NODE_COLORS = { fill: '#C6E5FF', stroke: '#5B8FF9' };
|
||||||
|
|
||||||
|
// 节点类型双语标签映射
|
||||||
|
export const NODE_TYPE_LABELS: Record<string, { zh: string; en: string }> = {
|
||||||
|
'概念': { zh: '概念', en: 'Concept' },
|
||||||
|
'工具': { zh: '工具', en: 'Tool' },
|
||||||
|
'应用': { zh: '应用', en: 'Application' },
|
||||||
|
};
|
||||||
|
|
||||||
// 获取节点颜色配置
|
// 获取节点颜色配置
|
||||||
export const getNodeColors = (node: Node): { fill: string; stroke: string } => {
|
export const getNodeColors = (node: Node): { fill: string; stroke: string } => {
|
||||||
if (!node.type) return DEFAULT_NODE_COLORS;
|
if (!node.type) return DEFAULT_NODE_COLORS;
|
||||||
return NODE_COLOR_MAP[node.type] || DEFAULT_NODE_COLORS;
|
return NODE_COLOR_MAP[node.type] || DEFAULT_NODE_COLORS;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取节点双语标签
|
||||||
|
export const getNodeLabels = (type: string): { zh: string; en: string } => {
|
||||||
|
return NODE_TYPE_LABELS[type] || { zh: type, en: type };
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import GraphView from '../components/GraphView/index';
|
import GraphView from '../components/GraphView/index';
|
||||||
import SearchBar from '../components/SearchBar/index';
|
import SearchBar from '../components/SearchBar/index';
|
||||||
import NodePanel from '../components/NodePanel/index';
|
import NodePanel from '../components/NodePanel/index';
|
||||||
|
import Legend from '../components/Legend/index';
|
||||||
import { useGraphData } from '../hooks/useGraphData';
|
import { useGraphData } from '../hooks/useGraphData';
|
||||||
import { useGraphLayout } from '../hooks/useGraphLayout';
|
import { useGraphLayout } from '../hooks/useGraphLayout';
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ export const KnowledgeGraph = () => {
|
|||||||
|
|
||||||
<div className="flex-1 flex overflow-hidden">
|
<div className="flex-1 flex overflow-hidden">
|
||||||
<main className="flex-1 bg-white relative">
|
<main className="flex-1 bg-white relative">
|
||||||
|
<Legend />
|
||||||
<GraphView
|
<GraphView
|
||||||
data={graphData}
|
data={graphData}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
@@ -61,7 +63,7 @@ export const KnowledgeGraph = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{hoveredNode && (
|
{hoveredNode && (
|
||||||
<div className="absolute bottom-4 left-4 bg-white/95 backdrop-blur-sm rounded-lg shadow-lg p-3 max-w-xs">
|
<div className="absolute bottom-4 right-1/2 translate-x-1/2 bg-white/95 backdrop-blur-sm rounded-lg shadow-lg p-3 max-w-sm z-20">
|
||||||
<div className="font-medium text-gray-800">{hoveredNode.label}</div>
|
<div className="font-medium text-gray-800">{hoveredNode.label}</div>
|
||||||
{hoveredNode.type && (
|
{hoveredNode.type && (
|
||||||
<div className="text-sm text-gray-500">{hoveredNode.type}</div>
|
<div className="text-sm text-gray-500">{hoveredNode.type}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user