自定义布局
阅读时间约 5 分钟
G6 中布局分为迭代布局
和非迭代布局
两种,迭代布局是指布局算法会迭代多次直到收敛,非迭代布局是指布局算法只执行一次。
实现一个非迭代布局相对简单,只需要实现 BaseLayout
中的 execute
方法即可,下面是一个简单的自定义布局实现:
import { BaseLayout } from '@antv/g6';import type { GraphData } from '@antv/g6';class DiagonalLayout extends BaseLayout {id = 'diagonal-layout';async execute(data: GraphData): Promise<GraphData> {const { nodes = [] } = data;return {nodes: nodes.map((node, index) => ({id: node.id,style: {x: 50 * index + 25,y: 50 * index + 25,},})),};}}
在上面的代码中,我们实现了一个简单的布局算法,将节点从左上角沿对角线排列。
(async () => {const { Graph, BaseLayout, register, ExtensionCategory } = window.g6;class DiagonalLayout extends BaseLayout {id = 'diagonal-layout';async execute(data) {const { nodes = [] } = data;return {nodes: nodes.map((node, index) => ({id: node.id,style: {x: 50 * index + 25,y: 50 * index + 25,},})),};}}register(ExtensionCategory.LAYOUT, 'diagonal', DiagonalLayout);const container = createContainer({ width: 200, height: 200 });const graph = new Graph({container,width: 200,height: 200,data: {nodes: [{ id: 'node-1' }, { id: 'node-2' }, { id: 'node-3' }, { id: 'node-4' }],},layout: {type: 'diagonal',},});await graph.render();return container;})();
execute
方法返回的是一个 GraphData 对象,里面仅需要包含元素的基本信息(如 id、source、target)以及布局新增的属性(如 x、y、边的控制点等),不需要包含其他无用的信息。
如果仅需要对节点进行布局,可以只返回节点信息,不需要返回边的信息。
迭代布局的实现同样需要继承 BaseLayout
,但是除 execute
外还需要实现 tick
和 stop
方法,tick
方法用于将布局迭代到指定轮次,stop
方法用于停止布局迭代。
此外,迭代布局中需要在每次迭代调用 options.onTick
以触发图的更新。
type onTick = (data: GraphData) => void;
下面是一个简单的迭代布局实现:
import { BaseLayout } from '@antv/g6';import type { GraphData, BaseLayoutOptions } from '@antv/g6';interface TickTockLayoutOptions extends BaseLayoutOptions {onTick: (data: GraphData) => void;}class TickTockLayout extends BaseLayout<TickTockLayoutOptions> {public id = 'custom-layout';private tickCount = 0;private data?: GraphData;private timer?: number;private resolve?: () => void;private promise?: Promise<void>;async execute(data: GraphData, options: TickTockLayoutOptions): Promise<GraphData> {const { onTick } = { ...this.options, ...options };this.tickCount = 0;this.data = data;this.promise = new Promise((resolve) => {this.resolve = resolve;});this.timer = window.setInterval(() => {onTick(this.simulateTick());if (this.tickCount === 10) this.stop();}, 200);await this.promise;return this.simulateTick();}simulateTick = () => {const x = this.tickCount++ % 2 === 0 ? 50 : 150;return {nodes: (this?.data?.nodes || []).map((node, index) => ({id: node.id,style: { x, y: (index + 1) * 30 },})),};};tick = () => {return this.simulateTick();};stop = () => {clearInterval(this.timer);this.resolve?.();};}
在这个例子中,我们实现了一个简单的迭代布局算法,每 200ms 将节点的 x 坐标在 50 和 150 之间切换,并按照节点顺序在 y 方向上排列。
(async () => {const { Graph, BaseLayout, register, ExtensionCategory } = window.g6;class TickTockLayout extends BaseLayout {id = 'tick-tock-layout';async execute(data, options) {const { onTick } = { ...this.options, ...options };this.tickCount = 0;this.data = data;this.promise = new Promise((resolve) => {this.resolve = resolve;});this.timer = window.setInterval(() => {onTick(this.simulateTick());if (this.tickCount === 10) this.stop();}, 200);await this.promise;return this.simulateTick();}simulateTick = () => {const x = this.tickCount++ % 2 === 0 ? 50 : 150;return {nodes: (this?.data?.nodes || []).map((node, index) => ({id: node.id,style: { x, y: (index + 1) * 30 },})),};};tick = () => {return this.simulateTick();};stop = () => {clearInterval(this.timer);this.resolve?.();};}register(ExtensionCategory.LAYOUT, 'tick-tock', TickTockLayout);const container = createContainer({ width: 200, height: 200 });const graph = new Graph({container,width: 200,height: 200,animation: true,data: {nodes: [{ id: 'node-1' }, { id: 'node-2' }, { id: 'node-3' }, { id: 'node-4' }, { id: 'node-5' }],},layout: {type: 'tick-tock',},});graph.render();return container;})();
通过 G6 提供的 register 方法注册即可,详见注册布局
可在 options.layout
中配置布局的类型和参数,详见配置布局