You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
11 KiB
332 lines
11 KiB
<template>
|
|
<div>
|
|
<!-- <div id="graph-container">
|
|
<div v-if="isLoading">Loading data from Neo4j...</div>
|
|
<div v-if="errorMessage">{{ errorMessage }}</div>
|
|
</div> -->
|
|
<div ref="myPage" class="my-graph" style="height: calc(100vh - 184px);">
|
|
<RelationGraph
|
|
ref="graphRef"
|
|
:options="graphOptions"
|
|
:on-node-click="onNodeClick"
|
|
:on-canvas-click="onCanvasClick"
|
|
>
|
|
<template #node="{node}">
|
|
<div @mouseover="showNodeTips(node, $event)" @mouseout="hideNodeTips(node, $event)">
|
|
<div class="c-my-rg-node" :style="{'height': node.width + 'px', 'line-height': node.width + 'px'}">
|
|
{{ node.text }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #graph-plug>
|
|
<div v-if="isShowNodeTipsPanel" :style="{left: nodeMenuPanelPosition.x + 'px', top: nodeMenuPanelPosition.y + 'px' }" style="z-index: 999;padding:10px;background-color: #ffffff;border:#eeeeee solid 1px;box-shadow: 0px 0px 8px #cccccc;position: absolute;">
|
|
<div style="line-height: 25px;color: #888888;font-size: 12px;">节点类型:{{ currentNode.typeText }}</div>
|
|
<div style="line-height: 25px;color: #888888;font-size: 12px;">节点名称:{{ currentNode.text }}</div>
|
|
</div>
|
|
</template>
|
|
</RelationGraph>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
// import { getGraphData } from '../../../neo4j.js'
|
|
import RelationGraph from 'relation-graph'
|
|
import { FetchInitShowByCategory } from '@/api/ai/ai'
|
|
|
|
export default {
|
|
name: 'Demo',
|
|
components: { RelationGraph },
|
|
props: {
|
|
},
|
|
data() {
|
|
return {
|
|
graphData: {},
|
|
isShowCodePanel: false,
|
|
isShowNodeTipsPanel: false,
|
|
nodeMenuPanelPosition: { x: 0, y: 0 },
|
|
currentNode: {},
|
|
graphOptions: {
|
|
useAnimationWhenExpanded: true,
|
|
useAnimationWhenRefresh: true,
|
|
placeOtherGroup: true,
|
|
disableNodeclickEffect: true,
|
|
reLayoutWhenExpandedOrCollapsed: true,
|
|
defaultExpandHolderPosition: 'bottom',
|
|
zoomToFitWhenRefresh: true,
|
|
layout: {
|
|
layoutName: 'force',
|
|
'force_line_elastic': 0.5
|
|
},
|
|
allowSwitchLineShape: true,
|
|
allowSwitchJunctionPoint: true,
|
|
defaultLineColor: 'rgba(0,0,0,0.5)',
|
|
defaultNodeBorderWidth: 0,
|
|
defaultNodeBorderColor: 'transpanret',
|
|
defaultNodeFontColor: '#fff',
|
|
defaultFocusRootNode: false
|
|
},
|
|
isLoading: false,
|
|
errorMessage: '',
|
|
nodes: [],
|
|
edges: [],
|
|
allData: { 'nodes': [], 'lines': [] }
|
|
}
|
|
},
|
|
watch: {
|
|
// 'graphData': {
|
|
// handler(val) {
|
|
// setTimeout(() => {
|
|
// this.setData()
|
|
// }, 100)
|
|
// },
|
|
// immediate: true,
|
|
// deep: true
|
|
// }
|
|
},
|
|
mounted() {
|
|
this.resizeTimer = setInterval(async() => {
|
|
// const graphInstance = this.$refs.graphRef.getInstance();
|
|
// await graphInstance.zoomToFit();
|
|
}, 3000)
|
|
// this.allData.nodes = []
|
|
// this.allData.lines = []
|
|
// const query = "MATCH path = (target:Archives {id: '0047B3542CE88B895E41DE'})-[rels:HAS_KEYWORD*1..4]-(related:Archives) WHERE target <> related UNWIND relationships(path) AS r WITH startNode(r) AS n, r, endNode(r) AS m, length(path)/2 AS depth RETURN DISTINCT n, r, m;"
|
|
// this.fetchData(query)
|
|
// this.showGraph()
|
|
},
|
|
beforeDestroy() {
|
|
clearInterval(this.resizeTimer)
|
|
},
|
|
methods: {
|
|
resetAllData() {
|
|
this.allData = { 'nodes': [], 'lines': [] }
|
|
},
|
|
getOrigionGraphData(nodeId, id, tableName) {
|
|
const parmas = {
|
|
'searchKey': 'id',
|
|
'searchValue': id,
|
|
'tableName': tableName, // 节点名称
|
|
'querySize': 10
|
|
}
|
|
FetchInitShowByCategory(parmas).then((res) => {
|
|
this.graphData = res
|
|
this.setData(this.graphData, nodeId)
|
|
}).catch(err => {
|
|
console.log(err)
|
|
})
|
|
},
|
|
setData(data, selectedNodeId = null) {
|
|
const { nodes, lines } = data
|
|
// 档案Archives、档案门类Category、档案分类CategoryClass、全宗Fonds、关键词Keywords、保管期限Retention、保密期限SecrecyPeriod、密级SecurityClass
|
|
const typeToTextMap = {
|
|
Archives: '档案',
|
|
Category: '档案门类',
|
|
CategoryClass: '档案分类',
|
|
Fonds: '全宗',
|
|
Keywords: '关键词',
|
|
Retention: '保管期限',
|
|
SecrecyPeriod: '保密期限',
|
|
SecurityClass: '密级'
|
|
}
|
|
const newNodes = nodes.map(item => {
|
|
const newItem = {
|
|
...item,
|
|
id: String(item.id),
|
|
originId: item.properties.id,
|
|
text: item.properties.title,
|
|
type: item.labels[0]
|
|
}
|
|
const typeToColorMap = {
|
|
Archives: '#0348F3',
|
|
Category: '#14C9C9',
|
|
CategoryClass: '#F8B722',
|
|
Fonds: '#722ED1',
|
|
Keywords: '#F4647B',
|
|
Retention: '#018BFF',
|
|
SecrecyPeriod: '#FEBD98',
|
|
SecurityClass: '#B1EBDF'
|
|
}
|
|
|
|
const defaultColor = '#000000' // 默认颜色
|
|
if (typeToColorMap[item.labels[0]]) {
|
|
newItem.color = typeToColorMap[item.labels[0]]
|
|
newItem.borderColor = typeToColorMap[item.labels[0]]
|
|
} else {
|
|
newItem.color = defaultColor
|
|
newItem.borderColor = defaultColor
|
|
}
|
|
// 根据 type 给 newItem 加上 typeText
|
|
newItem.typeText = typeToTextMap[newItem.type]
|
|
|
|
return newItem
|
|
})
|
|
|
|
const newEdges = lines.map(item => {
|
|
return {
|
|
...item,
|
|
from: String(item.source),
|
|
to: String(item.target),
|
|
text: item.type
|
|
}
|
|
})
|
|
|
|
const existingNodeIds = new Set(this.allData.nodes.map(node => node.id))
|
|
const allNodesExist = newNodes.every(node => existingNodeIds.has(node.id))
|
|
|
|
let validNewEdges = []
|
|
let uniqueNewNodes = []
|
|
if (!allNodesExist) {
|
|
uniqueNewNodes = newNodes.filter(node => !existingNodeIds.has(node.id))
|
|
const existingLineKeys = new Set(this.allData.lines.map(line => `${line.from}-${line.to}-${line.text}`))
|
|
|
|
// 过滤掉 to 为选中节点及其关联节点的边
|
|
if (selectedNodeId) {
|
|
const relatedNodeIds = [selectedNodeId, ...this.getRelatedNodeIds(selectedNodeId)]
|
|
validNewEdges = newEdges.filter(edge => {
|
|
return !relatedNodeIds.includes(edge.to) && !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`)
|
|
})
|
|
} else {
|
|
validNewEdges = newEdges.filter(edge => !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`))
|
|
}
|
|
}
|
|
|
|
const originalNodesLength = this.allData.nodes.length
|
|
const originalLinesLength = this.allData.lines.length
|
|
|
|
this.allData.nodes = [...this.allData.nodes, ...uniqueNewNodes]
|
|
this.allData.lines = [...this.allData.lines, ...validNewEdges]
|
|
|
|
this.allData.nodes.sort((a, b) => {
|
|
return parseInt(a.id) - parseInt(b.id)
|
|
})
|
|
|
|
// 判断数据是否有变化
|
|
if (this.allData.nodes.length > originalNodesLength || this.allData.lines.length > originalLinesLength) {
|
|
this.showGraph()
|
|
} else {
|
|
console.log('数据无变化,不更新图表')
|
|
}
|
|
},
|
|
getRelatedNodeIds(nodeId) {
|
|
return this.allData.lines
|
|
.filter(line => line.from === nodeId || line.to === nodeId)
|
|
.map(line => line.from === nodeId ? line.to : line.from)
|
|
},
|
|
async showGraph() {
|
|
console.log('this.allData', this.allData)
|
|
// const rootId = this.allData.nodes[0].id
|
|
// this.allData.rootId = rootId
|
|
|
|
const graphInstance = this.$refs.graphRef.getInstance()
|
|
// await this.stopForceIfNeed()
|
|
await graphInstance.setJsonData(this.allData)
|
|
|
|
// setTimeout(async() => {
|
|
// await graphInstance.setZoom(100)
|
|
// await graphInstance.moveToCenter()
|
|
// await graphInstance.zoomToFit()
|
|
// }, 1800)
|
|
},
|
|
async stopForceIfNeed() {
|
|
const graphInstance = this.$refs.graphRef.getInstance()
|
|
await graphInstance.stopAutoLayout()
|
|
},
|
|
async updateLayouterOptions() {
|
|
await this.stopForceIfNeed()
|
|
const graphInstance = this.$refs.graphRef.getInstance()
|
|
graphInstance.layouter.maxLayoutTimes = this.graphOptions.layout.maxLayoutTimes
|
|
graphInstance.layouter.force_node_repulsion = this.graphOptions.layout.force_node_repulsion
|
|
graphInstance.layouter.force_line_elastic = this.graphOptions.layout.force_line_elastic
|
|
setTimeout(async() => {
|
|
await graphInstance.startAutoLayout()
|
|
}, 500)
|
|
},
|
|
onNodeClick(nodeObject, $event) {
|
|
console.log('this.allData', this.allData)
|
|
console.log('节点数据:', nodeObject)
|
|
const nodeId = nodeObject.id
|
|
const targetNode = this.allData.nodes.find(node => node.id === nodeId)
|
|
if (targetNode) {
|
|
const originId = targetNode.originId
|
|
console.log(originId)
|
|
// this.$emit('getGraphData', originId, targetNode.type)
|
|
this.getOrigionGraphData(nodeId, originId, targetNode.type)
|
|
} else {
|
|
console.log('未找到对应的节点')
|
|
}
|
|
|
|
// const query = `MATCH (n) WHERE id(n) = ${nodeId} MATCH (n)-[r]-(m) RETURN n, r,m;`
|
|
// this.fetchData(query, nodeId)
|
|
// console.log('query', query)
|
|
},
|
|
onCanvasClick($event) {
|
|
const _all_nodes = this.$refs.graphRef.getInstance().getNodes()
|
|
_all_nodes.forEach((thisNode) => {
|
|
const _isHideThisNode = true
|
|
thisNode.opacity = _isHideThisNode ? 1 : 0.1
|
|
})
|
|
},
|
|
showNodeTips(nodeObject, $event) {
|
|
this.currentNode = nodeObject
|
|
|
|
const nodeId = nodeObject.id
|
|
const targetNode = this.allData.nodes.find(node => node.id === nodeId)
|
|
if (!targetNode) {
|
|
console.error(`未找到 ID 为 ${nodeId} 的节点`)
|
|
return
|
|
}
|
|
|
|
this.currentNode.typeText = targetNode.typeText
|
|
|
|
const _base_position = this.$refs.graphRef.getInstance().options.fullscreen ? { x: 0, y: 0 } : this.$refs.myPage.getBoundingClientRect()
|
|
this.isShowNodeTipsPanel = true
|
|
this.nodeMenuPanelPosition.x = $event.clientX - _base_position.x + 10
|
|
this.nodeMenuPanelPosition.y = $event.clientY - _base_position.y + 10
|
|
},
|
|
hideNodeTips(nodeObject, $event) {
|
|
this.isShowNodeTipsPanel = false
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
::v-deep .rel-toolbar{
|
|
color: #000;
|
|
.c-current-zoom{
|
|
color: #000;
|
|
}
|
|
}
|
|
|
|
.c-my-rg-node {
|
|
font-size: 13px;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
height: 80px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.c-node-menu-item{
|
|
line-height: 30px;
|
|
padding-left: 10px;
|
|
cursor: pointer;
|
|
color: #444444;
|
|
font-size: 14px;
|
|
border-top:#efefef solid 1px;
|
|
}
|
|
|
|
.c-node-menu-item:hover{
|
|
background-color: rgba(66,187,66,0.2);
|
|
}
|
|
|
|
.c-big-style{
|
|
font-size: 30px;
|
|
}
|
|
|
|
::v-deep .c-rg-line-text{
|
|
font-size: 10px !important;
|
|
}
|
|
</style>
|