背景 / 需求
现有所有图(除树外)的 Layout 都作为插件接入 G6。插件是一种可扩展、可插拔、完全独立于 G6 的一部分。然而,G6 的定位是图可视化引擎,而不是图渲染引擎。图布局作为图可视化的基础,应当是 G6 原生支持的、不可或缺当一部分。
目标及边界
将大多数(业务中、学术上)常用、好用的 Layout 算法内置到 G6 当中。其中工作包括:
1. 现已在插件中的 Layout 的迁移;
2. 其他常用 Layout 的内置实现。
- Tree Layout
- Dagre
- Radial
- Dendrogram
General Graph Layout
一系列 Layout 在 G6 中与 Renderer 同等级;
- 支持 Layout 的注册、复写、扩展;
- 统一所有图(包括树)的布局及其流程规范;
-
用户
前端开发者
- Researcher
用户操作
- 创建布局
- 方法 1 :在 new Graph 时以配置方式创建
- 方法 2 :单独创建 Layout,独立于 G6
- 更新数据
- 不主动更新布局,调用才更新
- 更新布局配置
- 更新后主动更新布局
- 更换布局方法
- 更换后主动更新布局
数据流转
- 流入
- 使用在 new Graph 时以配置方式创建(方法1):根据 graph 上的数据进行布局
- 单独创建(方法2):单独调用时,根据传入参数 data 进行布局
- 返回:布局计算后不直接修改原数据模型,而是返回 positions 位置数组,交由 controller 决定更新方式
- 更新数据:传参
具体设计
类设计
接口设计
layout.js 布局基类
Layout.registerLayout = function(type, layout) {
if (!layout) {
throw new Error('please specify handler for this layout:' + type);
}
const base = function(cfg) {
const self = this;
Util.mix(self, self.getDefaultCfg(), cfg);
};
Util.augment(base, {
/**
* 初始化
*/
init() {
}
/**
* 执行布局,不改变原数据模型位置,只返回布局后但结果位置
*/
excute() {
return positions;
}
/**
* 更新布局配置,但不执行布局
*/
updateLayoutCfg(cfg) {
}
/**
* 更换数据
*/
changeData(data) {
self.set('data', data);
self.excute();
}
/**
* 销毁
*/
destroy() {
}
/**
* 定义自定义行为的默认参数,会与用户传入的参数进行合并
*/
getDefaultCfg() {}
}, layout);
Layout[type] = base;
};
layout controller: layout.js
class Layout {
constructor(graph) {
this.graph = graph;
const layout = graph.get('layout');
if (layout === undefined) {
if (graph.data[0].x === undefined) {
// 创建随机布局
const randomLayout = new Random();
this.set('layout', randomLayout);
} else { // 若未指定布局且数据中没有位置信息,则不进行布局,直接按照原数据坐标绘制。
return;
}
}
layout = _getLayout();
this._initLayout();
}
_initLayout() {
const layout = this.layout;
const graph = this.graph;
layout.init();
}
// 执行并绘制
refreshLayout() {
...
}
// 更新布局参数
updateLayoutCfg(cfg) {
...
}
// 更换布局
changeLayout(layout) {
...
}
// 控制布局动画
layoutAnimate() {
...
}
// 根据 type 创建 Layout 实例
_getLayout() {
...
}
destroy() {
const layout = this.layout;
layout.destroy();
}
}
Layout 流程 生命周期
创建:
方法 1(暴露给用户): 在 new Graph 时配置创建:
- new Graph 时若指定了内置 layout,则在 graph render 时调用 layout controller 的 refreshLayout();
- 若未指定布局,则不进行布局,直接按照原数据坐标绘制。
const graph = new G6.Graph({
container: 'mountNode',
width: 1000,
height: 800,
plugins: [ minimap ],
layout: {
type: 'force',
center: [500, 400],
nodeStrength: 1,
edgeStrength: 1,
preventOverlap: true,
nodeRadius: 10
}
});
方法 2 (不暴露,保留):单独使用
const layout = new Force({
center: [500, 400],
nodeStrength: 1,
edgeStrength: 1,
preventOverlap: true,
nodeRadius: 10,
data
);
forcelayout.excute();
update / refresh: graph.refreshLayout()
- 在外部更改节点位置(drag、换数据等)时,不主动重新布局,用户调用 graph.refreshLayout() 才会更新。
- destroy:用户主动调用销毁或graph销毁时 更换布局
扩展 / 自定义布局
G6.registerLayout("custom-layout", {
excute() {
// ...
return positions;
}
});