427 lines
13 KiB
JavaScript
427 lines
13 KiB
JavaScript
import { defineStore } from 'pinia'
|
||
import { DirectedGraph } from 'graphology'
|
||
|
||
export const useGraphStore = defineStore('graph', {
|
||
state: () => ({
|
||
// 选中和聚焦状态
|
||
selectedNode: null,
|
||
focusedNode: null,
|
||
selectedEdge: null,
|
||
focusedEdge: null,
|
||
|
||
// 图数据
|
||
rawGraph: null,
|
||
sigmaGraph: null,
|
||
sigmaInstance: null,
|
||
|
||
// 实体类型和颜色映射
|
||
entityTypes: [],
|
||
typeColorMap: new Map(),
|
||
|
||
// 加载状态
|
||
isFetching: false,
|
||
graphIsEmpty: false,
|
||
|
||
// 移动到选中节点的标志
|
||
moveToSelectedNode: false,
|
||
|
||
// 图统计信息
|
||
stats: {
|
||
total_nodes: 0,
|
||
total_edges: 0,
|
||
displayed_nodes: 0,
|
||
displayed_edges: 0
|
||
}
|
||
}),
|
||
|
||
getters: {
|
||
// 获取选中节点的详细信息
|
||
selectedNodeData: (state) => {
|
||
if (!state.selectedNode || !state.rawGraph) return null
|
||
return state.rawGraph.nodes.find(node => node.id === state.selectedNode)
|
||
},
|
||
|
||
// 获取选中边的详细信息
|
||
selectedEdgeData: (state) => {
|
||
if (!state.selectedEdge || !state.rawGraph) return null
|
||
|
||
console.log('查找边数据,选中边ID:', state.selectedEdge)
|
||
|
||
// 首先尝试通过dynamicId匹配(Sigma使用的ID格式)
|
||
let foundEdge = state.rawGraph.edges.find(edge => edge.dynamicId === state.selectedEdge)
|
||
if (foundEdge) {
|
||
console.log('通过dynamicId找到边:', foundEdge)
|
||
return foundEdge
|
||
}
|
||
|
||
// 如果没找到,尝试通过原始ID匹配
|
||
foundEdge = state.rawGraph.edges.find(edge => edge.id === state.selectedEdge)
|
||
if (foundEdge) {
|
||
console.log('通过id找到边:', foundEdge)
|
||
return foundEdge
|
||
}
|
||
|
||
// 对于格式为 source-target-index 的dynamicId,也尝试解析
|
||
const dynamicIdPattern = /^(.+)-(.+)-(\d+)$/
|
||
const match = state.selectedEdge.match(dynamicIdPattern)
|
||
if (match) {
|
||
const [, source, target, index] = match
|
||
foundEdge = state.rawGraph.edges.find(edge =>
|
||
edge.source === source && edge.target === target
|
||
)
|
||
if (foundEdge) {
|
||
console.log('通过解析dynamicId找到边:', foundEdge)
|
||
return foundEdge
|
||
}
|
||
}
|
||
|
||
// 最后尝试通过source->target格式匹配
|
||
const arrowPattern = /^(.+)->(.+)$/
|
||
const arrowMatch = state.selectedEdge.match(arrowPattern)
|
||
if (arrowMatch) {
|
||
const [, source, target] = arrowMatch
|
||
foundEdge = state.rawGraph.edges.find(edge =>
|
||
edge.source === source.trim() && edge.target === target.trim()
|
||
)
|
||
if (foundEdge) {
|
||
console.log('通过箭头格式找到边:', foundEdge)
|
||
return foundEdge
|
||
}
|
||
}
|
||
|
||
console.warn('未找到匹配的边数据,选中边ID:', state.selectedEdge)
|
||
return null
|
||
},
|
||
|
||
// 检查图是否为空
|
||
isGraphEmpty: (state) => {
|
||
return !state.rawGraph || state.rawGraph.nodes.length === 0
|
||
}
|
||
},
|
||
|
||
actions: {
|
||
// 设置Sigma实例
|
||
setSigmaInstance(instance) {
|
||
this.sigmaInstance = instance
|
||
},
|
||
|
||
// 节点选择和聚焦
|
||
setSelectedNode(nodeId, moveToNode = false) {
|
||
this.selectedNode = nodeId
|
||
this.moveToSelectedNode = moveToNode
|
||
// 如果选中节点,清除选中的边
|
||
if (nodeId) {
|
||
this.selectedEdge = null
|
||
}
|
||
},
|
||
|
||
setFocusedNode(nodeId) {
|
||
this.focusedNode = nodeId
|
||
},
|
||
|
||
setSelectedEdge(edgeId) {
|
||
this.selectedEdge = edgeId
|
||
// 如果选中边,清除选中的节点
|
||
if (edgeId) {
|
||
this.selectedNode = null
|
||
}
|
||
},
|
||
|
||
setFocusedEdge(edgeId) {
|
||
this.focusedEdge = edgeId
|
||
},
|
||
|
||
// 清除所有选择
|
||
clearSelection() {
|
||
this.selectedNode = null
|
||
this.focusedNode = null
|
||
this.selectedEdge = null
|
||
this.focusedEdge = null
|
||
},
|
||
|
||
// 设置加载状态
|
||
setIsFetching(isFetching) {
|
||
this.isFetching = isFetching
|
||
},
|
||
|
||
// 设置实体类型
|
||
setEntityTypes(types) {
|
||
this.entityTypes = types
|
||
this.updateTypeColorMap()
|
||
},
|
||
|
||
// 更新类型颜色映射
|
||
updateTypeColorMap() {
|
||
const colorPalette = [
|
||
'#FF6B6B', // 人物 - 红色
|
||
'#4ECDC4', // 组织 - 青色
|
||
'#45B7D1', // 地点 - 蓝色
|
||
'#96CEB4', // 事件 - 绿色
|
||
'#FFEAA7', // 分类 - 黄色
|
||
'#DDA0DD', // 设备 - 紫色
|
||
'#FF7675', // 运动员 - 红色
|
||
'#FD79A8', // 记录 - 粉色
|
||
'#FDCB6E', // 年份 - 橙色
|
||
'#B2BEC3' // 未知 - 灰色
|
||
]
|
||
|
||
const typeColorMap = new Map()
|
||
this.entityTypes.forEach((type, index) => {
|
||
const colorIndex = index % colorPalette.length
|
||
typeColorMap.set(type.type, colorPalette[colorIndex])
|
||
})
|
||
|
||
// 为特殊类型设置固定颜色
|
||
typeColorMap.set('person', '#FF6B6B')
|
||
typeColorMap.set('organization', '#4ECDC4')
|
||
typeColorMap.set('location', '#45B7D1')
|
||
typeColorMap.set('geo', '#45B7D1')
|
||
typeColorMap.set('event', '#96CEB4')
|
||
typeColorMap.set('category', '#FFEAA7')
|
||
typeColorMap.set('unknown', '#B2BEC3')
|
||
|
||
this.typeColorMap = typeColorMap
|
||
},
|
||
|
||
// 获取实体类型颜色
|
||
getEntityColor(entityType) {
|
||
return this.typeColorMap.get(entityType) || '#B2BEC3'
|
||
},
|
||
|
||
// 设置原始图数据
|
||
setRawGraph(rawGraph) {
|
||
this.rawGraph = rawGraph
|
||
this.graphIsEmpty = !rawGraph || rawGraph.nodes.length === 0
|
||
this.updateStats()
|
||
},
|
||
|
||
// 设置Sigma图数据
|
||
setSigmaGraph(sigmaGraph) {
|
||
this.sigmaGraph = sigmaGraph
|
||
},
|
||
|
||
// 更新统计信息
|
||
updateStats() {
|
||
if (this.rawGraph) {
|
||
this.stats = {
|
||
total_nodes: this.rawGraph.nodes.length,
|
||
total_edges: this.rawGraph.edges.length,
|
||
displayed_nodes: this.rawGraph.nodes.length,
|
||
displayed_edges: this.rawGraph.edges.length
|
||
}
|
||
}
|
||
},
|
||
|
||
// 从API数据创建图数据结构
|
||
createGraphFromApiData(nodesData, edgesData) {
|
||
const rawGraph = {
|
||
nodes: [],
|
||
edges: [],
|
||
nodeIdMap: {},
|
||
edgeIdMap: {},
|
||
edgeDynamicIdMap: {}
|
||
}
|
||
|
||
console.log('Processing nodes data:', nodesData)
|
||
|
||
// 处理节点数据
|
||
nodesData.forEach((node, index) => {
|
||
// 适配新的LightRAG API格式
|
||
const nodeId = String(node.id)
|
||
const labels = node.labels || [node.entity_type || 'unknown']
|
||
const entityType = node.entity_type || labels[0] || 'unknown'
|
||
|
||
const processedNode = {
|
||
id: nodeId,
|
||
labels: Array.isArray(labels) ? labels.map(String) : [String(labels)],
|
||
entity_type: String(entityType),
|
||
properties: {
|
||
entity_id: String(node.properties?.entity_id || node.entity_id || nodeId),
|
||
entity_type: String(entityType),
|
||
description: String(node.properties?.description || node.description || ''),
|
||
file_path: String(node.properties?.file_path || node.file_path || ''),
|
||
...(node.properties || {})
|
||
},
|
||
// Sigma.js需要的属性
|
||
size: this.calculateNodeSize(node),
|
||
x: Math.random() * 1000, // 随机初始位置
|
||
y: Math.random() * 1000,
|
||
color: this.getEntityColor(String(entityType)),
|
||
degree: 0 // 将在处理边时计算
|
||
}
|
||
|
||
rawGraph.nodes.push(processedNode)
|
||
rawGraph.nodeIdMap[nodeId] = index
|
||
})
|
||
|
||
// 计算节点度数
|
||
const nodeDegrees = {}
|
||
edgesData.forEach(edge => {
|
||
const sourceId = String(edge.source)
|
||
const targetId = String(edge.target)
|
||
nodeDegrees[sourceId] = (nodeDegrees[sourceId] || 0) + 1
|
||
nodeDegrees[targetId] = (nodeDegrees[targetId] || 0) + 1
|
||
})
|
||
|
||
// 更新节点度数和大小
|
||
rawGraph.nodes.forEach(node => {
|
||
node.degree = nodeDegrees[node.id] || 0
|
||
node.size = this.calculateNodeSize({ degree: node.degree })
|
||
})
|
||
|
||
console.log('Processing edges data:', edgesData)
|
||
|
||
// 处理边数据
|
||
edgesData.forEach((edge, index) => {
|
||
const sourceId = String(edge.source)
|
||
const targetId = String(edge.target)
|
||
const dynamicId = `${sourceId}-${targetId}-${index}`
|
||
|
||
// 适配新的LightRAG API格式
|
||
const weight = Number(edge.properties?.weight || edge.weight || 1.0)
|
||
|
||
const processedEdge = {
|
||
id: String(edge.id),
|
||
source: sourceId,
|
||
target: targetId,
|
||
type: edge.type || 'DIRECTED',
|
||
properties: {
|
||
weight: weight,
|
||
keywords: String(edge.properties?.keywords || edge.keywords || ''),
|
||
description: String(edge.properties?.description || edge.description || ''),
|
||
file_path: String(edge.properties?.file_path || edge.file_path || ''),
|
||
...(edge.properties || {})
|
||
},
|
||
dynamicId: dynamicId,
|
||
// Sigma.js需要的属性
|
||
size: this.calculateEdgeSize(weight),
|
||
color: '#666',
|
||
originalWeight: weight
|
||
}
|
||
|
||
rawGraph.edges.push(processedEdge)
|
||
rawGraph.edgeIdMap[edge.id] = index
|
||
rawGraph.edgeDynamicIdMap[dynamicId] = index
|
||
})
|
||
|
||
return rawGraph
|
||
},
|
||
|
||
// 计算节点大小
|
||
calculateNodeSize(node) {
|
||
const baseSizeM = 15
|
||
const degree = node.degree || 0
|
||
return Math.min(baseSizeM + degree * 3, 40)
|
||
},
|
||
|
||
// 计算边大小
|
||
calculateEdgeSize(weight) {
|
||
const minSize = 3 // 与Sigma配置中的minEdgeThickness保持一致
|
||
const maxSize = 8 // 与Sigma配置中的maxEdgeThickness保持一致
|
||
const normalizedWeight = Math.max(0, Math.min(1, (weight - 1) / 9)) // 假设权重范围1-10
|
||
return minSize + normalizedWeight * (maxSize - minSize)
|
||
},
|
||
|
||
// 创建Sigma图实例
|
||
createSigmaGraph(rawGraph) {
|
||
console.log('开始创建Sigma图,节点数量:', rawGraph.nodes.length, '边数量:', rawGraph.edges.length)
|
||
const sigmaGraph = new DirectedGraph()
|
||
|
||
// 添加节点
|
||
rawGraph.nodes.forEach(node => {
|
||
// 确保所有属性都是正确的类型
|
||
const nodeAttributes = {
|
||
label: String(node.properties?.entity_id || node.id),
|
||
size: Number(node.size) || 15,
|
||
color: String(node.color) || '#B2BEC3',
|
||
x: Number(node.x) || Math.random() * 1000,
|
||
y: Number(node.y) || Math.random() * 1000,
|
||
// 保存原始数据引用
|
||
originalData: node
|
||
}
|
||
|
||
sigmaGraph.addNode(String(node.id), nodeAttributes)
|
||
})
|
||
|
||
console.log('节点添加完成,开始添加边...')
|
||
|
||
// 添加边
|
||
let edgeAddedCount = 0
|
||
let edgeSkippedCount = 0
|
||
rawGraph.edges.forEach((edge, index) => {
|
||
// 添加调试信息
|
||
if (index < 3) {
|
||
console.log('处理边 #' + index + ':', {
|
||
id: edge.id,
|
||
source: edge.source,
|
||
target: edge.target,
|
||
dynamicId: edge.dynamicId,
|
||
edgeObject: edge
|
||
})
|
||
}
|
||
|
||
if (sigmaGraph.hasNode(String(edge.source)) && sigmaGraph.hasNode(String(edge.target))) {
|
||
// 确保所有属性都是正确的类型
|
||
const edgeAttributes = {
|
||
size: Number(edge.size) || 1,
|
||
color: String(edge.color) || '#666',
|
||
label: String(edge.properties?.keywords || edge.properties?.description || ''),
|
||
originalWeight: Number(edge.originalWeight) || 1,
|
||
// 保存原始数据引用
|
||
originalData: edge
|
||
}
|
||
|
||
// 使用标准的 addEdge 方法:addEdge(edgeId, source, target, attributes)
|
||
try {
|
||
// 使用动态ID作为Sigma边ID,避免重复
|
||
const sigmaEdgeId = edge.dynamicId || `${edge.source}->${edge.target}`
|
||
|
||
// 检查是否已存在相同的边
|
||
if (!sigmaGraph.hasEdge(sigmaEdgeId)) {
|
||
sigmaGraph.addEdgeWithKey(sigmaEdgeId, String(edge.source), String(edge.target), edgeAttributes)
|
||
edgeAddedCount++
|
||
} else {
|
||
edgeSkippedCount++
|
||
}
|
||
} catch (err) {
|
||
console.warn('添加边失败:', {
|
||
source: edge.source,
|
||
target: edge.target,
|
||
attributes: edgeAttributes,
|
||
error: err.message
|
||
})
|
||
}
|
||
} else {
|
||
console.warn('节点不存在,跳过边:', {
|
||
source: edge.source,
|
||
target: edge.target,
|
||
hasSource: sigmaGraph.hasNode(String(edge.source)),
|
||
hasTarget: sigmaGraph.hasNode(String(edge.target))
|
||
})
|
||
}
|
||
})
|
||
|
||
console.log(`边添加完成: 成功 ${edgeAddedCount}, 跳过 ${edgeSkippedCount}`)
|
||
|
||
return sigmaGraph
|
||
},
|
||
|
||
// 重置所有状态
|
||
reset() {
|
||
this.selectedNode = null
|
||
this.focusedNode = null
|
||
this.selectedEdge = null
|
||
this.focusedEdge = null
|
||
this.rawGraph = null
|
||
this.sigmaGraph = null
|
||
this.moveToSelectedNode = false
|
||
this.graphIsEmpty = false
|
||
this.stats = {
|
||
total_nodes: 0,
|
||
total_edges: 0,
|
||
displayed_nodes: 0,
|
||
displayed_edges: 0
|
||
}
|
||
}
|
||
}
|
||
}) |