使用布局 Layout
简介
图布局是指图中节点的排布方式,根据图的数据结构不同,布局可以分为两类:一般图布局、树图布局。G6 为这两类图都内置了一些常用的图布局算法。使用内置的图布局可以完成布局的参数、方法、数据的切换等。
除了内置布局方法外,一般图布局还支持 自定义布局 机制。
事实上,G6 的布局是自由的,内置布局算法仅仅是操作了数据中节点的 x
和 y
值。因此,除了使用内置布局以及自定义的一般图布局外,用户还可以使用外部图布局算法,计算节点位置后赋值到数据中节点的 x
和 y
字段上,G6 便可以根据该位置信息进行绘制。
本文将逐一介绍内置的布局算法,及其使用方式。
G6 布局方法总览
一般图 Graph
- Random Layout:随机布局;
- Force Layout:经典力导向布局;
- Fruchterman Layout:Fruchterman 布局,一种力导布局;
- Circular Layout:环形布局;
- Radial Layout:辐射状布局;
- MDS Layout:高维数据降维算法布局;
- Dagre Layout:层次布局;
- Concentric Layout:同心圆布局;
- Grid Layout:网格布局。
树图 TreeGraph
- CompactBox Layout:紧凑树布局;
- Dendrogram Layout:树状布局(叶子节点布局对齐到同一层);
- Intended Layout:缩进布局;
- Mindmap Layout:脑图布局。
一般图 Graph
配置一般图布局
用户可以通过在实例化图时使用图的配置项 layout
指定布局方法。下面代码在实例化图时设置了布局方法为 type: 'force'
,即经典力导向图布局。并设置了参数 preventOverlap: true
和 nodeSize: 30
,表示希望节点不重叠。节点大小 nodeSize
用于算法中判断节点是否重叠,更多配置项见 Graph 各布局的配置项。
const graph = new G6.Graph({
... // 其他配置项
layout: { // Object,可选,布局的方法及其配置项,默认为 random 布局。
type: 'force',
preventOverlap: true,
nodeSize: 30,
... // 其他配置
}
});
当实例化图时没有配置布局时:
- 若数据中节点有位置信息(
x
和y
),则按照数据的位置信息进行绘制; - 若数据中节点没有位置信息,则默认使用 Random Layout 进行布局。
一般图布局方法
图布局通用 API:Layout API。
Random
描述:随机布局。API:Random API参数:
参数名 | 类型 | 示例 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
width | Number | 300 | 图的宽 | |
height | Number | 300 | 图的高 |
Force
描述:经典力导向布局。API:Force API参数:与 d3 的力导布局参数相对应。
参数名 | 类型 | 示例 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
linkDistance | Number | Function | 示例1: 50 示例2:d => { // d 是一条边 if (d.id === 'edge1') { return 100; } return 50;} | 50 |
nodeStrength | Number | Function | 示例1: -30 示例2:d => { // d 是一个节点 if (d.id === 'node1') { return -100; } return -30;} | null |
edgeStrength | Number | 示例1: 1 示例2:d => { // d 是一个节点 if (d.id === 'node1') { return 10; } return 1;} | null | 边的作用力,默认根据节点的出入度自适应。可以使用回调函数的形式对不同对节点定义不同边作用力(如示例2) |
preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 nodeSize ,只有设置了与当前图节点大小相同的 nodeSize 值,才能够进行节点重叠的碰撞检测。若未设置 nodeSize ,则根据节点数据中的 size 进行碰撞检测。若二者都未设置,则默认以 10 为节点大小进行碰撞检测 |
nodeSize | Array | Number | 20 | undefined |
nodeSpacing3.1.6 后支持 | Number / Function | 示例 1 : 10示例 2 : d => { // d 是一个节点 if (d.id === 'node1') { return 100; } return 10;} | 0 | preventOverlap 为 true 时生效,防止重叠时节点边缘间距的最小值。可以是回调函数,为不同节点设置不同的最小间距,如示例 2 所示 |
alphaDecay | Number | 0.03 | 0.028 | 迭代阈值的衰减率。[0, 1],0.028 对应迭代书为 300 |
alphaMin | Number | 0.03 | 0.001 | 停止迭代的阈值 |
alpha | Number | 0.1 | 0.3 | 当前阈值 |
collideStrength | Number | 0.8 | 1 | 防止重叠的力强度,[0, 1]。 |
forceSimulation | Object | null | 自定义 force 方法,若不指定,则使用 d3 的方法。 | |
onTick | Function | {} | 每一次迭代的回调函数 | |
onLayoutEnd | Function | {} | 布局完成后的回调函数 |
Fruchterman
描述:Fruchterman 布局,一种力导布局。API:Fruchterman API参数:
参数名 | 类型 | 示例 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
maxIteration | Number | 1000 | 1000 | 最大迭代次数 |
gravity | Number | 10 | 10 | 重力大小,影响布局的紧凑程度 |
speed | Number | 1 | 1 | 每次迭代节点移动的速度。速度太快可能会导致强烈震荡 |
clustering | Boolean | false | false | 是否按照聚类布局 |
clusterGravity | Number | 30 | 10 | 聚类内部的重力大小,影响聚类的紧凑程度 |
Circular
描述:环形布局。API:Circular API参数:
参数名 | 类型 | 示例/可选值 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
radius | Number | 50 | null | 圆的半径。若设置了 radius,则 startRadius 与 endRadius 不生效 |
startRadius | Number | 10 | null | 螺旋状布局的起始半径 |
endRadius | Number | 100 | null | 螺旋状布局的结束半径 |
clockwise | Boolean | true | true | 是否顺时针排列 |
divisions | Number | 3 | 1 | 节点在环上的分段数(几个段将均匀分布),在 endRadius - startRadius != 0 时生效 |
ordering | String | null | 'topology' | 'degree' |
angleRatio | Number | 1 | 1 | 从第一个节点到最后节点之间相隔多少个 2*PI |
Radial
描述:辐射状布局。API:Radial API参数:
参数名 | 类型 | 示例 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
linkDistance | Number | 50 | 50 | 边长 |
maxIteration | Number | 1000 | 1000 | 停止迭代到最大迭代数 |
focusNode | String | Object | 'node1' | null |
unitRadius | Number | 10 | 100 | 每一圈距离上一圈的距离。默认填充整个画布,即根据图的大小决定 |
preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 nodeSize ,只有设置了与当前图节点大小相同的 nodeSize 值,才能够进行节点重叠的碰撞检测。3.1.6 后支持:若未设置 nodeSize ,则将会根据数据中节点的 size 字段数值进行碰撞检测计算。若二者皆未设置,则以节点大小为 10 进行计算。 |
maxPreventOverlapIteration | Number | 500 | 200 | 防止重叠步骤的最大迭代次数 |
nodeSize | Number | 10 | 10 | 节点大小(直径)。用于防止节点重叠时的碰撞检测。3.1.6 后支持:若未设置则使用数据中节点的 size 字段数值进行碰撞检测计算。若二者皆未设置,则以节点大小为 10 进行计算。 |
nodeSpacing3.1.6 后支持 | Number | Function | 示例 1 : 10示例 2 : d => { // d 是一个节点 if (d.id === 'node1') { return 100; } return 10;} | 0 |
strictRadial | Boolean | true | false | 是否必须是严格的 radial 布局,即每一层的节点严格布局在一个环上。preventOverlap 为 true 时生效。详见 Radial-strictRadial API- 当 preventOverlap 为 true ,且 strictRadial 为 false 时,有重叠的节点严格沿着所在的环展开,但在一个环上若节点过多,可能无法完全避免节点重叠。- 当 preventOverlap 为 true ,且 strictRadial 为 true 时,允许同环上重叠的节点不严格沿着该环布局,可以在该环的前后偏移以避免重叠。 |
MDS
描述:高维数据降维算法布局。API:MDS API参数:
参数名 | 类型 | 示例 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
linkDistance | Number | 50 | 50 | 边长 |
Dagre
描述:层次布局。API:Dagre API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
rankdir | String | 'TB' / 'BT' / 'LR' / 'RL' | 'TB' | layout 的方向。T:top;B:bottom;L:left;R:right |
align | String | 'UL' / 'UR' / 'DL' / 'DR' | 'UL' | 节点对齐方式。U:upper;D:down;L:left;R:right |
nodesep | Number | 40 | 50 | 在rankdir 为 'TB' 或 'BT' 时代表节点水平间距(px);在rankdir 为 'LR' 或 'RL' 时代表节点的竖直间距。优先级高于 nodesepFunc |
ranksep | Number | 40 | 50 | 层间距(px)。在rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级高于 ranksepFunc |
nodesepFunc3.1.6 后支持 | Function | d => { // d 是一个节点 if (d.id === 'node1') { return 100; } return 10;} | undefined | 节点水平间距(px)的回调函数,通过该参数可以对不同节点设置不同的节点间距。在rankdir 为 'TB' 或 'BT' 时是节点的水平间距;在rankdir 为 'LR' 或 'RL' 时是节点的竖直间距。优先级低于 nodesep ,即若设置了 nodesep ,则 nodesepFunc 不生效 |
ranksepFunc3.1.6 后支持 | Function | d => { // d 是一个节点 if (d.id === 'node1') { return 100; } return 10;} | undefined | 层间距(px)的回调函数,通过该参数可以对不同节点设置不同的层间距。在rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级低于 ranksep ,即若设置了 ranksep ,则 ranksepFunc 不生效 |
controlPoints | Boolean | true | true | 是否保留布局连线的控制点 |
Concentric
注:该算法参考 cytoscape.js,遵守 MIT 开源协议。描述:同心圆布局。API:Concentric API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
center | Array | [ 0, 0 ] | 图的中心 | 随机布局的中心 |
nodeSize | Number | 30 | 30 | 节点大小(直径)。用于防止节点重叠时的碰撞检测 |
minNodeSpacing | Number | 10 | 10 | 环与环之间最小间距,用于调整半径 |
preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 nodeSize ,只有设置了与当前图节点大小相同的 nodeSize 值,才能够进行节点重叠的碰撞检测。若未设置 nodeSize ,则将根据节点数据中的 size 进行碰撞检测。若二者都未设置,则默认以 30 为节点大小进行碰撞检测 |
sweep | Number | Math.PI | undefined | 第一个节点与最后一个节点之间的弧度差 |
equidistant | Boolean | false | false | 环与环之间的距离是否相等 |
startAngle | Number | 3.14 | 3 / 2 * Math.PI | 开始放置节点的弧度 |
clockwise | Boolean | false | false | 是否按照顺时针顺序 |
maxLevelDiff | Number | 0.5 | undefined | 每一层同心值的求和。若为 undefined,则将会被设置为 maxValue / 4 ,其中 maxValue 为最大的排序依据的属性值。例如,若 sortBy='degree',则 maxValue 为所有节点中度数最大的节点的度数 |
sortBy | String | 'degree' | 'property1' | 'weight' |
Grid
注:该算法参考 cytoscape.js,遵守 MIT 开源协议。描述:网格布局。API:Grid API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
begin | Array | [ 0, 0 ] | [ 0, 0 ] | 网格开始位置(左上角) |
preventOverlap | Boolean | false | false | 是否防止重叠,必须配合属性 nodeSize ,只有设置了与当前图节点大小相同的 nodeSize 值,才能够进行节点重叠的碰撞检测。若未设置 nodeSize ,则将根据节点数据中的 size 进行碰撞检测。若二者都未设置,则默认以 30 为节点大小进行碰撞检测 |
preventOverlapPadding | Number | 10 | 10 | 避免重叠时节点的间距 padding。preventOverlap 为 true 时生效 |
nodeSize | Number | 30 | 30 | 节点大小(直径)。用于防止节点重叠时的碰撞检测 |
condense | Boolean | false | false | 为 false 时表示利用所有可用画布空间,为 true 时表示利用最小的画布空间 |
rows | Number | 5 | undefined | 网格的行数,为 undefined 时算法根据节点数量、布局空间、cals(若指定)自动计算 |
cals | Number | 5 | undefined | 网格的列数,为 undefined 时算法根据节点数量、布局空间、rows(若指定)自动计算 |
sortBy | String | 'degree' | 'property1' | 'weight' |
树图 TreeGraph
由于树图特殊性,G6扩展出了 TreeGraph ,详细文档请见:TreeGraph API。树布局是一种能很好展示有一定层次结构数据的布局方式。推荐使用 G6.TreeGraph 实现。
配置树图布局
与一般图 Graph 配置方法相似,通过实例化图时配置 layout
属性设置树的布局,还可以通过 modes
属性为树配置 展开/收缩行为。以下代码声明了一个实例,定义了布局为从左到右结构的基础树图,并且定义了展开收缩行为。
const graph = new G6.TreeGraph({
container: 'mountNode',
modes: {
default: [
{
// 定义展开/收缩行为
type: 'collapse-expand',
},
'drag-canvas',
],
},
// 定义布局
layout: {
type: 'dendrogram', // 布局类型
direction: 'LR', // 自左至右布局,可选的有 H / V / LR / RL / TB / BT
nodeSep: 50, // 节点之间间距
rankSep: 100, // 每个层级之间的间距
},
});
树图布局方法
compactBox
描述:紧凑树布局。从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。API:CompactBox API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
direction | String | 'TB' / 'BT' / 'LR' / 'RL' / 'H' / 'V' | 'LR' | layout 的方向。- TB —— 根节点在上,往下布局- BT —— 根节点在下,往上布局 (左)TB。(右)BT。- LR —— 根节点在左,往右布局- RL —— 根节点在右,往左布局 (左)LR。(右)RL。- H —— 根节点在中间,水平对称布局- V —— 根节点在中间,垂直对称布局 > (左)H。(右)V。 |
getId | Function | (d) => { // d 是一个节点 return d.id;} | undefined | 节点 id 的回调函数 |
getHeight | Function | (d) => { // d 是一个节点 return 10;} | undefined | 节点高度的回调函数 |
getWidth | Function | (d) => { // d 是一个节点 return 20;} | undefined | 节点宽度的回调函数 |
getVGap | Function | (d) => { // d 是一个节点 return 100;} | undefined | 节点纵向间距的回调函数 |
getHGap | Function | (d) => {// d 是一个节点 return 50;} | undefined | 节点横向间距的回调函数 |
radial | Boolean | true | false | 是否按照辐射状布局。若 radial 为 true ,建议 direction 设置为 'LR' 或 'RL' : |
dendrogram
描述:生态树布局。不管数据的深度多少,总是叶节点对齐。不考虑节点大小,布局时将节点视为1个像素点。API:Dendrogram API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
direction | String | 'TB' / 'BT' / 'LR' / 'RL' / 'H' / 'V' | 'LR' | layout 的方向。- TB —— 根节点在上,往下布局- BT —— 根节点在下,往上布局> (左)TB。(右)BT。- LR —— 根节点在左,往右布局- RL —— 根节点在右,往左布局> (左)LR。(右)RL。- H —— 根节点在中间,水平对称布局- V —— 根节点在中间,垂直对称布局> (左)H。(右)V。 |
nodeSep | Number | 50 | 0 | 节点间距 |
rankSep | Number | 100 | 0 | 层与层之间的间距 |
radial | Boolean | true | false | 是否按照辐射状布局。若 radial 为 true ,建议 direction 设置为 'LR' 或 'RL' : |
indented
描述:缩进树布局。每个元素会占一行/一列。
API:Indented API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
direction | String | 'LR' / 'RL' / 'H' | 'LR' | layout 的方向。'LR' —— 根节点在左,往右布局(下图左)'RL' —— 根节点在右,往左布局(下图中)'H' —— 根节点在中间,水平对称布局(下图右) |
indent | Number | 80 | 20 | 列间间距 |
getHeight | Function | (d) => { // d 是一个节点 return 10;} | undefined | 节点高度的回调函数 |
getWidth | Function | (d) => { // d 是一个节点 return 20;} | undefined | 节点宽度的回调函数 |
getSide | Function | (d) => { // d 是一个节点 return 'left';} | undefined | 节点放置在根节点左侧或右侧的回调函数,仅对与根节点直接相连的节点有效,设置后将会影响被设置节点的所有子孙节点 |
mindmap
描述:脑图布局。深度相同的节点将会被放置在同一层,与 compactBox 不同的是,布局不会考虑节点的大小。API:Mindmap API参数:
参数名 | 类型 | 示例 / 可选值 | 默认值 | 说明 |
---|---|---|---|---|
direction | String | 'H' / 'V' | 'H' | layout 的方向。- H:horizontal(水平)—— 根节点的子节点分成两部分横向放置在根节点左右两侧- V:vertical (竖直)—— 将根节点的所有孩子纵向排列 |
getHeight | Function | (d) => { // d 是一个节点 return 10;} | undefined | 节点高度的回调函数 |
getWidth | Function | (d) => { // d 是一个节点 return 20;} | undefined | 节点宽度的回调函数 |
getVGap | Function | (d) => { // d 是一个节点 return 100;} | 18 | 节点纵向间距的回调函数 |
getHGap | Function | (d) => { // d 是一个节点 return 50;} | 18 | 节点横向间距的回调函数 |
getSide | String | Function | (d) => { // d 是一个节点 return 'left';} | 'right' |
布局的切换机制
G6 提供了两种关于布局的切换机制:
updateLayout(params)
:布局方法或参数的切换changeData()
:数据的切换
布局方法或参数切换
接口定义:
/**
* 更换布局或布局参数
* @param {String | object} cfg 新布局配置项
* 若 cfg 为 String 或含有 type 字段,且与之前的布局方法不同时将会更换布局
* 否则只是更新原有布局的参数
*/
updateLayout(cfg);
布局方法切换:若参数 cfg
为 String
或是含有 type
字段的对象,且与之前的布局方法名不同时将会更换布局。
布局参数切换:若参数 cfg
是对象且其中不含有 type
字段,或指定的布局方法名称与之前的布局方法相同,则保持原有布局方法,仅更新该布局的参数。
数据切换
接口定义:
/**
* 更改源数据,根据新数据重新渲染视图
* @param {object} data 源数据
* @return {object} this
*/
changeData(data);
切换示例
期待效果
初始化时使用默认 random 布局,2000 ms 后更换为允许节点重叠的 force 布局,4000 ms 后更换为不允许节点重叠的 force 布局,6000 ms 后更换数据为 data2
。
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tutorial Layout Demo</title>
</head>
<body>
<div id="mountNode"></div>
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.0/build/g6.js"></script>
<script src="https://gw.alipayobjects.com/os/antv/assets/lib/jquery-3.2.1.min.js"></script>
<script>
const data = {
nodes: [
{ id: '0', label: '0' },
{ id: '1', label: '1' },
{ id: '2', label: '2' },
{ id: '3', label: '3' },
{ id: '4', label: '4' }
], edges: [
{ source: '0', target: '1' },
{ source: '0', target: '2' },
{ source: '0', target: '3' },
{ source: '0', target: '4' },
{ source: '1', target: '2' },
{ source: '1', target: '3' }
]
};
const data2 = {
nodes: [
{ id: '0', label: '0' },
{ id: '1', label: '1' },
{ id: '2', label: '2' }
], edges: [
{ source: '0', target: '1' },
{ source: '0', target: '2' }
]
};
const graph = new G6.Graph({
container: 'mountNode', // String | HTMLElement,必须,容器 id 或容器本身
width: 300, // Number,必须,图的宽度
height: 300, // Number,必须,图的高度
animate: true // Boolean,可选,切换布局时是否使用动画过度
});
// 读取数据和渲染
graph.data(data);
graph.render();
// 2000 ms 后切换为允许节点重叠的 force 布局
setTimeout(() => {
graph.updateLayout('force'); // 参数为 String 代表布局名称
}, 8000);
// 4000 ms 后切换为不允许节点重叠且边长为 100 的 force 布局。
setTimeout(() => {
graph.updateLayout({
type: 'force', // 布局名称
preventOverlap: true, // 布局参数,是否允许重叠
nodeSize: 40, // 布局参数,节点大小,用于判断节点是否重叠
linkDistance: 100 // 布局参数,边长
});
}, 10000);
// 6000 ms 后切换数据为 data2
setTimeout(() => {
graph.changeData(data2);
}, 12000);
</script>
</body>
</html>
子图布局
目前,子图布局独立与全局布局的思路,与 graph 不挂钩,直接使用实例化布局方法的方式,灌入子图数据,通过布局将位置写到相应数据中。这种机制还可供外部的全局布局使用,即使不用 G6 渲染,也可以计算节点布局后的位置。但与萧庆讨论后,决定这种方式暂时不透出够用户。在子图布局上,这种机制后续需要修改,并与全局布局思路统一( graph,controller )。
使用方法
// 实例化布局
const subgraphLayout = new G6.Layout['force']({
center: [500, 450],
});
// 初始化布局,灌入子图数据
subgraphLayout.init({
nodes: subGraphNodes,
edges: subGraphEdges,
});
//执行布局
subgraphLayout.execute();
// 图实例根据数据更新节点位置
graph.positionsAnimate();