基于Vue+Echart繪制動(dòng)態(tài)圖
需求分析
用戶(hù)需要展示他的數(shù)據(jù)庫(kù)是有哪個(gè)數(shù)據(jù)庫(kù)轉(zhuǎn)化的,需要展示數(shù)據(jù)庫(kù)的軌跡圖,前導(dǎo)庫(kù)的關(guān)系圖。
后端接口返回的格式
傳入當(dāng)前的數(shù)據(jù)庫(kù)(節(jié)點(diǎn))的id

接口返回
currentDb是當(dāng)前數(shù)據(jù)庫(kù)(節(jié)點(diǎn))的信息 Object
newDbList當(dāng)前數(shù)據(jù)庫(kù)(節(jié)點(diǎn))的后繼數(shù)據(jù)庫(kù)(節(jié)點(diǎn))Array
oldDbList當(dāng)前數(shù)據(jù)庫(kù)(節(jié)點(diǎn))的前導(dǎo)數(shù)據(jù)庫(kù)(節(jié)點(diǎn))Array
前端需要實(shí)現(xiàn)的效果
第一次展示當(dāng)前數(shù)據(jù)庫(kù)(節(jié)點(diǎn))以及前導(dǎo)數(shù)據(jù)庫(kù)(節(jié)點(diǎn))和后繼數(shù)據(jù)庫(kù)(節(jié)點(diǎn))
當(dāng)鼠標(biāo)懸浮在某個(gè)數(shù)據(jù)庫(kù)(節(jié)點(diǎn))的時(shí)候,再在此基礎(chǔ)上渲染懸浮的數(shù)據(jù)庫(kù)(節(jié)點(diǎn))以及前導(dǎo)數(shù)據(jù)庫(kù)(節(jié)點(diǎn))和后繼數(shù)據(jù)庫(kù)(節(jié)點(diǎn))

解決方法
關(guān)系圖的setOption
如下
const chartOptions = {
title: {
text: '前導(dǎo)庫(kù)關(guān)系圖',
},
//鼠標(biāo)懸浮的時(shí)候展示的內(nèi)容
tooltip: {
formatter: function (params) {
return `名稱(chēng): ${params.data.name}<br>
別名: ${params.data.info?.dbNickName || '無(wú)'}<br>
所在平臺(tái):${params.data.info?.datasourceSystemName || '無(wú)'}<br>
數(shù)據(jù)庫(kù)類(lèi)型:${params.data.info?.dbType || '無(wú)'}<br>
備注:${params.data.info?.remark || '無(wú)'}
`;
}
},
series: [
{
type: 'graph',
layout: 'none',
animation: false,
roam: true,
label: {
show: true,
},
force: {
gravity: 0,
repulsion: 1000,
edgeLength: 5
},
edgeSymbol: ['circle', 'arrow'], // 使用箭頭作為邊的符號(hào)
edgeSymbolSize: [4, 10],
edgeLabel: {
fontSize: 12,
},
data: this.nodes,
links: this.links,
lineStyle: {
opacity: 0.9,
width: 2,
curveness: 0,
// 添加箭頭配置
arrow: {
type: 'arrow', // 箭頭的類(lèi)型
size: 8, // 箭頭的大小
arrowOffset: 10, // 箭頭偏移位置
},
},
emphasis: {
focus: 'adjacency',
link: {
show: true,
},
handleSize: 6,
},
},
],
};參考echart官網(wǎng)https://echarts.apache.org/zh/index.html 可以查看配置的相關(guān)解釋?zhuān)颂幉贿^(guò)多解釋
準(zhǔn)備第一次渲染 需要的數(shù)據(jù) 函數(shù)
prepareData(data) {
// 當(dāng)前節(jié)點(diǎn)
const currentNode = [
{
id: data.currentDb.id,
name: data.currentDb.dbName,
info: data.currentDb,
x: 400,
y: 100,
symbol: 'rect', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
itemStyle: {
color: '#e63f32'
},
}
]
this.nodePosition(currentNode)
this.currentId = data.currentDb.id;
// 根據(jù)父組件傳遞的數(shù)據(jù)創(chuàng)建節(jié)點(diǎn)
if (data.newDbList) {
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},注:此處使用曲線(xiàn)的原因是因?yàn)榍€(xiàn)可以大大降低 兩個(gè)節(jié)點(diǎn)的連線(xiàn)通過(guò)第三個(gè)節(jié)點(diǎn)的問(wèn)題 ,造成展示錯(cuò)誤(沒(méi)有做link連線(xiàn)的判定)
結(jié)算新節(jié)點(diǎn)的位置 函數(shù)
輸入?yún)?shù)nodes是存儲(chǔ)節(jié)點(diǎn)的數(shù)組
// 計(jì)算節(jié)點(diǎn)位置并添加節(jié)點(diǎn)
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);
});
},具體邏輯:先判斷當(dāng)前節(jié)點(diǎn)位置是否已經(jīng)存在,如果存在,則利用隨機(jī)數(shù)在存在的位置上偏移一定的值 ,再存儲(chǔ)。
節(jié)點(diǎn)位置存儲(chǔ)方式x_y
nodePositions: new Set(), // 使用 Set 來(lái)存儲(chǔ)節(jié)點(diǎn)位置信息
處理新節(jié)點(diǎn)信息的函數(shù)
changeData(data) {
console.log('data.currentDb', data.currentDb)
// 當(dāng)前節(jié)點(diǎn)的x,y值
const currentX = data.currentDb.x;
const currentY = data.currentDb.y;
if (data.newDbList) {
const newNodes=[]
// 使用Set來(lái)創(chuàng)建一個(gè)唯一節(jié)點(diǎn)ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const oldNodes=[]
// 使用Set來(lái)創(chuàng)建一個(gè)唯一節(jié)點(diǎn)ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
console.log('existingNodeIds', existingNodeIds)
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
itemStyle: {
color: '#41a5ee'
},
}
if (!existingNodeIds.has(item.id)) {
// 如果新節(jié)點(diǎn)的ID不存在于現(xiàn)有節(jié)點(diǎn)中,添加新節(jié)點(diǎn)
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},與prepareData函數(shù)內(nèi)容差不多 ,作者此處還未做代碼整合
懸浮在節(jié)點(diǎn)上方時(shí)的處理函數(shù)
updateNode() {
this.myChart.on('mouseover', (params) => {
if (params.dataType === 'node') {
const currentData = params.data;
// 先隱藏所有節(jié)點(diǎn)的 label
this.nodes.forEach(node => {
if (node.label) {
node.label.show = false;
}
});
// 顯示當(dāng)前節(jié)點(diǎn)的 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是作者項(xiàng)目的后端請(qǐng)求,請(qǐng)根據(jù)自己實(shí)際情況調(diào)整
主要是通過(guò)改變setOption里面的data來(lái)改變當(dāng)前的圖
完整代碼
<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 來(lái)存儲(chǔ)節(jié)點(diǎn)位置信息
// 當(dāng)前節(jié)點(diǎn)id
currentId: null
};
},
mounted() {
console.log('當(dāng)前的節(jié)點(diǎn)', this.data.currentDb)
console.log('第一次的節(jié)點(diǎn)', this.data)
const chartContainer = this.$el.querySelector('#chart-container');
this.myChart = echarts.init(chartContainer);
this.myChart.clear();
this.prepareData(this.data); // 準(zhǔn)備節(jié)點(diǎn)和鏈接數(shù)據(jù)
this.drawChart();
this.updateNode()
},
methods: {
// 準(zhǔn)備節(jié)點(diǎn)和鏈接數(shù)據(jù)
prepareData(data) {
// 當(dāng)前節(jié)點(diǎn)
const currentNode = [
{
id: data.currentDb.id,
name: data.currentDb.dbName,
info: data.currentDb,
x: 400,
y: 100,
symbol: 'rect', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
itemStyle: {
color: '#e63f32'
},
}
]
this.nodePosition(currentNode)
this.currentId = data.currentDb.id;
// 根據(jù)父組件傳遞的數(shù)據(jù)創(chuàng)建節(jié)點(diǎn)
if (data.newDbList) {
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},
changeData(data) {
console.log('data.currentDb', data.currentDb)
// 當(dāng)前節(jié)點(diǎn)的x,y值
const currentX = data.currentDb.x;
const currentY = data.currentDb.y;
if (data.newDbList) {
const newNodes=[]
// 使用Set來(lái)創(chuàng)建一個(gè)唯一節(jié)點(diǎn)ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...newLinks
]
}
if (data.oldDbList) {
const oldNodes=[]
// 使用Set來(lái)創(chuàng)建一個(gè)唯一節(jié)點(diǎn)ID的集合
const existingNodeIds = new Set(this.nodes.map(node => node.id));
console.log('existingNodeIds', existingNodeIds)
const gridSize = 60; // 網(wǎng)格大小
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', // 使用矩形作為節(jié)點(diǎn)的形狀
symbolSize: [40, 30], // 設(shè)置矩形節(jié)點(diǎn)的大小
itemStyle: {
color: '#41a5ee'
},
}
if (!existingNodeIds.has(item.id)) {
// 如果新節(jié)點(diǎn)的ID不存在于現(xiàn)有節(jié)點(diǎn)中,添加新節(jié)點(diǎn)
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, // 調(diào)整曲線(xiàn)的彎曲度
},
},
}
))
this.links = [
...this.links,
...oldLinks
]
}
},
// 計(jì)算節(jié)點(diǎn)位置并添加節(jié)點(diǎn)
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: '前導(dǎo)庫(kù)關(guān)系圖',
},
tooltip: {
formatter: function (params) {
return `名稱(chēng): ${params.data.name}<br>
別名: ${params.data.info?.dbNickName || '無(wú)'}<br>
所在平臺(tái):${params.data.info?.datasourceSystemName || '無(wú)'}<br>
數(shù)據(jù)庫(kù)類(lèi)型:${params.data.info?.dbType || '無(wú)'}<br>
備注:${params.data.info?.remark || '無(wú)'}
`;
}
},
series: [
{
type: 'graph',
layout: 'none',
// layout: 'force',
animation: false,
// data: [this.createTreeData()], // 樹(shù)狀布局需要提供一個(gè)樹(shù)形結(jié)構(gòu)的數(shù)據(jù)
roam: true,
label: {
show: true,
},
force: {
// initLayout: 'circular'
gravity: 0,
repulsion: 1000,
edgeLength: 5
},
edgeSymbol: ['circle', 'arrow'], // 使用箭頭作為邊的符號(hào)
edgeSymbolSize: [4, 10],
edgeLabel: {
fontSize: 12,
},
data: this.nodes,
links: this.links,
lineStyle: {
opacity: 0.9,
width: 2,
curveness: 0,
// 添加箭頭配置
arrow: {
type: 'arrow', // 箭頭的類(lèi)型
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;
// 先隱藏所有節(jié)點(diǎn)的 label
this.nodes.forEach(node => {
if (node.label) {
node.label.show = false;
}
});
// 顯示當(dāng)前節(jié)點(diǎn)的 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>以上就是基于Vue+Echart繪制動(dòng)態(tài)圖的詳細(xì)內(nèi)容,更多關(guān)于Vue Echart動(dòng)態(tài)圖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Vue.js項(xiàng)目API、Router配置拆分實(shí)踐
這篇文章主要介紹了詳解Vue.js項(xiàng)目API、Router配置拆分實(shí)踐,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
在vue3中使用el-tree-select實(shí)現(xiàn)樹(shù)形下拉選擇器效果
el-tree-select是一個(gè)含有下拉菜單的樹(shù)形選擇器,結(jié)合了?el-tree?和?el-select?兩個(gè)組件的功能,這篇文章主要介紹了在vue3中使用el-tree-select做一個(gè)樹(shù)形下拉選擇器,需要的朋友可以參考下2024-03-03
Vue.js axios響應(yīng)攔截如何獲取返回狀態(tài)碼
這篇文章主要介紹了Vue.js axios響應(yīng)攔截如何獲取返回狀態(tài)碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
Vue 讀取HTMLCollection列表的length為0問(wèn)題
這篇文章主要介紹了Vue 讀取HTMLCollection列表的length為0問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
使用Vue3實(shí)現(xiàn)倒計(jì)時(shí)器及倒計(jì)時(shí)任務(wù)的完整代碼
文章介紹了如何使用Vue3和Element-plus開(kāi)發(fā)一個(gè)倒計(jì)時(shí)器和倒計(jì)時(shí)任務(wù)管理界面,倒計(jì)時(shí)器具備手動(dòng)設(shè)置、開(kāi)始、暫停和重啟功能,文章還提供了倒計(jì)時(shí)器的完整代碼,包括HTML、JavaScript和CSS部分,感興趣的朋友一起看看吧2024-11-11
vue動(dòng)畫(huà)效果實(shí)現(xiàn)方法示例
這篇文章主要介紹了vue動(dòng)畫(huà)效果實(shí)現(xiàn)方法,結(jié)合完整實(shí)例形式分析了vue.js+animate.css實(shí)現(xiàn)的動(dòng)畫(huà)切換效果相關(guān)操作技巧,需要的朋友可以參考下2019-03-03

