准备写关于x6的一个系列文章,大致有以下内容:
- X6 极速入门:手把手教你绘制第一个拓扑图
- 揭秘 X6 核心概念:Graph、Node、Edge 与 View
- 自定义节点与边
- X6 交互魔法:拖拽、连线、缩放、对齐线
- X6 布局之道:内置布局算法与自定义布局
- 基于 React + X6 构建可视化流程图编辑器
- X6 性能优化秘籍:应对大规模节点渲染的挑战
- X6源码分析
- 手写最小 X6(X6-mini)
一、 Graph
Graph
是 X6 的核心中的核心,它是所有元素的容器和管理者
1.1 核心职责
- 容器管理:负责在指定的 DOM 容器中渲染整个图谱。
- 事件中枢 :管理画布和其上所有元素的事件系统(点击、拖拽、缩放等)。
- 视图操控 :提供对画布的平移、缩放、滚动、裁切等视图操作。
- 元素管理 :负责所有节点和边的增、删、改、查(CRUD)。
- 渲染控制 :控制渲染时机(如
batchUpdate
批量更新)和渲染模式(SVG 或 HTML)。
1.2 代码示例
javascript
import { Graph } from '@antv/x6';
// 创建 Graph 实例是一切的开始
const graph = new Graph({
container: document.getElementById('container'),
width: 1000,
height: 800,
grid: true, // 显示网格
panning: true, // 启用画布拖拽
mousewheel: true, // 启用滚轮缩放
background: { color: '#F8F9FA' },
// 图的主要交互行为都通过 `graph.options` 配置
connecting: {
snap: true, // 连线时自动吸附到节点
allowEdge: false, // 不允许边连接边
allowBlank: false, // 不允许连接到画布空白处
},
});
// Graph 是数据管理器
// 1. 添加元素
const node = graph.addNode({ ... });
const edge = graph.addEdge({ ... });
// 2. 获取元素
graph.getNodes(); // 获取所有节点
graph.getCellById(node.id); // 根据ID获取元素(Cell是Node和Edge的基类)
// 3. 批量操作,性能关键!
graph.batchUpdate(() => {
for (let i = 0; i < 1000; i++) {
graph.addNode(createNode(i));
}
});
// 4. 视图控制
graph.zoom(0.8); // 缩放到 80%
graph.translate(100, 50); // 平移画布
graph.centerContent(); // 将所有内容居中显示
// 5. 事件监听
graph.on('node:click', ({ node }) => { // 监听节点点击事件
console.log(`Node ${node.id}被点击了!`);
});
graph.on('edge:connected', ({ edge }) => { // 监听连线成功事件
console.log('一条新的连接建立了!', edge);
});
二、 Node & Edge
Node
(节点)和 Edge
(边)继承自共同的基类 Cell
2.1 共同的基类:Cell
Cell
提供了所有图形元素最基础的属性和方法:
- id: 唯一标识符
- shape : 元素的形状(如
'rect'
,'circle'
,'edge'
),决定了其默认行为和渲染方式。 - data : 用于存储业务数据的万能字段,这是将视图与业务模型关联的关键
- zIndex: 控制元素的层级。
- visible: 显示/隐藏元素。
- attr : 最强大的属性,用于设置元素的样式(SVG 属性或 CSS)。
2.2 Node
节点通常代表一个实体
javascript
// 创建一个节点,本质是创建了一个数据模型
const node = graph.addNode({
id: 'server-1', // 强烈建议指定ID!
shape: 'rect', // 指定形状
x: 100,
y: 100,
width: 80,
height: 40,
label: 'Web Server', // 文本
// 业务数据:这是将图形与你的应用逻辑连接起来的桥梁
data: {
ip: '192.168.1.1',
status: 'online',
capacity: 0.8,
},
// attr: 精细化控制样式(覆盖shape的默认样式)
attrs: {
// 对应SVG的 <rect> 元素
body: {
fill: '#e6f7ff',
stroke: '#1890ff',
strokeWidth: 2,
rx: 4, // 圆角
ry: 4,
},
// 对应SVG的 <text> 元素
label: {
text: 'Web Server', // 会覆盖外层的label属性
fontSize: 12,
fill: '#333',
refX: '50%', // 文本定位技巧:相对节点中心
refY: '50%',
textAnchor: 'middle', // 文本锚点居中
textVerticalAnchor: 'middle',
},
},
});
// 后续更新节点
node.prop('attrs/body/fill', '#ffccc7'); // 更改填充色
node.prop('data/status', 'offline'); // 更新业务数据
// 或使用标准方法
node.attr('body/fill', '#ffccc7');
2.3 Edge
边代表节点之间的关系,如数据流、依赖、关联等。
javascript
const edge = graph.addEdge({
id: 'link-1',
shape: 'edge',
source: { cell: 'server-1' }, // 源节点ID
target: { cell: 'db-1' }, // 目标节点ID
// 边的业务数据
data: {
type: 'database_connection',
bandwidth: '1Gbps',
},
// 边的样式控制
attrs: {
// 对应SVG的 <path> 元素(线条本身)
line: {
sourceMarker: { name: 'circle' }, // 起点标记
targetMarker: { name: 'block' }, // 终点标记(箭头)
stroke: '#9254de',
strokeWidth: 1.5,
strokeDasharray: '5, 5', // 虚线
},
},
// 顶点(路径点),用于控制边的路径
vertices: [
{ x: 250, y: 100 },
],
// 标签(可以多个)
labels: [
{
attrs: { label: { text: '1Gbps' } },
position: 0.3, // 沿边位置,0-1
},
],
// 路由器(routing):决定路径如何路由(如:orth-正交,manhattan-曼哈顿)
router: { name: 'manhattan' },
// 连接器(connector):决定拐角样式(如:rounded-圆角)
connector: { name: 'rounded' },
});
// 动态更新边
edge.attr('line/stroke', '#ff4d4f'); // 线条变红
edge.prop('vertices', [{ x: 300, y: 150 }]); // 修改路径点
三、 View
它定义了如何将数据模型转换为屏幕上的实际元素。
3.1 为什么需要 View?
X6 内置了大量 Shape(如 rect
, circle
, edge
),每个 Shape 都对应一个默认的 View
。这个默认的 View
知道如何根据模型的 attrs
去设置 SVG 元素的属性。
当你需要超越内置形状 时,View
的作用就凸显出来了:
- 创建一个全新的、复杂的节点形状。
- 在节点内部实现复杂的交互(如一个可点击的关闭按钮)。
- 使用 HTML/DOM 来渲染节点内容(性能不如 SVG,但更灵活)。
3.2 自定义 View 示例:创建一个自定义节点
假设我们要创建一个带有关闭按钮的标签节点。
javascript
import { Shape } from '@antv/x6';
// 1. 继承 Shape 注册一个新的节点形状
Graph.registerNode('custom-tag', class extends Shape.Rect {
// 2. 定义默认属性(会合并到用户传入的attrs中)
getDefaults() {
return {
...super.getDefaults(),
markup: [ // 3. 定义节点的DOM结构(Markup)
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
{
tagName: 'path', // 一个"x"形的关闭图标
selector: 'closeButton',
attrs: {
d: 'M 5 5 L 13 13 M 5 13 L 13 5',
fill: 'none',
stroke: '#999',
strokeWidth: 1.5,
},
},
],
attrs: {
body: { ... },
label: { ... },
// 关闭按钮默认隐藏,鼠标悬浮时显示
closeButton: { visibility: 'hidden' },
},
};
}
// 4. 自定义事件监听
// 在View被初始化后调用(类似mounted)
postprocess() {
this.on('mouseenter', () => {
this.attr('closeButton', { visibility: 'visible' });
});
this.on('mouseleave', () => {
this.attr('closeButton', { visibility: 'hidden' });
});
// 监听关闭按钮的点击事件
this.on('cell:closeButton:click', (e) => {
e.stopPropagation(); // 阻止事件冒泡(避免触发cell:click)
this.remove(); // 删除自己这个节点
});
}
}, true);
// 使用自定义节点
const tagNode = graph.addNode({
shape: 'custom-tag', // 使用注册的形状名
x: 50,
y: 50,
width: 100,
height: 30,
label: 'Important',
attrs: {
body: { fill: '#ffd591', stroke: '#ffa940', rx: 4, ry: 4 },
label: { text: 'Important', fontSize: 12, fill: '#333', refX: 18 },
closeButton: {
// 定义按钮位置,相对于节点
transform: 'translate(85, 8)',
stroke: { type: 'palette', palette: 'primaryColor' }, // 可使用主题色
},
},
});
关系图
四大核心概念的关系:
graph TD
A[Graph
画布/舞台/管理器] -- 管理 --> B[Cell
元素基类] B -- 继承 --> C[Node
节点/实体] B -- 继承 --> D[Edge
边/关系] B -- 拥有 --> E[Data
业务数据] B -- 拥有 --> F[Attrs
样式属性] B -- 由...渲染 --> G[View
视图/渲染器] G -- 监听/响应 --> H[Events
用户交互事件] H -- 触发 --> A
画布/舞台/管理器] -- 管理 --> B[Cell
元素基类] B -- 继承 --> C[Node
节点/实体] B -- 继承 --> D[Edge
边/关系] B -- 拥有 --> E[Data
业务数据] B -- 拥有 --> F[Attrs
样式属性] B -- 由...渲染 --> G[View
视图/渲染器] G -- 监听/响应 --> H[Events
用户交互事件] H -- 触发 --> A
- Graph 是管理者 ,持有并管理所有 Cell(Node 和 Edge)。
- Cell 是数据模型 ,其
attrs
决定样式,data
承载业务含义。 - View 是渲染器 ,负责将 Cell 模型转化为屏幕上可见的图形,并处理交互。