July 14, 2023

创建一个力导向气泡图

1 先上效果

bubb.gif

可以看到效果涉及:

下面来看看是怎样实现的

2 数据格式

nodes 节点

key value
id 唯一id
label 标签文本
style 节点样式,如:fill(填充色)、stroke(边缘颜色)、shadowBlur、shadowOffsetX、shadowOffsetY、shadowColor….
description 描述文本(放大后显示)
x 节点初始位置
y 节点初始位置
size 节点大小

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const data = {
nodes: [
{
id: '0',
label: 'iCheng',
style: { fill: 'l(0) 0:#ffffff 0.5:#8FE9FF 1:#87EAEF', stroke: '', shadowBlur: 60, shadowOffsetX: 10, shadowOffsetY: 10, shadowColor: '#ADD8E6' },
description: 'See the latest updates to the MDN reference pages about JavaScript regular expressions',
x: 624.0361181340486,
y: 208.12272169694995,
size: 57
},
{
id: '1',
label: 'analyse',
style: { fill: 'l(0) 0:#FFD3C9 0.5: #FFD3C9 1:#ffffff', stroke: '', shadowBlur: 60, shadowOffsetX: 10, shadowOffsetY: 10, shadowColor: '#E6E6FA' },
description: 'Learn what HTML landmark roles are, how they improve accessibility',
x: 538.8142691877796,
y: 236.54991963171793,
size: 78
}
],
// 本示例不需要边
edges: [],
};

fill

填充色使用渐变色

描边线性渐变

1
2
// 渐变角度为 0,渐变的起始点颜色 #ffffff,中点的渐变色为 #7ec2f3,结束的渐变色为 #1890ff
'l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff'

填充环形渐变

1
2
// 渐变起始圆的圆心坐标为被填充物体的包围盒中心点,半径为(包围盒对角线长度 / 2) 的 0.1 倍,渐变的起始点颜色 #ffffff,中点的渐变色为 #7ec2f3,结束的渐变色为 #1890ff
'l(0) 0:#ffffff 0.5:#7ec2f3 1:#1890ff'

3 图配置

基于G6

Graph 是 G6 图表的载体,所有的 G6 节点实例操作以及事件,行为监听都在 Graph 实例上进行

Graph 的初始化通过 new 进行实例化,实例化时需要传入需要的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const container = document.getElementById('container');
const width = container.scrollWidth;
const height = (container.scrollHeight || 1000) - 20;

const graph = new G6.Graph({
container: 'container',
width,
height,
// 布局配置
layout: {
type: 'force', // 力导向布局
nodeStrength: 300, // 节点作用力,正数代表节点之间的引力作用,负数代表节点之间的斥力作用
collideStrength: 0.7, // 防止重叠的力强度,范围 [0, 1]
alphaDecay: 0.01, // 迭代阈值的衰减率
preventOverlap: true, // 是否防止重叠 必须在数据中设置了 size 或在该布局中配置了与当前图节点大小相同的 nodeSize 值
},
modes: {
default: ['collapse-expand-combo'], // combo类型
},
// 默认状态下节点的配置,比如 type, size, color。会被写入的 data 覆盖
defaultNode: {
type: 'bubble',
size: 80,
labelCfg: {
position: 'center',
style: {
fill: 'black',
fontSize: 6,
fontStyle: 'bold',
},
},
},
});

3.1 Force 力导向

Force 布局采用经典的力导向布局方法,将根据节点的信息,施加力使节点尽可能聚集

更多配置可查看文档:https://g6.antv.antgroup.com/api/graph-layout/combo-force

4 节点交互事件

4.1 节点开始被拖拽 node:dragstart

当节点开始被拖拽的时候触发的事件

节点开始被拖拽时,触发 refreshDragedNodePosition 方法

1
2
3
4
graph.on('node:dragstart', function (e) {
graph.layout(); // graph.layout()重新以当前配置的属性进行一次布局
refreshDragedNodePosition(e);
});

4.2 更新节点位置信息 refreshDragedNodePosition

跟随拖动的位置,做相应数据处理

参数e:鼠标拖动信息(包含节点 x、y 坐标信息)

这个方法,会拿到节点当前x、y坐标值,赋予其 fx、fy 属性

fx、fy

当你希望固定某个节点的位置,不受力的影响时,可以在该节点数据中配置 fxfy 作为固定的坐标

如果将 fxfy 置为空,该节点就会继续受力的作用而聚拢

1
2
3
4
5
6
function refreshDragedNodePosition (e) {
// console.log(e)
const model = e.item.get('model');
model.fx = e.x;
model.fy = e.y;
}

4.3 拖动过程中 node:drag

当节点在拖动过程中时触发的事件

触发 refreshDragedNodePosition 方法

1
2
3
graph.on('node:drag', function (e) {
refreshDragedNodePosition(e);
});

4.4 拖拽完成后 node:dragend

当拖拽完成后触发的事件

fxfy 置为空,该节点就会继续受力的作用而聚拢

1
2
3
4
graph.on('node:dragend', function (e) {
e.item.get('model').fx = null;
e.item.get('model').fy = null;
});

4.5 点击事件 node:click

当节点被点击时触发的事件

通过重置 size 属性,改变节点大小

重置 label 属性,改变节点的文本

再调用 graph.layout() 重新以当前配置的属性进行一次布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
graph.on('node:click', function (e) {
// debugger;
const node = e.item; //拿到当前点击的节点
const states = node.getStates(); //返回当前元素的所有状态 啥也没有
let clicked = false;
const model = node.getModel(); // 获得节点位置
let size = 200; // 点击后的大小
let labelText = 'NODE: ' + model.id + '\n' + model.description; // 拼接: "NODE: this is node 8, and the value of it is 21"
states.forEach(function (state) {
if (state === 'click') {
clicked = true;
size = model.oriSize;
labelText = model.oriLabel;
}
});
graph.setItemState(node, 'click', !clicked); //设置元素状态(元素实例,状态值,是否启用状态)
// 更新元素,包括更新数据、样式等
graph.updateItem(node, {
size,
label: labelText,
});
graph.layout(); // graph.layout()重新以当前配置的属性进行一次布局
});

5 初始化图数据

1
2
graph.data(data); // 初始化图数据,data是一个包括 节点 数组和 边 数组的对象
graph.render();

6 执行时机

因为要操作 DOM,因此上述 JS 代码在 mounted() 调用

About this Post

This post is written by Duan WeiCheng, licensed under CC BY-NC 4.0.