|
|
<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>
|