同心圆布局 Concentric
概述
同心圆布局是一种将节点根据某种排序规则分层,并以圆心为中心、沿圆周排列每层节点的布局方式。参考更多同心圆布局样例或源码。
使用场景
- 分层数据可视化,如权限控制结构、组织架构图等,中心是顶级角色,外圈为下级节点。
- 排序分析结果可视化,高重要度放中心,低重要度放外围,快速表达图中节点的相对影响力。
配置项
| 属性 | 描述 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 布局类型 | concentric |
- | ✓ |
| center | 圆形布局的中心位置,默认为当前容器的中心位置 | [number, number] | [number, number, number] | - | |
| clockwise | 是否按照顺时针排列 | boolean | false | |
| equidistant | 环与环之间的距离是否相等 | boolean | false | |
| width | 布局的宽度,默认使用容器宽度 | number | - | |
| height | 布局的高度,默认使用容器高度 | number | - | |
| sortBy | 指定排序的依据(节点属性名) 数值越高则该节点被放置得越中心。若为 degree,则会计算节点的度数,度数越高,节点将被放置得越中心 |
string | degree |
|
| maxLevelDiff | 同一层节点的最大属性差值 若为 undefined,则将会被设置为 maxValue / 4 ,其中 maxValue 为最大的排序依据的属性值。例如,若 sortBy 为 ‘degree’,则 maxValue 为所有节点中度数最大的节点的度数 |
number | undefined | |
| nodeSize | 节点大小(直径)。用于防止节点重叠时的碰撞检测 | number | number[] | ((nodeData: Node) => number) | 30 | |
| nodeSpacing | 环与环之间最小间距,用于调整半径 | number | number[] | ((node?: Node) => number) | 10 | |
| preventOverlap | 是否防止重叠 必须配合 nodeSize 属性或节点数据中的 data.size 属性,只有在数据中设置了 data.size 或在该布局中配置了与当前图节点大小相同的 nodeSize 值,才能够进行节点重叠的碰撞检测 |
boolean | false | |
| startAngle | 开始布局节点的弧度 | number | 3 / 2 * Math.PI | |
| sweep | 同一层中第一个节点与最后一个节点之间的弧度差 若为 undefined ,则将会被设置为 2 * Math.PI * (1 - 1 / |level.nodes|) ,其中 level.nodes 为该算法计算出的每一层的节点,|level.nodes| 代表该层节点数量 |
number | undefined |
代码示例
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
width: 500,
height: 250,
autoFit: 'view',
data: {
nodes: [
{ id: 'center', data: { label: '中心', level: 0 } },
{ id: 'level1-0', data: { label: 'L1-0', level: 1 } },
{ id: 'level1-1', data: { label: 'L1-1', level: 1 } },
{ id: 'level1-2', data: { label: 'L1-2', level: 1 } },
{ id: 'level1-3', data: { label: 'L1-3', level: 1 } },
{ id: 'level1-4', data: { label: 'L1-4', level: 1 } },
{ id: 'level1-5', data: { label: 'L1-5', level: 1 } },
{ id: 'level2-0', data: { label: 'L2-0', level: 2 } },
{ id: 'level2-1', data: { label: 'L2-1', level: 2 } },
{ id: 'level2-2', data: { label: 'L2-2', level: 2 } },
{ id: 'level2-3', data: { label: 'L2-3', level: 2 } },
{ id: 'level2-4', data: { label: 'L2-4', level: 2 } },
{ id: 'level2-5', data: { label: 'L2-5', level: 2 } },
{ id: 'level2-6', data: { label: 'L2-6', level: 2 } },
{ id: 'level2-7', data: { label: 'L2-7', level: 2 } },
{ id: 'level2-8', data: { label: 'L2-8', level: 2 } },
{ id: 'level2-9', data: { label: 'L2-9', level: 2 } },
{ id: 'level2-10', data: { label: 'L2-10', level: 2 } },
{ id: 'level2-11', data: { label: 'L2-11', level: 2 } },
],
edges: [
{ id: 'e-center-level1-0', source: 'center', target: 'level1-0' },
{ id: 'e-center-level1-1', source: 'center', target: 'level1-1' },
{ id: 'e-center-level1-2', source: 'center', target: 'level1-2' },
{ id: 'e-center-level1-3', source: 'center', target: 'level1-3' },
{ id: 'e-center-level1-4', source: 'center', target: 'level1-4' },
{ id: 'e-center-level1-5', source: 'center', target: 'level1-5' },
{ id: 'e-level1-0-level2-0', source: 'level1-0', target: 'level2-0' },
{ id: 'e-level1-0-level2-1', source: 'level1-0', target: 'level2-1' },
{ id: 'e-level1-1-level2-2', source: 'level1-1', target: 'level2-2' },
{ id: 'e-level1-1-level2-3', source: 'level1-1', target: 'level2-3' },
{ id: 'e-level1-2-level2-4', source: 'level1-2', target: 'level2-4' },
{ id: 'e-level1-2-level2-5', source: 'level1-2', target: 'level2-5' },
{ id: 'e-level1-3-level2-6', source: 'level1-3', target: 'level2-6' },
{ id: 'e-level1-3-level2-7', source: 'level1-3', target: 'level2-7' },
{ id: 'e-level1-4-level2-8', source: 'level1-4', target: 'level2-8' },
{ id: 'e-level1-4-level2-9', source: 'level1-4', target: 'level2-9' },
{ id: 'e-level1-5-level2-10', source: 'level1-5', target: 'level2-10' },
{ id: 'e-level1-5-level2-11', source: 'level1-5', target: 'level2-11' },
],
},
layout: {
type: 'concentric',
nodeSize: 32,
sortBy: 'degree',
preventOverlap: true,
},
behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element'],
animation: false,
});
graph.render();
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}展开查看完整代码
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
autoFit: 'view',
data: {
nodes: [
{ id: 'center', data: { label: '中心', level: 0 } },
{ id: 'level1-0', data: { label: 'L1-0', level: 1 } },
{ id: 'level1-1', data: { label: 'L1-1', level: 1 } },
{ id: 'level1-2', data: { label: 'L1-2', level: 1 } },
{ id: 'level1-3', data: { label: 'L1-3', level: 1 } },
{ id: 'level1-4', data: { label: 'L1-4', level: 1 } },
{ id: 'level1-5', data: { label: 'L1-5', level: 1 } },
{ id: 'level2-0', data: { label: 'L2-0', level: 2 } },
{ id: 'level2-1', data: { label: 'L2-1', level: 2 } },
{ id: 'level2-2', data: { label: 'L2-2', level: 2 } },
{ id: 'level2-3', data: { label: 'L2-3', level: 2 } },
{ id: 'level2-4', data: { label: 'L2-4', level: 2 } },
{ id: 'level2-5', data: { label: 'L2-5', level: 2 } },
{ id: 'level2-6', data: { label: 'L2-6', level: 2 } },
{ id: 'level2-7', data: { label: 'L2-7', level: 2 } },
{ id: 'level2-8', data: { label: 'L2-8', level: 2 } },
{ id: 'level2-9', data: { label: 'L2-9', level: 2 } },
{ id: 'level2-10', data: { label: 'L2-10', level: 2 } },
{ id: 'level2-11', data: { label: 'L2-11', level: 2 } },
],
edges: [
{ id: 'e-center-level1-0', source: 'center', target: 'level1-0' },
{ id: 'e-center-level1-1', source: 'center', target: 'level1-1' },
{ id: 'e-center-level1-2', source: 'center', target: 'level1-2' },
{ id: 'e-center-level1-3', source: 'center', target: 'level1-3' },
{ id: 'e-center-level1-4', source: 'center', target: 'level1-4' },
{ id: 'e-center-level1-5', source: 'center', target: 'level1-5' },
{ id: 'e-level1-0-level2-0', source: 'level1-0', target: 'level2-0' },
{ id: 'e-level1-0-level2-1', source: 'level1-0', target: 'level2-1' },
{ id: 'e-level1-1-level2-2', source: 'level1-1', target: 'level2-2' },
{ id: 'e-level1-1-level2-3', source: 'level1-1', target: 'level2-3' },
{ id: 'e-level1-2-level2-4', source: 'level1-2', target: 'level2-4' },
{ id: 'e-level1-2-level2-5', source: 'level1-2', target: 'level2-5' },
{ id: 'e-level1-3-level2-6', source: 'level1-3', target: 'level2-6' },
{ id: 'e-level1-3-level2-7', source: 'level1-3', target: 'level2-7' },
{ id: 'e-level1-4-level2-8', source: 'level1-4', target: 'level2-8' },
{ id: 'e-level1-4-level2-9', source: 'level1-4', target: 'level2-9' },
{ id: 'e-level1-5-level2-10', source: 'level1-5', target: 'level2-10' },
{ id: 'e-level1-5-level2-11', source: 'level1-5', target: 'level2-11' },
],
},
layout: {
type: 'concentric',
nodeSize: 32,
sortBy: 'degree',
preventOverlap: true,
},
behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element'],
animation: false,
});
graph.render();