需要用到思维导图,找到蚂蚁金服的G6可以满足需求,不得不说阿里巴巴yyds,这一套图标全家桶可太棒了
npm install --save @antv/g6
import G6 from '@antv/g6';
通过一个小demo了解基础的创建方式
<template>
<div>
<div id="mountNode"></div>
</div>
</template>
const graph = new G6.Graph({
container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身
width: 800, // Number,必须,图的宽度
height: 500, // Number,必须,图的高度
});
const data = {
// 点集
nodes: [
{
id: 'node1', // String,该节点存在则必须,节点的唯一标识
x: 100, // Number,可选,节点位置的 x 值
y: 200, // Number,可选,节点位置的 y 值
label: 'start'
},
{
id: 'node2', // String,该节点存在则必须,节点的唯一标识
x: 300, // Number,可选,节点位置的 x 值
y: 200, // Number,可选,节点位置的 y 值
label: 'end'
},
],
// 边集
edges: [
{
source: 'node1', // String,必须,起始点 id
target: 'node2', // String,必须,目标点 id
label: '我是连线'
},
],
};
graph.data(remoteData); // 加载远程数据
graph.render(); // 渲染
<template>
<div>
<div id="mountNode"></div>
</div>
</template>
<script>
import G6 from '@antv/g6';
import {onMounted} from "vue";
export default {
name: "Test02",
setup(){
const data = {
// 点集
nodes: [
{
id: 'node1', // String,该节点存在则必须,节点的唯一标识
x: 100, // Number,可选,节点位置的 x 值
y: 200, // Number,可选,节点位置的 y 值
label: 'start'
},
{
id: 'node2', // String,该节点存在则必须,节点的唯一标识
x: 300, // Number,可选,节点位置的 x 值
y: 200, // Number,可选,节点位置的 y 值
label: 'end'
},
],
// 边集
edges: [
{
source: 'node1', // String,必须,起始点 id
target: 'node2', // String,必须,目标点 id
label: '我是连线'
},
],
};
onMounted(()=>{
// 实例化graph对象
const graph = new G6.Graph({
container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身
width: 800, // Number,必须,图的宽度
height: 500, // Number,必须,图的高度
});
graph.data(remoteData); // 加载远程数据
graph.render(); // 渲染
};
main()
})
}
}
</script>
<style scoped>
</style>
之后就是详细各部分的教程
点和线的样式和设置都是包裹在nodes对象数组中的,在对象中填入下面的配置即可配置点和边的样式
{
id: 'node0', // 元素的 id
type: 'circle', // 元素的图形
size: 40, // 元素的大小
label: 'node0' // 标签文字
visible: true, // 控制初次渲染显示与隐藏,若为 false,代表隐藏。默认不隐藏
labelCfg: { // 标签配置属性
positions: 'center',// 标签的属性,标签在元素中的位置
style: { // 包裹标签样式属性的字段 style 与标签其他属性在数据结构上并行
fontSize: 12 // 标签的样式属性,文字字体大小
// ... // 标签的其他样式属性
}
}
// ..., // 其他属性
style: { // 包裹样式属性的字段 style 与其他属性在数据结构上并行
fill: '#000', // 样式属性,元素的填充色
stroke: '#888', // 样式属性,元素的描边色
// ... // 其他样式属性
}
}
既可以在节点数据就写好样式,但更多的我们是拿到后端传来的很标准的json数据(只有数据),这个时候可以通过遍历节点来设置各个节点的样式
要注意的是动态配置的样式会覆盖掉在graph初始化时配置的默认样式
const nodes = remoteData.nodes; //这里是拿到的节点数据
nodes.forEach((node) => { //遍历节点数据
if (!node.style) {
node.style = {};
}
switch (
node.class // 根据节点数据中的 class 属性配置图形
) {
case 'c0': {
node.type = 'circle'; // class = 'c0' 时节点图形为 circle
break;
}
case 'c1': {
node.type = 'rect'; // class = 'c1' 时节点图形为 rect
node.size = [35, 20]; // class = 'c1' 时节点大小
break;
}
case 'c2': {
node.type = 'ellipse'; // class = 'c2' 时节点图形为 ellipse
node.size = [35, 20]; // class = 'c2' 时节点大小
break;
}
}
});
graph.data(remoteData);
由于点和变数据在设置的时候可以指定位置,而如果画布不够大的话是会超出画布大小,显示不完整的
const graph = new G6.Graph({
container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身
width: 800, // Number,必须,图的宽度
height: 500, // Number,必须,图的高度
fitView: true, // 让图片适应当前的画布大小
fitViewPadding: [20,40,50,20] // 适应之后加入边框
});
需要点和边有统一的样式的时候,可以在graph初始化的时候指定默认样式
const graph = new G6.Graph({
container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身
width: 800, // Number,必须,图的宽度
height: 500, // Number,必须,图的高度
fitView: true, // 让图片适应当前的画布大小
fitViewPadding: [20,40,50,20], // 适应之后加入边框
// 从这开始指定默认节点样式
defaultNode: {
size: 30, // 节点大小
// ... // 节点的其他配置
// 节点样式配置
style: {
fill: 'steelblue', // 节点填充色
stroke: '#666', // 节点描边色
lineWidth: 1, // 节点描边粗细
},
// 节点上的标签文本配置
labelCfg: {
// 节点上的标签文本样式配置
style: {
fill: '#fff', // 节点标签文字颜色
},
},
},
// 边在默认状态下的样式配置(style)和其他配置
defaultEdge: {
// ... // 边的其他配置
// 边样式配置
style: {
opacity: 0.6, // 边透明度
stroke: 'grey', // 边描边颜色
},
// 边上的标签文本配置
labelCfg: {
autoRotate: true, // 边上的标签文本根据边的方向旋转
},
},
});
const graph = new G6.Graph({
// ... // 其他配置项
layout: {
// Object,可选,布局的方法及其配置项,默认为 random 布局。
type: 'force',
linkDistance: 100, // 指定边距离为100
preventOverlap: true, // 防止节点重叠
// nodeSize: 30 // 节点大小,用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性,则不需要在此设置 nodeSize。
},
});
下文会说
下文会说
当数据中没有节点位置信息,或者数据中的位置信息不满足需求时,需要借助一些布局算法对图进行布局
力导向布局:一个布局网络中,粒子与粒子之间具有引力和斥力,从初始的随机无序的布局不断演变,逐渐趋于平衡稳定的布局方式称之为力导向布局。适用于描述事物间关系,比如人物关系、计算机网络关系等。
const graph = new G6.Graph({
// 。。。。。其他配置省略
// 图布局Layout
layout: {
// Object,可选,布局的方法及其配置项,默认为 random 布局。
type: 'force',
linkDistance: 100, // 指定边距离为100
preventOverlap: true, // 防止节点重叠
// nodeSize: 30 // 节点大小,用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性,则不需要在此设置 nodeSize。
},
)}
给图添加简单的交互,比如:hover 节点、点击节点、点击边、放缩画布、拖拽画布
直接在graph初始化的时候写上mode就行,
const graph = new G6.graph({
// 交互模式
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点
},
})
const graph = new G6.Graph({
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node', // 允许拖拽画布、放缩画布、拖拽节点
{
type: 'tooltip', // 提示框
formatText(model) {
// 提示框文本内容,实际上就是放了个div进去
const text = 'label: ' + model.label + '<br/> class: ' + model.class;
return text;
}
},
{
type: 'edge-tooltip', // 边提示框
formatText(model) {
// 边提示框文本内容
const text =
'source: ' +
model.source +
'<br/> target: ' +
model.target +
'<br/> weight: ' +
model.weight;
return text;
}
},
],
)}
比如点击,hover触发的事件和样式的改变等等
const graph = new G6.Graph({
// ... // 其他配置项
// 节点不同状态下的样式集合
nodeStateStyles: {
// 鼠标 hover 上节点,即 hover 状态为 true 时的样式
hover: {
fill: 'lightsteelblue',
},
// 鼠标点击节点,即 click 状态为 true 时的样式
click: {
stroke: '#000',
lineWidth: 3,
},
},
// 边不同状态下的样式集合
edgeStateStyles: {
// 鼠标点击边,即 click 状态为 true 时的样式
click: {
stroke: 'steelblue',
},
},
});
//graph事件监听
// 鼠标进入节点
graph.on('node:mouseenter', (e) => {
const nodeItem = e.item; // 获取鼠标进入的节点元素对象
graph.setItemState(nodeItem, 'hover', true); // 设置当前节点的 hover 状态为 true
});
// 鼠标离开节点
graph.on('node:mouseleave', (e) => {
const nodeItem = e.item; // 获取鼠标离开的节点元素对象
graph.setItemState(nodeItem, 'hover', false); // 设置当前节点的 hover 状态为 false
});
// 点击节点
graph.on('node:click', (e) => {
// 先将所有当前是 click 状态的节点置为非 click 状态
const clickNodes = graph.findAllByState('node', 'click');
clickNodes.forEach((cn) => {
graph.setItemState(cn, 'click', false);
});
const nodeItem = e.item; // 获取被点击的节点元素对象
graph.setItemState(nodeItem, 'click', true); // 设置当前节点的 click 状态为 true
});
// 点击边
graph.on('edge:click', (e) => {
// 先将所有当前是 click 状态的边置为非 click 状态
const clickEdges = graph.findAllByState('edge', 'click');
clickEdges.forEach((ce) => {
graph.setItemState(ce, 'click', false);
});
const edgeItem = e.item; // 获取被点击的边元素对象
graph.setItemState(edgeItem, 'click', true); // 设置当前边的 click 状态为 true
});
步骤:
// 实例化minimap插件对象
const minimap = new G6.Minimap({
size: [100, 100],
className: 'minimap',
type: 'delegate',
})
// 实例化网格插件对象
const grid = new G6.Grid()
const graph = new G6.Graph({
// ......其余配置
plugins:[minimap, grid],
)}
整个组件的案例代码在这,里面包含了上面说的所有部分
<template>
<div>
<div id="mountNode"></div>
</div>
</template>
<script>
import request from "../utils/request";
import G6 from '@antv/g6';
import {onMounted} from "vue";
export default {
name: "Test02",
setup(){
// 实例化minimap插件对象
const minimap = new G6.Minimap({
size: [100, 100],
className: 'minimap',
type: 'delegate',
})
// 实例化网格插件对象
const grid = new G6.Grid()
const data = {
// 点集
nodes: [
{
id: 'node1', // String,该节点存在则必须,节点的唯一标识
x: 100, // Number,可选,节点位置的 x 值
y: 200, // Number,可选,节点位置的 y 值
label: 'start'
},
{
id: 'node2', // String,该节点存在则必须,节点的唯一标识
x: 300, // Number,可选,节点位置的 x 值
y: 200, // Number,可选,节点位置的 y 值
label: 'end'
},
],
// 边集
edges: [
{
source: 'node1', // String,必须,起始点 id
target: 'node2', // String,必须,目标点 id
label: '我是连线'
},
],
};
onMounted(()=>{
const graph = new G6.Graph({
container: 'mountNode', // String | HTMLElement,必须,在 Step 1 中创建的容器 id 或容器本身
width: 800, // Number,必须,图的宽度
height: 500, // Number,必须,图的高度
fitView: true, // 让图片适应当前的画布大小
fitViewPadding: [20,40,50,20], // 适应之后加入边框
plugins:[minimap, grid],
// 图布局Layout
layout: {
// Object,可选,布局的方法及其配置项,默认为 random 布局。
type: 'force',
linkDistance: 100, // 指定边距离为100
preventOverlap: true, // 防止节点重叠
// nodeSize: 30 // 节点大小,用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性,则不需要在此设置 nodeSize。
},
// 交互模式
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node', // 允许拖拽画布、放缩画布、拖拽节点
{
type: 'tooltip', // 提示框
formatText(model) {
// 提示框文本内容,实际上就是放了个div进去
const text = 'label: ' + model.label + '<br/> class: ' + model.class;
return text;
}
},
{
type: 'edge-tooltip', // 边提示框
formatText(model) {
// 边提示框文本内容
const text =
'source: ' +
model.source +
'<br/> target: ' +
model.target +
'<br/> weight: ' +
model.weight;
return text;
}
},
],
},
// 交互式状态,hover和click时候的样式改变
nodeStateStyles: {
// 鼠标 hover 上节点,即 hover 状态为 true 时的样式
hover: {
fill: 'lightsteelblue',
},
// 鼠标点击节点,即 click 状态为 true 时的样式
click: {
stroke: '#000',
lineWidth: 3,
},
},
// 边不同状态下的样式集合
edgeStateStyles: {
// 鼠标点击边,即 click 状态为 true 时的样式
click: {
stroke: 'steelblue',
},
},
// 节点的默认样式
defaultNode: {
size: 30, // 节点大小
// ... // 节点的其他配置
// 节点样式配置
style: {
fill: 'steelblue', // 节点填充色
stroke: '#666', // 节点描边色
lineWidth: 1, // 节点描边粗细
},
// 节点上的标签文本配置
labelCfg: {
// 节点上的标签文本样式配置
style: {
fill: '#fff', // 节点标签文字颜色
},
},
},
// 边在默认状态下的样式配置(style)和其他配置
defaultEdge: {
// ... // 边的其他配置
// 边样式配置
style: {
opacity: 0.6, // 边透明度
stroke: 'grey', // 边描边颜色
},
// 边上的标签文本配置
labelCfg: {
autoRotate: true, // 边上的标签文本根据边的方向旋转
},
},
});
//graph事件监听
// 鼠标进入节点
graph.on('node:mouseenter', (e) => {
const nodeItem = e.item; // 获取鼠标进入的节点元素对象
graph.setItemState(nodeItem, 'hover', true); // 设置当前节点的 hover 状态为 true
});
// 鼠标离开节点
graph.on('node:mouseleave', (e) => {
const nodeItem = e.item; // 获取鼠标离开的节点元素对象
graph.setItemState(nodeItem, 'hover', false); // 设置当前节点的 hover 状态为 false
});
// 点击节点
graph.on('node:click', (e) => {
// 先将所有当前是 click 状态的节点置为非 click 状态
const clickNodes = graph.findAllByState('node', 'click');
clickNodes.forEach((cn) => {
graph.setItemState(cn, 'click', false);
});
const nodeItem = e.item; // 获取被点击的节点元素对象
graph.setItemState(nodeItem, 'click', true); // 设置当前节点的 click 状态为 true
});
// 点击边
graph.on('edge:click', (e) => {
// 先将所有当前是 click 状态的边置为非 click 状态
const clickEdges = graph.findAllByState('edge', 'click');
clickEdges.forEach((ce) => {
graph.setItemState(ce, 'click', false);
});
const edgeItem = e.item; // 获取被点击的边元素对象
graph.setItemState(edgeItem, 'click', true); // 设置当前边的 click 状态为 true
});
//案例中的真实数据
const main = async () => {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json',
);
const remoteData = await response.json();
// ...
graph.data(remoteData); // 加载远程数据
graph.render(); // 渲染
};
main()
})
}
}
</script>
<style scoped>
.g6-tooltip {
border: 1px solid #e2e2e2;
border-radius: 4px;
font-size: 12px;
color: #545454;
background-color: rgba(117, 77, 77, 0.9);
padding: 10px 8px;
box-shadow: rgb(174, 174, 174) 0px 0px 10px;
}
</style>