基于Vue+Echart的动态图绘制
需求分析
用户需要展示他的数据库是有哪个数据库转化的,需要展示数据库的轨迹图,前导库的关系图。
后端接口返回的格式
传入当前的数据库(节点)的id
接口返回
currentDb
是当前数据库(节点)的信息 Object
newDbList
当前数据库(节点)的后继数据库(节点)Array
oldDbList
当前数据库(节点)的前导数据库(节点)Array
前端需要实现的效果
第一次展示当前数据库(节点)以及前导数据库(节点)和后继数据库(节点)
当鼠标悬浮在某个数据库(节点)的时候,再在此基础上渲染悬浮的数据库(节点)以及前导数据库(节点)和后继数据库(节点)
解决方法
关系图的setOption
如下
vue
const chartOptions = {
title: {
text: '前导库关系图',
},
//鼠标悬浮的时候展示的内容
tooltip: {
formatter: function (params) {
return `名称: ${params.data.name}<br>
别名: ${params.data.info?.dbNickName || '无'}<br>
所在平台:${params.data.info?.datasourceSystemName || '无'}<br>
数据库类型:${params.data.info?.dbType || '无'}<br>
备注:${params.data.info?.remark || '无'}
`;
}
},
series: [
{
type: 'graph',
layout: 'none',
animation: false,
roam: true,
label: {
show: true,
},
force: {
gravity: 0,
repulsion: 1000,
edgeLength: 5
},
edgeSymbol: ['circle', 'arrow'], // 使用箭头作为边的符号
edgeSymbolSize: [4, 10],
edgeLabel: {
fontSize: 12,
},
data: this.nodes,
links: this.links,
lineStyle: {
opacity: 0.9,
width: 2,
curveness: 0,
// 添加箭头配置
arrow: {
type: 'arrow', // 箭头的类型
size: 8, // 箭头的大小
arrowOffset: 10, // 箭头偏移位置
},
},
emphasis: {
focus: 'adjacency',
link: {
show: true,
},
handleSize: 6,
},
},
],
};
参考echart官网echarts.apache.org/zh/index.ht... 可以查看配置的相关解释,此处不过多解释
准备第一次渲染 需要的数据 函数
vue
prepareData(data) {
// 当前节点
const currentNode = [
{
id: data.currentDb.id,
name: data.currentDb.dbName,
info: data.currentDb,
x: 400,
y: 100,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#e63f32'
},
}
]
this.nodePosition(currentNode)
this.currentId = data.currentDb.id;
// 根据父组件传递的数据创建节点
if (data.newDbList) {
const gridSize = 60; // 网格大小
const newNodes = data.newDbList.map((item, index) => {
// let indexNew=index+1;
const yOffset = data.newDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20) * (index + 1);
return {
id: item.id,
info: item,
name: item.dbName,
x: 400 + gridSize,
y: 100 + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
},
}
})
this.nodePosition(newNodes)
const newLinks = data.newDbList.map((link) => (
{
source: data.currentDb.id.toString(),
target: link.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const gridSize = 60; // 网格大小
const oldNodes = data.oldDbList.map((item, index) => {
// let indexNew=index+1;
const yOffset = data.oldDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20) * (index + 1);
return {
id: item.id,
info: item,
name: item.dbName,
x: 400 - gridSize,
y: 100 + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
},
};
})
this.nodePosition(oldNodes)
const oldLinks = data.oldDbList.map((link) => (
{
source: link.id.toString(),
target: data.currentDb.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},
注:此处使用曲线的原因是因为曲线可以大大降低 两个节点的连线通过第三个节点的问题 ,造成展示错误(没有做link连线的判定)
结算新节点的位置 函数
输入参数nodes
是存储节点的数组
vue
// 计算节点位置并添加节点
nodePosition(nodes) {
nodes.forEach(node => {
const originalPositionKey = `${node.x}_${node.y}`;
let positionKey = originalPositionKey;
while (this.nodePositions.has(positionKey)) {
const randomValue = Math.floor(Math.random() * 81) - 40;
node.x += randomValue;
node.y += randomValue;
positionKey = `${node.x}_${node.y}`;
}
this.nodePositions.add(positionKey);
this.nodes.push(node);
});
},
具体逻辑:先判断当前节点位置是否已经存在,如果存在,则利用随机数在存在的位置上偏移一定的值 ,再存储。
节点位置存储方式x_y
vbnet
nodePositions: new Set(), // 使用 Set 来存储节点位置信息
处理新节点信息的函数
vue
changeData(data) {
console.log('data.currentDb', data.currentDb)
// 当前节点的x,y值
const currentX = data.currentDb.x;
const currentY = data.currentDb.y;
if (data.newDbList) {
const newNodes=[]
// 使用Set来创建一个唯一节点ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
const gridSize = 60; // 网格大小
data.newDbList.map((item, index) => {
const yOffset = data.newDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20) * (index + 1);
const newNode = {
id: item.id,
info: item,
name: item.dbName,
x: currentX + gridSize,
y: currentY + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
}
}
if (!existingNodeIds.has(item.id)) {
newNodes.push(newNode)
} else {
}
this.nodePosition(newNodes)
})
const newLinks = data.newDbList.map((link) => (
{
source: data.currentDb.id.toString(),
target: link.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const oldNodes=[]
// 使用Set来创建一个唯一节点ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
console.log('existingNodeIds', existingNodeIds)
const gridSize = 60; // 网格大小
data.oldDbList.map((item, index) => {
const yOffset = data.oldDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20);
const newNode = {
id: item.id,
info: item,
name: item.dbName,
x: currentX - gridSize,
y: currentY + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
},
}
if (!existingNodeIds.has(item.id)) {
// 如果新节点的ID不存在于现有节点中,添加新节点
oldNodes.push(newNode);
} else {
}
this.nodePosition(oldNodes)
})
const oldLinks = data.oldDbList.map((link) => (
{
source: link.id.toString(),
target: data.currentDb.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},
与prepareData
函数内容差不多 ,作者此处还未做代码整合
悬浮在节点上方时的处理函数
vue
updateNode() {
this.myChart.on('mouseover', (params) => {
if (params.dataType === 'node') {
const currentData = params.data;
// 先隐藏所有节点的 label
this.nodes.forEach(node => {
if (node.label) {
node.label.show = false;
}
});
// 显示当前节点的 label
if (currentData.label) {
currentData.label.show = true;
}
databaseRelation(currentData.id).then(res => {
if (res.code === 200) {
const data = {
...res.data,
currentDb: params.data,
}
this.changeData(data)
this.drawChart();
} else {
this.$message(res.msg)
}
})
console.log('params', params.data)
}
});
}
databaseRelation
是作者项目的后端请求,请根据自己实际情况调整
主要是通过改变setOption
里面的data来改变当前的图
完整代码
vue
<template>
<div>
<div id="chart-container" style="height: 400px;"></div>
</div>
</template>
<script>
import echarts from 'echarts'
import { databaseRelation } from '@/api/qysj/app/rawData/Database'
export default {
props: {
data: Object,
},
data() {
return {
myChart: null,
nodes: [
],
links: [
],
nodePositions: new Set(), // 使用 Set 来存储节点位置信息
// 当前节点id
currentId: null
};
},
mounted() {
console.log('当前的节点', this.data.currentDb)
console.log('第一次的节点', this.data)
const chartContainer = this.$el.querySelector('#chart-container');
this.myChart = echarts.init(chartContainer);
this.myChart.clear();
this.prepareData(this.data); // 准备节点和链接数据
this.drawChart();
this.updateNode()
},
methods: {
// 准备节点和链接数据
prepareData(data) {
// 当前节点
const currentNode = [
{
id: data.currentDb.id,
name: data.currentDb.dbName,
info: data.currentDb,
x: 400,
y: 100,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#e63f32'
},
}
]
this.nodePosition(currentNode)
this.currentId = data.currentDb.id;
// 根据父组件传递的数据创建节点
if (data.newDbList) {
const gridSize = 60; // 网格大小
const newNodes = data.newDbList.map((item, index) => {
// let indexNew=index+1;
const yOffset = data.newDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20) * (index + 1);
return {
id: item.id,
info: item,
name: item.dbName,
x: 400 + gridSize,
y: 100 + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
},
}
})
this.nodePosition(newNodes)
const newLinks = data.newDbList.map((link) => (
{
source: data.currentDb.id.toString(),
target: link.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const gridSize = 60; // 网格大小
const oldNodes = data.oldDbList.map((item, index) => {
// let indexNew=index+1;
const yOffset = data.oldDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20) * (index + 1);
return {
id: item.id,
info: item,
name: item.dbName,
x: 400 - gridSize,
y: 100 + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
},
};
})
this.nodePosition(oldNodes)
const oldLinks = data.oldDbList.map((link) => (
{
source: link.id.toString(),
target: data.currentDb.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},
changeData(data) {
console.log('data.currentDb', data.currentDb)
// 当前节点的x,y值
const currentX = data.currentDb.x;
const currentY = data.currentDb.y;
if (data.newDbList) {
const newNodes=[]
// 使用Set来创建一个唯一节点ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
const gridSize = 60; // 网格大小
data.newDbList.map((item, index) => {
const yOffset = data.newDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20) * (index + 1);
const newNode = {
id: item.id,
info: item,
name: item.dbName,
x: currentX + gridSize,
y: currentY + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
}
}
if (!existingNodeIds.has(item.id)) {
newNodes.push(newNode)
} else {
}
this.nodePosition(newNodes)
})
const newLinks = data.newDbList.map((link) => (
{
source: data.currentDb.id.toString(),
target: link.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const oldNodes=[]
// 使用Set来创建一个唯一节点ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
console.log('existingNodeIds', existingNodeIds)
const gridSize = 60; // 网格大小
data.oldDbList.map((item, index) => {
const yOffset = data.oldDbList.length === 1 ? 0 : (index % 2 === 0 ? 20 : -20);
const newNode = {
id: item.id,
info: item,
name: item.dbName,
x: currentX - gridSize,
y: currentY + yOffset,
symbol: 'rect', // 使用矩形作为节点的形状
symbolSize: [40, 30], // 设置矩形节点的大小
itemStyle: {
color: '#41a5ee'
},
}
if (!existingNodeIds.has(item.id)) {
// 如果新节点的ID不存在于现有节点中,添加新节点
oldNodes.push(newNode);
} else {
}
this.nodePosition(oldNodes)
})
const oldLinks = data.oldDbList.map((link) => (
{
source: link.id.toString(),
target: data.currentDb.id.toString(),
lineStyle: {
normal: {
curveness: 0.2, // 调整曲线的弯曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},
// 计算节点位置并添加节点
nodePosition(nodes) {
nodes.forEach(node => {
const originalPositionKey = `${node.x}_${node.y}`;
let positionKey = originalPositionKey;
while (this.nodePositions.has(positionKey)) {
const randomValue = Math.floor(Math.random() * 81) - 40;
node.x += randomValue;
node.y += randomValue;
positionKey = `${node.x}_${node.y}`;
}
this.nodePositions.add(positionKey);
this.nodes.push(node);
});
},
drawChart() {
console.log('this.nodes', this.nodes)
console.log('this.links', this.links)
const chartOptions = {
title: {
text: '前导库关系图',
},
tooltip: {
formatter: function (params) {
return `名称: ${params.data.name}<br>
别名: ${params.data.info?.dbNickName || '无'}<br>
所在平台:${params.data.info?.datasourceSystemName || '无'}<br>
数据库类型:${params.data.info?.dbType || '无'}<br>
备注:${params.data.info?.remark || '无'}
`;
}
},
series: [
{
type: 'graph',
layout: 'none',
// layout: 'force',
animation: false,
// data: [this.createTreeData()], // 树状布局需要提供一个树形结构的数据
roam: true,
label: {
show: true,
},
force: {
// initLayout: 'circular'
gravity: 0,
repulsion: 1000,
edgeLength: 5
},
edgeSymbol: ['circle', 'arrow'], // 使用箭头作为边的符号
edgeSymbolSize: [4, 10],
edgeLabel: {
fontSize: 12,
},
data: this.nodes,
links: this.links,
lineStyle: {
opacity: 0.9,
width: 2,
curveness: 0,
// 添加箭头配置
arrow: {
type: 'arrow', // 箭头的类型
size: 8, // 箭头的大小
arrowOffset: 10, // 箭头偏移位置
},
},
emphasis: {
focus: 'adjacency',
link: {
show: true,
},
handleSize: 6,
},
},
],
};
this.myChart.setOption(chartOptions);
},
updateNode() {
this.myChart.on('mouseover', (params) => {
if (params.dataType === 'node') {
const currentData = params.data;
// 先隐藏所有节点的 label
this.nodes.forEach(node => {
if (node.label) {
node.label.show = false;
}
});
// 显示当前节点的 label
if (currentData.label) {
currentData.label.show = true;
}
databaseRelation(currentData.id).then(res => {
if (res.code === 200) {
const data = {
...res.data,
currentDb: params.data,
}
console.log('data', data)
this.changeData(data)
this.drawChart();
// this.myChart.setOption(chartOptions);
} else {
this.$message(res.msg)
}
})
console.log('params', params.data)
}
});
}
},
};
</script>
本文由博客一文多发平台 OpenWrite 发布!