1. 前端选型
本着凡事先问GPT的原则,先看看GPT给我哪些答案。
1.1 vis:好像没听过,试一下看看
1.2. echarts:一般用来做图表,关系图也没用过,试一下
1.4. d3:听说入门比较麻烦,对菜鸟不友好,算了,先不尝试了
1.3. G6:蚂蚁出品的可视化的组件,试试。
2. 测试数据准备
这里主要准备了一些《狂飙》电视剧中的人物关系,只当个Demo做演示用,不要琢磨细致程度
3. 写代码测试效果
3.1. vis
<template>
<div id="graphContainer"></div>
</template>
<script>
import {Network, DataSet} from "vis-network/standalone";
import "vis-network/styles/vis-network.css";
import {nodes, edges} from './data/data'
export default {
name: "Index",
data() {
return {
graph: undefined,
nodes: [],
edges: [],
container: undefined,
}
},
mounted() {
this.$nextTick(() => {
this.initGraph()
})
},
methods: {
// 初始化图形配置
initGraph() {
edges.forEach((item) => {
item.from = item.source
item.to = item.target
})
this.nodes = new DataSet(nodes);
this.edges = new DataSet(edges);
const container = document.getElementById("graphContainer");
const data = {
nodes: this.nodes,
edges: this.edges
};
const options = {};
const network = new Network(container, data, options);
console.log(network)
}
}
}
</script>
<style scoped>
#graphContainer {
width: 100%;
height: 800px;
background-color: white;
}
</style>
3.2. echarts
<template>
<div id="graphContainer"></div>
</template>
<script>
import * as echarts from 'echarts'
import {nodes, edges} from './data/data'
export default {
name: "Index",
data() {
return {
graph: undefined,
nodes: [],
edges: [],
option: {}
}
},
mounted() {
this.$nextTick(() => {
this.initGraph()
})
},
methods: {
initData() {
this.nodes = [...nodes]
this.edges = [...edges]
let iconList = []
this.nodes.forEach(item => {
item.name = item.label
item.symbolSize = 50
item.icon = item.icon || 'logo.png'
iconList.push(this.getImgData(item.icon))
})
this.edges.forEach(item => {
item.name = item.label
item.value = item.label
})
// Promise.all(iconList).then(imgUrls => {
// this.nodes.forEach((item, i) => {
// item.symbol = "image://" + imgUrls[i]
// })
// this.renderGraph()
// })
this.renderGraph()
},
// 初始化图形配置
initGraph() {
this.graph = echarts.init(document.getElementById("graphContainer"))
this.initData()
this.renderGraph()
},
// 用数据渲染图形
renderGraph() {
this.option = {
title: {
text: '狂飙人物关系图',
subtext: 'Default layout',
top: 'bottom',
left: 'right'
},
tooltip: {
show: false
},
series: [
{
name: '狂飙人物关系图',
type: 'graph',
layout: 'force',
data: this.nodes,
links: this.edges,
roam: true,
itemStyle: {},
label: {
show: true,
position: 'bottom',
formatter: '{b}'
},
edgeLabel: {
show: true,
formatter: '{c}'
},
force: {
edgeLength: [100, 200],
repulsion: 800
},
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 10
},
edgeLabel: {
color: 'red'
}
}
}
]
}
this.graph.setOption(this.option)
},
getImgData(imgSrc) {
let fun = function (resolve) {
const canvas = document.createElement('canvas');
const contex = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = '';
img.onload = function () {
// 设置图形宽高比例
let center = {
x: img.width / 2,
y: img.height / 2
};
let diameter = img.width;
let radius = diameter / 2; // 半径
canvas.width = diameter;
canvas.height = diameter;
contex.clearRect(0, 0, diameter, diameter);
contex.save();
contex.beginPath();
contex.arc(radius, radius, radius, 0, 2 * Math.PI); // 画出圆
contex.clip();
contex.drawImage(
img,
center.x - radius,
center.y - radius,
diameter,
diameter,
0,
0,
diameter,
diameter
); // 在刚刚裁剪的园上画图
contex.restore(); // 还原状态
resolve(canvas.toDataURL('image/png', 1));
};
img.src = imgSrc;
};
let promise = new Promise(fun);
return promise;
}
}
}
</script>
<style scoped>
#graphContainer {
width: 100%;
height: 800px;
background-color: white;
}
</style>
3.3. G6
<template>
<div id="graphContainer"></div>
</template>
<script>
import G6 from '@antv/g6'
import {nodes, edges} from './data/data'
export default {
name: "Index",
data() {
return {
graph: undefined,
nodes: [],
edges: [],
container: undefined,
}
},
mounted() {
this.$nextTick(() => {
this.initGraph()
})
},
methods: {
// 初始化图形配置
initGraph() {
this.container = document.getElementById("graphContainer")
const width = this.container.scrollWidth
const height = this.container.scrollHeight
this.graph = new G6.Graph({
container: 'graphContainer',
width,
height,
layout: {
type: 'force',
preventOverlap: true,
linkDistance: 120, // 可选,边长
nodeStrength: 30, // 可选
edgeStrength: 0.6, // 可选
collideStrength: 0.2, // 可选
nodeSize: 120
},
defaultNode: {
color: '#5B8FF9',
size: 40,
labelCfg: {
position: 'bottom'
}
},
modes: {
default: ['drag-canvas'],
},
});
this.graph.on('node:dragstart', e => {
this.graph.layout();
console.log(e)
this.refreshDragedNodePosition(e);
});
this.graph.on('node:drag', e => {
console.log(e)
this.refreshDragedNodePosition(e);
});
this.graph.on('node:dragend', e => {
e.item.get('model').fx = null;
e.item.get('model').fy = null;
});
this.renderGraph()
},
// 用数据渲染图形
renderGraph() {
this.nodes = nodes || []
this.edges = nodes || []
this.graph.data({
nodes: nodes,
edges: edges.map(function (edge, i) {
edge.id = 'edge' + i;
return Object.assign({}, edge);
}),
});
this.graph.render();
},
refreshDragedNodePosition(e) {
const model = e.item.get('model');
model.fx = e.x;
model.fy = e.y;
}
}
}
</script>
<style scoped>
#graphContainer {
width: 100%;
height: 800px;
background-color: white;
}
</style>
3. 加上人物头像
三个组件其实展示效果都差不多,需要更好的效果还是需要后期再进行一些配置与编码,这里我用了echarts进行了进一步的配置,增加了头像,这里获取人物头像这个参考了网络上的,具体的地址忘记了,如有需要联系加上哈。
echarts中有个突出关联节点的配置还比较实用,vis其实也有,g6的我是没找到,可能是我还没看到,看下echarts中的效果。
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 10
},
edgeLabel: {
color: 'red'
}
}