|
@@ -2,185 +2,179 @@
|
|
|
<el-dialog v-model="dialogFormVisible" title="图谱节点详情" width="1100" height="600">
|
|
|
<div id="graph" class="graph">
|
|
|
|
|
|
- <svg id="mindSvg" ref="svgContainer" viewBox="0 0 900 600" width="900" height="600" >
|
|
|
- <!-- 定义箭头 -->
|
|
|
- <defs>
|
|
|
- <marker id="arrow"
|
|
|
- viewBox="0 -5 10 10"
|
|
|
- refX="8"
|
|
|
- refY="0"
|
|
|
- markerWidth="6"
|
|
|
- markerHeight="6"
|
|
|
- orient="auto">
|
|
|
- <path d="M 0,-5 L 10,0 L 0,5" fill="rgb(65, 143, 146)"/>
|
|
|
- </marker>
|
|
|
- <pattern id="crossHatch" patternUnits="userSpaceOnUse" width="10" height="10" patternTransform="rotate(45)">
|
|
|
- <line x1="0" y1="0" x2="0" y2="10" stroke="lightgrey" stroke-width="1"/>
|
|
|
- <line x1="0" y1="0" x2="10" y2="0" stroke="lightgrey" stroke-width="1"/>
|
|
|
- </pattern>
|
|
|
- </defs>
|
|
|
-
|
|
|
- </svg>
|
|
|
+ <svg id="mindSvg" ref="svgContainer" viewBox="0 0 900 600" width="900" height="600">
|
|
|
+ <!-- 定义箭头 -->
|
|
|
+ <defs>
|
|
|
+ <marker id="arrow" viewBox="0 -5 10 10" refX="8" refY="0" markerWidth="6" markerHeight="6" orient="auto">
|
|
|
+ <path d="M 0,-5 L 10,0 L 0,5" fill="rgb(65, 143, 146)" />
|
|
|
+ </marker>
|
|
|
+ <pattern id="crossHatch" patternUnits="userSpaceOnUse" width="10" height="10" patternTransform="rotate(45)">
|
|
|
+ <line x1="0" y1="0" x2="0" y2="10" stroke="lightgrey" stroke-width="1" />
|
|
|
+ <line x1="0" y1="0" x2="10" y2="0" stroke="lightgrey" stroke-width="1" />
|
|
|
+ </pattern>
|
|
|
+ </defs>
|
|
|
+
|
|
|
+ </svg>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
-import { ref, onMounted, defineEmits ,nextTick} from 'vue'
|
|
|
+import { ref, onMounted, defineEmits, nextTick } from 'vue'
|
|
|
import { getNodeNeighbors } from '@/api/GraphApi'
|
|
|
-import svgPanZoom from 'svg-pan-zoom';
|
|
|
+import svgPanZoom from 'svg-pan-zoom';
|
|
|
import { ElMessageBox } from 'element-plus';
|
|
|
const dialogFormVisible = ref(false)
|
|
|
-const nodesData = ref([{ id: 1, label: 'Node 1', direction:"in"}])
|
|
|
-const edgesData = ref([{ from: 1, to: 2 ,fromPos: {x:100,y:100}, toPos: {x:100,y:100}}])
|
|
|
+const nodesData = ref([{ id: 1, label: 'Node 1', direction: "in" }])
|
|
|
+const edgesData = ref([{ from: 1, to: 2, fromPos: { x: 100, y: 100 }, toPos: { x: 100, y: 100 } }])
|
|
|
const svgContainer = ref()
|
|
|
const props = defineProps({
|
|
|
- title: { type: String, required: true, default: '图谱节点详情'},
|
|
|
- nodeId: { type: Number, required: true, default: 0},
|
|
|
- graphId: { type: Number, required: true, default: 0}
|
|
|
-
|
|
|
+ title: { type: String, required: true, default: '图谱节点详情' },
|
|
|
+ nodeId: { type: Number, required: true, default: 0 },
|
|
|
+ graphId: { type: Number, required: true, default: 0 }
|
|
|
+
|
|
|
})
|
|
|
-const loadNodeNeighbors = (node_id:number) => {
|
|
|
+const loadNodeNeighbors = (node_id: number) => {
|
|
|
nodesData.value.length = 0
|
|
|
edgesData.value.length = 0
|
|
|
- getNodeNeighbors(node_id, props.graphId).then((res:any) => {
|
|
|
- for (let i = 0; i < res.records.length; i++) {
|
|
|
- const element = res.records[i];
|
|
|
- console.log(element)
|
|
|
- nodesData.value.push({id:element.id, label:element.name, direction:element.direction})
|
|
|
-
|
|
|
- }
|
|
|
- drawGraph(node_id)
|
|
|
- })
|
|
|
+ getNodeNeighbors(node_id, props.graphId).then((res: any) => {
|
|
|
+ for (let i = 0; i < res.records.length; i++) {
|
|
|
+ const element = res.records[i];
|
|
|
+ console.log(element)
|
|
|
+ nodesData.value.push({ id: element.id, label: element.name, direction: element.direction })
|
|
|
+
|
|
|
+ }
|
|
|
+ drawGraph(node_id)
|
|
|
+ })
|
|
|
}
|
|
|
-function handleNodeClick(nodeId){
|
|
|
- loadNodeNeighbors(Number(nodeId))
|
|
|
+function handleNodeClick(nodeId: any) {
|
|
|
+ loadNodeNeighbors(Number(nodeId))
|
|
|
// var result = ElMessageBox.confirm('是否加载该节点的邻居节点?', '提示', )
|
|
|
// result.then(() => {
|
|
|
-
|
|
|
+
|
|
|
// })
|
|
|
}
|
|
|
-const drawGraph = (node_id:number) => {
|
|
|
- const svg = svgContainer.value //document.getElementById('mindSvg') as SVGElement | null;
|
|
|
- if (svg == null) {
|
|
|
- console.log("svg is null")
|
|
|
- console.log(svgContainer.value)
|
|
|
- return;
|
|
|
+const drawGraph = (node_id: number) => {
|
|
|
+ const svg = svgContainer.value //document.getElementById('mindSvg') as SVGElement | null;
|
|
|
+ if (svg == null) {
|
|
|
+ console.log("svg is null")
|
|
|
+ console.log(svgContainer.value)
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ svg.innerHTML = '';
|
|
|
+ console.log("drawGraph", svg)
|
|
|
+ const svgWidth = svg.width.baseVal.value
|
|
|
+ const svgHeight = svg.height.baseVal.value
|
|
|
+ console.log("svgWidth", svgWidth, "svgHeight", svgHeight)
|
|
|
+ // 计算左边,中间,和右边的节点数量
|
|
|
+ var totalNodes = nodesData.value.length
|
|
|
+ var leftNodesCount = 0
|
|
|
+ var rightNodesCount = 0
|
|
|
+ nodesData.value.forEach(node => {
|
|
|
+ if (node.direction == 'in') {
|
|
|
+ leftNodesCount += 1
|
|
|
+ } else if (node.direction == 'out') {
|
|
|
+ rightNodesCount += 1
|
|
|
}
|
|
|
- svg.innerHTML = '';
|
|
|
- console.log("drawGraph", svg)
|
|
|
- const svgWidth = svg.width.baseVal.value
|
|
|
- const svgHeight = svg.height.baseVal.value
|
|
|
- console.log("svgWidth", svgWidth, "svgHeight", svgHeight)
|
|
|
- // 计算左边,中间,和右边的节点数量
|
|
|
- var totalNodes = nodesData.value.length
|
|
|
- var leftNodesCount = 0
|
|
|
- var rightNodesCount = 0
|
|
|
- nodesData.value.forEach(node => {
|
|
|
- if (node.direction=='in') {
|
|
|
- leftNodesCount += 1
|
|
|
- } else if (node.direction=='out') {
|
|
|
- rightNodesCount += 1
|
|
|
- }
|
|
|
- })
|
|
|
- var rectHeight = 40
|
|
|
- var rectWidth = 120
|
|
|
- var rectMargin = 20
|
|
|
- var totalLeftHeight = leftNodesCount * (rectHeight + rectMargin) - rectMargin
|
|
|
- var totalRightHeight = rightNodesCount * (rectHeight + rectMargin) - rectMargin
|
|
|
- var leftTop = [svgWidth / 4, (svgHeight - totalLeftHeight) / 2] // 左边顶部节点的位置
|
|
|
- var rightTop = [svgWidth * 3 / 4, (svgHeight - totalRightHeight) / 2] // 左边顶部节点的位置
|
|
|
- const centerPos = {x:svgWidth / 2 , y:svgHeight / 2 }
|
|
|
- var gLinesGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
|
- var gRectsGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
|
- svg.appendChild(gLinesGroup)
|
|
|
- svg.appendChild(gRectsGroup)
|
|
|
- // 绘制节点
|
|
|
- var left_index = 1
|
|
|
- var right_index = 1
|
|
|
- nodesData.value.forEach(node => {
|
|
|
- console.log("draw node:",node)
|
|
|
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
|
- if (node.direction=="in") {
|
|
|
- var pos = {x:leftTop[0]- rectWidth/2 , y:leftTop[1] + (left_index - 1) * (rectHeight + rectMargin) }
|
|
|
- g.setAttribute('transform', `translate(${pos.x}, ${pos.y})`);
|
|
|
- left_index += 1
|
|
|
- pos.x = pos.x + rectWidth/2
|
|
|
- pos.y = pos.y + rectHeight/2
|
|
|
- edgesData.value.push({from:node.id, to:node_id, fromPos: pos, toPos: centerPos})
|
|
|
- } else if (node.direction=="out") {
|
|
|
- var pos ={x:rightTop[0]- rectWidth/2 ,y:rightTop[1] + (right_index - 1) * (rectHeight + rectMargin)}
|
|
|
- g.setAttribute('transform', `translate(${pos.x}, ${pos.y})`);
|
|
|
- right_index += 1
|
|
|
- pos.x = pos.x + rectWidth/2
|
|
|
- pos.y = pos.y + rectHeight/2
|
|
|
- edgesData.value.push({from:node_id, to:node.id, fromPos: centerPos, toPos: pos})
|
|
|
- } else if (node.direction=="self") {
|
|
|
- g.setAttribute('transform', `translate(${svgWidth / 2 - rectWidth/2}, ${svgHeight / 2 - rectHeight/2})`);
|
|
|
- }
|
|
|
- const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
|
- rect.setAttribute('width', '120');
|
|
|
- rect.setAttribute('height', '40');
|
|
|
- rect.setAttribute('data-node-id', node.id.toString());
|
|
|
- if (node.direction=="self") {
|
|
|
- rect.setAttribute('fill', '#418f92');
|
|
|
- } else {
|
|
|
-
|
|
|
- rect.setAttribute('fill', '#f0f0f0');
|
|
|
- }
|
|
|
- //rect.setAttribute('stroke', '#418f92');
|
|
|
- rect.setAttribute('rx', '5');
|
|
|
- rect.setAttribute('class', 'node_rect');
|
|
|
-
|
|
|
- const link = document.createElementNS('http://www.w3.org/2000/svg', 'a')
|
|
|
-
|
|
|
- link.setAttribute('data-node-id', node.id.toString())
|
|
|
- link.setAttribute('class', 'node_text')
|
|
|
- const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
|
- text.setAttribute('x', '60');
|
|
|
- text.setAttribute('y', '25');
|
|
|
- text.setAttribute('text-anchor', 'middle');
|
|
|
-
|
|
|
- text.textContent = node.label;
|
|
|
- link.appendChild(text)
|
|
|
-
|
|
|
- g.appendChild(rect);
|
|
|
- g.appendChild(link);
|
|
|
- gRectsGroup.appendChild(g);
|
|
|
-
|
|
|
- console.log("draw node finished")
|
|
|
-
|
|
|
- });
|
|
|
- // 绘制边
|
|
|
- edgesData.value.forEach(edge => {
|
|
|
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
|
- line.setAttribute('x1', `${edge.fromPos.x}`);
|
|
|
- line.setAttribute('y1', `${edge.fromPos.y}`);
|
|
|
- line.setAttribute('x2', `${edge.toPos.x}`);
|
|
|
- line.setAttribute('y2', `${edge.toPos.y}`);
|
|
|
- line.setAttribute('stroke', '#418f92');
|
|
|
- line.setAttribute('stroke-width', '2');
|
|
|
-
|
|
|
- line.setAttribute('style', 'z-index:0');
|
|
|
- gLinesGroup.appendChild(line);
|
|
|
+ })
|
|
|
+ var rectHeight = 40
|
|
|
+ var rectWidth = 120
|
|
|
+ var rectMargin = 20
|
|
|
+ var totalLeftHeight = leftNodesCount * (rectHeight + rectMargin) - rectMargin
|
|
|
+ var totalRightHeight = rightNodesCount * (rectHeight + rectMargin) - rectMargin
|
|
|
+ var leftTop = [svgWidth / 4, (svgHeight - totalLeftHeight) / 2] // 左边顶部节点的位置
|
|
|
+ var rightTop = [svgWidth * 3 / 4, (svgHeight - totalRightHeight) / 2] // 左边顶部节点的位置
|
|
|
+ const centerPos = { x: svgWidth / 2, y: svgHeight / 2 }
|
|
|
+ var gLinesGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
|
+ var gRectsGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
|
+ svg.appendChild(gLinesGroup)
|
|
|
+ svg.appendChild(gRectsGroup)
|
|
|
+ // 绘制节点
|
|
|
+ var left_index = 1
|
|
|
+ var right_index = 1
|
|
|
+ nodesData.value.forEach(node => {
|
|
|
+ console.log("draw node:", node)
|
|
|
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
|
+ if (node.direction == "in") {
|
|
|
+ var pos = { x: leftTop[0] - rectWidth / 2, y: leftTop[1] + (left_index - 1) * (rectHeight + rectMargin) }
|
|
|
+ g.setAttribute('transform', `translate(${pos.x}, ${pos.y})`);
|
|
|
+ left_index += 1
|
|
|
+ pos.x = pos.x + rectWidth / 2
|
|
|
+ pos.y = pos.y + rectHeight / 2
|
|
|
+ edgesData.value.push({ from: node.id, to: node_id, fromPos: pos, toPos: centerPos })
|
|
|
+ } else if (node.direction == "out") {
|
|
|
+ var pos = { x: rightTop[0] - rectWidth / 2, y: rightTop[1] + (right_index - 1) * (rectHeight + rectMargin) }
|
|
|
+ g.setAttribute('transform', `translate(${pos.x}, ${pos.y})`);
|
|
|
+ right_index += 1
|
|
|
+ pos.x = pos.x + rectWidth / 2
|
|
|
+ pos.y = pos.y + rectHeight / 2
|
|
|
+ edgesData.value.push({ from: node_id, to: node.id, fromPos: centerPos, toPos: pos })
|
|
|
+ } else if (node.direction == "self") {
|
|
|
+ g.setAttribute('transform', `translate(${svgWidth / 2 - rectWidth / 2}, ${svgHeight / 2 - rectHeight / 2})`);
|
|
|
+ }
|
|
|
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
|
+ rect.setAttribute('width', '120');
|
|
|
+ rect.setAttribute('height', '40');
|
|
|
+ rect.setAttribute('data-node-id', node.id.toString());
|
|
|
+ if (node.direction == "self") {
|
|
|
+ rect.setAttribute('fill', '#418f92');
|
|
|
+ } else {
|
|
|
+
|
|
|
+ rect.setAttribute('fill', '#f0f0f0');
|
|
|
+ }
|
|
|
+ //rect.setAttribute('stroke', '#418f92');
|
|
|
+ rect.setAttribute('rx', '5');
|
|
|
+ rect.setAttribute('class', 'node_rect');
|
|
|
+
|
|
|
+ const link = document.createElementNS('http://www.w3.org/2000/svg', 'a')
|
|
|
+
|
|
|
+ link.setAttribute('data-node-id', node.id.toString())
|
|
|
+ link.setAttribute('class', 'node_text')
|
|
|
+ const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
|
+ text.setAttribute('x', '60');
|
|
|
+ text.setAttribute('y', '25');
|
|
|
+ text.setAttribute('text-anchor', 'middle');
|
|
|
+
|
|
|
+ text.textContent = node.label;
|
|
|
+ link.appendChild(text)
|
|
|
+
|
|
|
+ g.appendChild(rect);
|
|
|
+ g.appendChild(link);
|
|
|
+ gRectsGroup.appendChild(g);
|
|
|
+
|
|
|
+ console.log("draw node finished")
|
|
|
+
|
|
|
+ });
|
|
|
+ // 绘制边
|
|
|
+ edgesData.value.forEach(edge => {
|
|
|
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
|
+ line.setAttribute('x1', `${edge.fromPos.x}`);
|
|
|
+ line.setAttribute('y1', `${edge.fromPos.y}`);
|
|
|
+ line.setAttribute('x2', `${edge.toPos.x}`);
|
|
|
+ line.setAttribute('y2', `${edge.toPos.y}`);
|
|
|
+ line.setAttribute('stroke', '#418f92');
|
|
|
+ line.setAttribute('stroke-width', '2');
|
|
|
+
|
|
|
+ line.setAttribute('style', 'z-index:0');
|
|
|
+ gLinesGroup.appendChild(line);
|
|
|
+ });
|
|
|
+ // document.querySelectorAll('.node_rect').forEach((rect:any) => {
|
|
|
+ // rect.addEventListener('click', () => {
|
|
|
+ // const nodeId = rect.getAttribute('data-node-id');
|
|
|
+ // console.log('Node clicked:', nodeId);
|
|
|
+ // handleNodeClick(nodeId)
|
|
|
+ // });
|
|
|
+ // })
|
|
|
+
|
|
|
+ document.querySelectorAll('.node_text').forEach((rect: any) => {
|
|
|
+ rect.addEventListener('click', () => {
|
|
|
+ const nodeId = rect.getAttribute('data-node-id');
|
|
|
+ console.log('Node clicked:', nodeId);
|
|
|
+ handleNodeClick(nodeId)
|
|
|
});
|
|
|
- // document.querySelectorAll('.node_rect').forEach((rect:any) => {
|
|
|
- // rect.addEventListener('click', () => {
|
|
|
- // const nodeId = rect.getAttribute('data-node-id');
|
|
|
- // console.log('Node clicked:', nodeId);
|
|
|
- // handleNodeClick(nodeId)
|
|
|
- // });
|
|
|
- // })
|
|
|
-
|
|
|
- document.querySelectorAll('.node_text').forEach((rect:any) => {
|
|
|
- rect.addEventListener('click', () => {
|
|
|
- const nodeId = rect.getAttribute('data-node-id');
|
|
|
- console.log('Node clicked:', nodeId);
|
|
|
- handleNodeClick(nodeId)
|
|
|
- });
|
|
|
- })
|
|
|
-
|
|
|
+ })
|
|
|
+
|
|
|
}
|
|
|
let panZoomController: any = null;
|
|
|
|
|
@@ -189,33 +183,34 @@ onMounted(() => {
|
|
|
|
|
|
});
|
|
|
});
|
|
|
-const showDialog = (visible:boolean = true, nodeId:number) => {
|
|
|
- dialogFormVisible.value = visible
|
|
|
-
|
|
|
- loadNodeNeighbors(nodeId)
|
|
|
+const showDialog = (visible: boolean = true, nodeId: number) => {
|
|
|
+ dialogFormVisible.value = visible
|
|
|
+
|
|
|
+ loadNodeNeighbors(nodeId)
|
|
|
}
|
|
|
defineExpose({ showDialog })
|
|
|
</script>
|
|
|
<style scoped>
|
|
|
.graph {
|
|
|
- background-color:rgb(255, 255, 255);
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
+ background-color: rgb(255, 255, 255);
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
.edge_line {
|
|
|
- stroke: #418f92;
|
|
|
- stroke-width: 20;
|
|
|
- marker-end: url(#arrow);
|
|
|
- z-index: 1;
|
|
|
+ stroke: #418f92;
|
|
|
+ stroke-width: 20;
|
|
|
+ marker-end: url(#arrow);
|
|
|
+ z-index: 1;
|
|
|
}
|
|
|
+
|
|
|
.node_rect {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: bold;
|
|
|
- background-color: #cecece;
|
|
|
- z-index: 10;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ background-color: #cecece;
|
|
|
+ z-index: 10;
|
|
|
}
|
|
|
</style>
|