背景 / 需求
现有所有图(除树外)的 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;}});
