vue2使用d3.js实现网络拓扑图
支持节点更换图标, 线条改变颜色
- 安装
npm i d3@7.9.0 --save
- 自定义组件
<template>
<div style="width: 100%; height: 100%">
<div ref="networkChart" class="network-chart"></div>
<el-dialog title="详情" :visible.sync="dialog1" width="30%">
详情
</el-dialog>
</div>
</template>
<script>
import * as d3 from 'd3'
export default {
data() {
return {
nodes: [
{ id: 'node1', name: '192.168.1.11', image: require('@/assets/img/1.png') },
{ id: 'node2', name: '192.168.1.12', image: require('@/assets/img/2.png') },
{ id: 'node3', name: '192.168.1.13', image: require('@/assets/img/3.png') },
{ id: 'node4', name: '192.168.1.14', image: require('@/assets/img/4.png') },
],
links: [
{ id: 'link1', source: 'node2', target: 'node1', distance: 100 },
{ id: 'link2', source: 'node3', target: 'node1', distance: 150 },
{ id: 'link3', source: 'node4', target: 'node1', distance: 200 },
],
dialog1: false,
}
},
mounted() {
this.drawNetwork()
setTimeout(() => {
this.changeNodeLineStatus()
}, 5000)
},
methods: {
drawNetwork() {
const width = this.$refs.networkChart.clientWidth
const height = this.$refs.networkChart.clientHeight
const imageSize = 40
// 创建 SVG 画布
const svg = d3.select(this.$refs.networkChart).append('svg').attr('width', width).attr('height', height)
// 创建力导向图模拟
const simulation = d3
.forceSimulation(this.nodes)
.force(
'link',
d3
.forceLink(this.links)
.id((d) => d.id)
.distance((d) => d.distance) // 使用每条边的 distance 属性
)
.force('charge', d3.forceManyBody().strength(-100))
.force('center', d3.forceCenter(width / 2, height / 2))
// 绘制边
const link = svg
.append('g')
.selectAll('line')
.data(this.links)
.enter()
.append('line')
.attr('id', (d) => d.id) // 设置边的 id
.attr('stroke', '#008000') // 默认边颜色
.attr('stroke-width', 2)
// 绘制节点
const node = svg
.append('g')
.selectAll('image')
.data(this.nodes)
.enter()
.append('image')
.attr('id', (d) => d.id) // 设置边的 id
.attr('xlink:href', (d) => d.image) // 设置图片路径
.attr('width', imageSize) // 图片宽度
.attr('height', imageSize) // 图片高度
.call(d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended))
.on('click', (event, d) => this.handleNodeClick(event, d)) // 节点点击事件
// 添加节点标签
const label = svg
.append('g')
.selectAll('text')
.data(this.nodes)
.enter()
.append('text')
.text((d) => d.name)
.attr('fill', '#ffffff')
.attr('dx', 20)
.attr('dy', 4)
// 更新节点和边的位置
simulation.on('tick', () => {
link
.attr('x1', (d) => d.source.x)
.attr('y1', (d) => d.source.y)
.attr('x2', (d) => d.target.x)
.attr('y2', (d) => d.target.y)
node
.attr('x', (d) => d.x - imageSize / 2) // 图片居中
.attr('y', (d) => d.y - imageSize / 2)
label.attr('x', (d) => d.x).attr('y', (d) => d.y)
})
// 拖拽事件
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart()
event.subject.fx = event.subject.x
event.subject.fy = event.subject.y
}
function dragged(event) {
event.subject.fx = event.x
event.subject.fy = event.y
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0)
// event.subject.fx = null
// event.subject.fy = null
event.subject.fx = event.x
event.subject.fy = event.y
}
},
changeNodeLineStatus() {
let index = 0
setInterval(() => {
if (index % 2 == 0) {
d3.select('#link1').attr('stroke', '#ff000050')
} else {
d3.select('#link1').attr('stroke', '#ff0000')
}
index++
}, 1000)
d3.select('#node2').attr('xlink:href', require('@/assets/img/1red.png'))
},
// 节点点击事件处理
handleNodeClick(event, clickedNode) {
console.log('Clicked node:', clickedNode)
this.dialog1 = true
// // 重置所有节点和边的颜色
// d3.selectAll('circle').attr('fill', '#69b3a2')
// d3.selectAll('line').attr('stroke', '#999')
// // 高亮点击的节点
// d3.select(event.currentTarget).attr('fill', 'orange')
// // 高亮与点击节点相连的边
// this.links.forEach((link) => {
// if (link.source.id === clickedNode.id || link.target.id === clickedNode.id) {
// d3.selectAll('line')
// .filter((d) => d.source.id === link.source.id && d.target.id === link.target.id)
// .attr('stroke', 'red')
// }
// })
},
},
}
</script>
<style lang="less" scoped>
.network-chart {
width: 100%;
height: 100%;
}
</style>