
Antv X6 Editor
- 327 installs
- 454 repo stars
- Updated July 31, 2026
- antvis/chart-visualization-skills
Embed AntV X6 diagram editors for flowcharts, workflows, and node-based UIs with drag-drop, connectors, snapping, and serialization in web apps.
About
Guides implementation of AntV X6 diagram and flow editors in web frontends, covering custom nodes, connectors, drag-and-drop tooling, routing, and persistence for workflow or visual builder products.
- X6 canvas and stencil setup
- Custom shapes and ports
- Drag-drop and snap-to-grid
- Connector and edge routing rules
- Serialize and restore diagram state
Antv X6 Editor by the numbers
- 327 all-time installs (skills.sh)
- Ranked #722 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/antvis/chart-visualization-skills --skill antv-x6-editorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 327 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 31, 2026 |
| Repository | antvis/chart-visualization-skills ↗ |
What it does
Embed AntV X6 diagram editors for flowcharts, workflows, and node-based UIs with drag-drop, connectors, snapping, and serialization in web apps.
Files
X6 图编辑引擎代码生成技能
核心约束(必须遵守)
<!-- CONSTRAINTS:START -->
X6 3.x 关键约束(强制)
- `graph.render()` 不存在:X6 3.x 中
new Graph()/addNode/addEdge/fromJSON均自动渲染,代码中不得出现graph.render()。 - 不得声明 `container` 变量:运行环境会作为函数参数注入
container。Graph 初始化只使用字符串字面量container: 'container',禁止const/let/var container = ...,也禁止document.getElementById('container')。 - 使用插件方法前必须先 `graph.use(new Plugin(...))` 注册对应插件:
graph.toPNG / toSVG / toJPEG依赖Export;graph.select / unselect依赖Selection;graph.undo / redo依赖History;graph.copy / paste / cut依赖Clipboard;graph.bindKey依赖Keyboard。未注册插件时对应方法不存在。 - 自定义 shape 必须先注册再使用:
Graph.registerNode(name, def)/Graph.registerEdge(name, def)/Shape.HTML.register({ shape, ... })必须在首次addNode / addEdge之前完成。 - `@antv/x6` 仅导出 11 个插件类:
Clipboard、Dnd、Export、History、Keyboard、MiniMap、Scroller、Selection、Snapline、Stencil、Transform。mousewheel、embedding、panning、connecting、translating、interacting、background、grid是new Graph()的构造选项,不是插件,不得 import 同名类、不得graph.use(new XxxClass())。例:滚轮缩放写在 Graph 构造中mousewheel: { enabled: true, zoomAtMousePosition: true, modifiers: ['ctrl'] }。 - 节点/边动画使用 `cell.animate(keyframes, options)`(Web Animations API 风格):X6 3.x 没有 `node.transition(path, target, options)` 方法。源码中
transition仅作为node.translate(tx, ty, { transition })/node.rotate(deg, { transition })的 options 字段存在(boolean | KeyframeEffectOptions),并非独立方法。示例:
// 通用动画
node.animate(
{ fill: ['#fff', '#1890ff'], transform: ['scale(1)', 'scale(1.2)'] },
{ duration: 500, iterations: 1, fill: 'forwards' },
);
// 仅平移过渡
node.translate(120, 0, { transition: { duration: 500, easing: 'ease-in-out' } });多步属性变更可用 graph.startBatch('animate'); cell.attr(...); graph.stopBatch('animate'); 包装。
初始化规范
container参数必填,必须使用字符串形式container: 'container',运行时环境会自动解析为 DOM 元素- 必须设置背景色:
background: { color: '#F2F7FA' },所有画布都需要统一的浅蓝灰色背景 - 不要添加 `grid` 配置,除非用户明确要求显示网格
- 不要设置 `width` / `height`,除非用户明确指定画布尺寸;画布默认自适应容器大小
- 导入方式:
import { Graph } from '@antv/x6',仅导入实际使用到的类 - 禁止无条件导入
Shape:仅在使用Shape.HTML.register()等 Shape 静态方法时才导入Shape - 插件从
'@antv/x6'直接导入,如import { Graph, Selection, History } from '@antv/x6' - 禁止使用
@antv/x6-plugin-xxx独立包导入(已废弃) - 标准初始化模板:
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
background: { color: '#F2F7FA' },
});节点操作规范
- 优先使用 `graph.addNode()` 逐个添加节点,而非
graph.fromJSON()批量导入(除非用户明确要求批量加载数据) - 内置 shape:
'rect'、'circle'、'ellipse'、'polygon'、'polyline'、'path'、'text'、'text-block'、'image'、'html' - 节点样式通过
attrs配置,遵循 SVG 属性命名 - 节点位置通过
x、y设置(左上角坐标),尺寸通过width、height设置 - 默认节点样式(除非用户指定其他样式,所有节点统一使用此默认样式):
attrs: {
body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 },
}- 禁止在 attrs 中使用 CSS 属性名(如
background-color),必须用 SVG 属性(如fill)
边操作规范
- 使用
graph.addEdge({ source, target, ... })添加边 source/target可以是:节点实例、节点 ID 字符串、{ cell: node, port: 'portId' }对象、坐标{ x, y }- 边样式:
attrs: { line: { stroke, strokeWidth, strokeDasharray, targetMarker, sourceMarker } } - 默认边样式(除非用户指定其他样式):
attrs: { line: { stroke: '#8f8f8f', strokeWidth: 1 } } - 箭头:
targetMarker: 'classic'(经典箭头)、'block'、'circle'、'diamond' - 路由器:
router: 'orth'(正交)、'manhattan'、'metro'、'er' - 连接器:
connector: 'rounded'(圆角)、'smooth'(贝塞尔曲线)、'jumpover'
连接桩(Ports)规范
- 连接桩定义在节点配置的
ports字段中 - 端口组:
ports: { groups: { groupName: { position, attrs, ... } }, items: [{ id, group }] } - position 取值:
'top'、'bottom'、'left'、'right' - 端口是连线的锚点,设置
attrs: { circle: { magnet: true } }允许从端口拖出连线 - 必须设置 `magnet: true` 才能从该端口发起或接收连线
交互配置规范
- 连线交互在 Graph 配置中通过
connecting字段设置 connecting: { allowBlank: false, router: 'orth', connector: 'rounded', createEdge() {...} }- 节点移动限制:
translating: { restrict: true }或传函数限制区域 - 嵌入:
embedding: { enabled: true }允许节点拖入分组
插件使用规范
- 插件从
@antv/x6导入,通过graph.use(new Plugin(options))注册 - 可用插件:
Selection、Snapline、History、Clipboard、Keyboard、Scroller、MiniMap、Transform、Export、Stencil、Dnd - Selection:
graph.use(new Selection({ enabled: true, rubberband: true })) - Snapline:
graph.use(new Snapline({ enabled: true })) - History:
graph.use(new History({ enabled: true })) - Clipboard:
graph.use(new Clipboard({ enabled: true })) - Keyboard:
graph.use(new Keyboard({ enabled: true })) - Scroller:
graph.use(new Scroller({ enabled: true })) - MiniMap:
graph.use(new MiniMap({ enabled: true, container: minimapContainer })) - Transform:
graph.use(new Transform({ resizing: { enabled: true }, rotating: { enabled: true } })) - Export:
graph.use(new Export())(注册后可调用graph.toPNG()/graph.toSVG()) - 动态控制:
graph.enablePlugins('selection')/graph.disablePlugins('selection') - 禁止在 Graph 构造函数中直接传入
selecting、snapline等选项(3.x 不支持)
序列化规范
- 导出:
const data = graph.toJSON()返回{ cells: [...] }对象 - 导入:
graph.fromJSON(data)加载整个图数据 - 清空:
graph.clearCells()清除所有元素 - 禁止手动构造 cells 数组中的内部字段(如
zIndex、parent),应通过 API 操作
事件规范
- 节点事件:
graph.on('node:click', ({ node, e }) => {...}) - 边事件:
graph.on('edge:click', ({ edge, e }) => {...}) - 画布事件:
graph.on('blank:click', ({ e }) => {...}) - 变更事件:
graph.on('node:moved', ({ node }) => {...}) - 事件回调参数是对象,不是位置参数:
({ node, e })而非(node, e)
导入规范
- 所有使用到的类都必须出现在 import 语句中:如使用
Selection,必须import { Graph, Selection } from '@antv/x6' - 禁止使用
Graph.Selection、Graph.Keyboard等命名空间写法(不存在) - 禁止使用未导入的类:
new Selection(...)必须对应import { Selection } from '@antv/x6' - import 自检清单(强制,输出代码前必须逐行核对):对代码中每一个
new XxxYyy(...)调用,XxxYyy必须字面出现在第一行import { ..., XxxYyy } from '@antv/x6'的花括号内。常见漏写:Selection、Keyboard、History、Clipboard、Snapline、MiniMap、Transform、Scroller、Export、Stencil、Dnd、Shape(用到Shape.HTML.register时)。 - import 漏写为何会变成 `Illegal constructor` 等运行时错:评测/Playground 环境用 UMD 构建(
window.X6)执行代码,而不是真正的 ES Module。Selection、Keyboard等会根据 import 列表从window.X6解构出来;如果 import 漏写,标识符Selection会回退到 `window.Selection`(浏览器原生 Selection 接口),new Selection({...})会抛Failed to construct 'Selection': Illegal constructor。Keyboard/History等同理(会is not a constructor)。 - import 漏写的标准修法:把所有用到的插件类合并到同一行
import { Graph, Selection, Keyboard, History, ... } from '@antv/x6';,不要分多行 import,也不要遗漏。
节点工具(Tools)规范
- 添加工具:
node.addTools([{ name: 'button-remove', args: { x: 0, y: 0 } }])或graph.addTools(node, [...]) - 移除工具:
node.removeTools()或graph.removeTools(node) - 检查工具:
node.hasTools() - 禁止使用
node.hideTools()/node.showTools()(3.x 不存在此 API) - 悬停显示/隐藏工具的正确方式:
graph.on('node:mouseenter', ({ node }) => {
node.addTools([{ name: 'button-remove', args: { x: 0, y: 0 } }]);
});
graph.on('node:mouseleave', ({ node }) => {
node.removeTools();
});渐变色规范
- X6 attrs 中
fill支持渐变对象语法,禁止直接操作graph.defs或document.createElementNS创建 SVG 渐变 - 线性渐变正确写法:
attrs: {
body: {
fill: {
type: 'linearGradient',
stops: [
{ offset: '0%', color: '#0000ff' },
{ offset: '100%', color: '#00ff00' },
],
},
},
}代码输出规范
- 必须输出纯 JavaScript,禁止使用 TypeScript 语法(如
private、类型注解: string、as类型断言) - HTML 自定义节点使用
Shape.HTML.register({ shape, html, effect })注册自定义 shape,禁止使用class extends Node方式 - 禁止
Graph.registerHTMLComponent(name, factory)—— 这是 X6 2.x 旧 API,3.x 源码已无此方法。所有 HTML 节点统一通过Shape.HTML.register注册(详见references/core/x6-core-html-shape.md) effect数组指定哪些属性变化时触发重新渲染(如['data']);纯静态展示节点不要加 effect- HTML 节点正确写法:
import { Graph, Shape } from '@antv/x6';
Shape.HTML.register({
shape: 'my-html',
effect: ['data'],
html(node) {
const div = document.createElement('div');
div.style.width = '100%';
div.style.height = '100%';
div.innerHTML = node.getData().content || '';
return div;
},
});
const graph = new Graph({ container: 'container' });
graph.addNode({ shape: 'my-html', x: 100, y: 100, width: 200, height: 80, data: { content: '<div>Hello</div>' } });Stencil 插件规范
- Stencil 通过
graph.use(new Stencil({ target: graph, groups: [...] }))注册 - 注册后通过
graph.getPlugin('stencil')获取实例,将stencil.container挂载到 DOM - Stencil 内的节点模板使用
graph.createNode(...)创建(非graph.addNode),再通过stencil.load(nodes, groupName)加载
动态端口规范
- 使用
node.addPort()动态添加端口时,必须在节点初始化时预定义对应的 ports.groups - 如果没有预定义 group,端口无法正确定位,可能导致渲染异常
- 正确写法:
const node = graph.addNode({
...,
ports: {
groups: {
in: { position: 'left', attrs: { circle: { r: 4, magnet: true, stroke: '#8f8f8f', fill: '#fff' } } },
out: { position: 'right', attrs: { circle: { r: 4, magnet: true, stroke: '#8f8f8f', fill: '#fff' } } },
},
},
});
node.addPort({ id: 'port1', group: 'out' });- `registerNode` + `addNode` 的 ports 合并陷阱(强约束):
Graph.registerNode(name, { ports: { items: [{ id: 'in1', group: 'in' }] } })之后,如果再graph.addNode({ shape: name, ports: { items: [{ id: 'in1', group: 'in' }] } }),X6 内部Cell构造时ObjectExt.merge(defaults, metadata)会按数组下标合并、node.addPorts走的也是[...current, ...new]简单拼接,不会去重,运行时直接抛Error: Duplicitied port id.。正确做法二选一: - 在
registerNode里只声明ports.groups,把ports.items留给addNode/ 后续node.addPort提供; - 或在
registerNode里完整声明ports.items,addNode时不再传 `ports.items`(如需追加端口,调用node.addPort({ id: '新id', group: 'xxx' }),且新 id 不能与 registry 里已声明的重名)。
DOM/CSS 操作规范(HTML 节点 / Stencil / 自定义工具)
- HTML 节点
html(node)回调里给 DOM 设样式时,禁止直接写连字符属性:el.style.box-sizing = '...'、el.style.font-size = '...'会被 JS 解析为el.style.box - sizing = ...,抛Invalid left-hand side in assignment。正确写法二选一: - 驼峰:
el.style.boxSizing = 'border-box'、el.style.fontSize = '14px'、el.style.backgroundColor = '#fff'; - 方括号:
el.style['box-sizing'] = 'border-box'、el.style['font-size'] = '14px'; - 多条样式优先用
el.style.cssText = 'box-sizing:border-box;font-size:14px;'或Object.assign(el.style, { boxSizing: 'border-box', fontSize: '14px' })。 - 同理,
el.classList.add('...')/el.setAttribute('data-x', '...')是合法 API;禁止el.class = '...'/el['class-name'] = ...。
不存在的 API(禁止使用)
- 禁止
graph.scrollToCell()→ 正确方式:graph.centerCell(cell)滚动并居中到指定 cell - 禁止
graph.highlightCell()/graph.highlightNode()→ 正确方式:通过node.attr('body/stroke', '#f00')或添加 CSS class 实现高亮 - 禁止
Shape.Cylinder/Shape.Diamond等不存在的内置 Shape → 用'rect'+rx/ry或'polygon'自定义 - 禁止
Shape.Edge.define()/Shape.Node.define()→ 正确方式:Graph.registerEdge()/Graph.registerNode() - 禁止
Shape.Group/Shape.Group.define()/new Shape.Group()→ X6 3.x 的Shape命名空间没有Group导出(实际只有Circle / Edge / Ellipse / HTML / Image / Path / Polygon / Polyline / Rect / TextBlock,运行时会报Cannot read properties of undefined (reading 'define'))。父子分组的正确方式:直接graph.addNode({ shape: 'rect', ... })创建一个普通节点作为父节点,再通过parent.addChild(child)/parent.embed(child)建立父子关系;或用Graph.registerNode('my-group', { inherit: 'rect', markup: [...], attrs: {...} })注册一个自定义分组形状再addNode({ shape: 'my-group' })。 - 禁止 把
Embedding当插件 import / new /graph.use(new Embedding(...))→ X6 3.x 没有Embedding插件类(运行时会报Embedding is not a constructor)。节点嵌入是 Graph 构造选项:new Graph({ container, embedding: { enabled: true, findParent: 'bbox', frontOnly: false, validate: ({ child, parent }) => true } })。Hover 高亮通过highlighting.embedding配置,嵌入/解除嵌入事件为node:embedding/node:embedded。 - 禁止
history.batch()→ 正确方式:graph.startBatch('custom'); ...; graph.stopBatch('custom');或graph.batchUpdate(() => { ... }) - 禁止
graph.defs/graph.svgDoc/document.createElementNS('...', 'linearGradient' | 'defs' | 'marker')→ X6 3.x 不暴露graph.defs/graph.svgDoc,运行时会报Cannot read properties of undefined。正确方式: - 节点/边普通
fill渐变:直接用 attrs 中的渐变对象语法(fill: { type: 'linearGradient', stops: [...] }) - 自定义 marker 需要渐变填充:先
const id = graph.defineGradient({ type: 'linearGradient', stops: [{ offset: 0, color: '#f00' }, { offset: 1, color: '#0f0' }] }),再在 marker 对象里写fill: \url(#${id})\`` - 自定义 marker / filter 同理使用
graph.defineMarker(options)/graph.defineFilter(options)
渲染输出规范(必须遵守)
- 画布初始化后必须存在至少一个 `graph.addNode` / `graph.addEdge` / `graph.fromJSON` 调用,确保画布有可视内容。即使用户 query 只描述了交互(panning / mousewheel / 插件等)配置,也必须自行补 2~3 个示例节点 + 1 条边作为渲染载体,否则视觉验证会被判定为「白屏」。
- 所有节点/边添加完成后,必须在末尾调用 `graph.centerContent()`(或在画布需要随内容缩放时使用
graph.zoomToFit({ padding: 20, maxScale: 1 }))。X6 默认不会自动居中,缺失该调用会导致内容偏向左上角、视觉评分不通过。两者二选一,不可同时调用。 - 多个交互(
panning+mousewheel+Selectionrubberband)同时启用时,必须用 `modifiers` 错开触发条件(例如:panning 用'shift',mousewheel 用'ctrl',rubberband 留空)。禁止把'mouseWheel'放进panning.eventTypes同时又启用mousewheel,两者会争抢滚轮事件。
<!-- CONSTRAINTS:END -->
---
禁止的错误模式
❌ 使用已废弃的独立插件包
// 错误:独立插件包已废弃
import { Selection } from '@antv/x6-plugin-selection';
import { History } from '@antv/x6-plugin-history';
// 正确:从 @antv/x6 直接导入
import { Graph, Selection, History } from '@antv/x6';
const graph = new Graph({ container: 'container' });
graph.use(new Selection({ enabled: true, rubberband: true }));
graph.use(new History({ enabled: true }));❌ 在构造函数中传入插件选项
// 错误:3.x 不支持构造函数选项模式
const graph = new Graph({
container: 'container',
selecting: { enabled: true }, // ❌
snapline: { enabled: true }, // ❌
history: { enabled: true }, // ❌
});
// 正确:使用 graph.use() 注册插件
import { Graph, Selection, Snapline, History } from '@antv/x6';
const graph = new Graph({ container: 'container' });
graph.use(new Selection({ enabled: true }));
graph.use(new Snapline({ enabled: true }));
graph.use(new History({ enabled: true }));❌ 混淆 CSS 属性和 SVG 属性
// 错误:使用 CSS 属性名
attrs: {
body: {
'background-color': '#fff', // ❌
'border-radius': '6px', // ❌
}
}
// 正确:使用 SVG 属性名
attrs: {
body: {
fill: '#fff', // ✅ 背景色
rx: 6, // ✅ 圆角
ry: 6,
stroke: '#8f8f8f', // ✅ 边框色
strokeWidth: 1, // ✅ 边框宽度
}
}❌ 缺少 container
// 错误:遗漏 container
const graph = new Graph({});
// 正确:container 必填
const graph = new Graph({ container: 'container' });❌ 连接桩未设置 magnet
// 错误:端口无法连线
ports: {
items: [{ id: 'port1', group: 'out' }],
groups: {
out: { position: 'right', attrs: { circle: { r: 5 } } }
}
}
// 正确:设置 magnet: true
ports: {
items: [{ id: 'port1', group: 'out' }],
groups: {
out: { position: 'right', attrs: { circle: { r: 5, magnet: true, stroke: '#8f8f8f' } } }
}
}❌ 事件回调使用位置参数
// 错误:参数不是位置传递
graph.on('node:click', (node, e) => { ... });
// 正确:解构对象参数
graph.on('node:click', ({ node, e }) => { ... });---
基础结构模板
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
background: { color: '#F2F7FA' },
});
const source = graph.addNode({
shape: 'rect',
x: 40,
y: 40,
width: 100,
height: 40,
label: 'Source',
attrs: {
body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 },
},
});
const target = graph.addNode({
shape: 'rect',
x: 300,
y: 200,
width: 100,
height: 40,
label: 'Target',
attrs: {
body: { stroke: '#8f8f8f', strokeWidth: 1, fill: '#fff', rx: 6, ry: 6 },
},
});
graph.addEdge({
source,
target,
attrs: {
line: { stroke: '#8f8f8f', strokeWidth: 1 },
},
});
// 内容居中:所有节点/边添加完成后调用,使画布内容相对于容器居中显示
// 如需缩放以适应容器,使用 graph.zoomToFit({ padding: 20, maxScale: 1 }) 替代
graph.centerContent();---
场景选择指南
| 场景 | 推荐配置 | 关键特性 |
|---|---|---|
| DAG 数据管道 | ports + orth router + connecting | 有向无环、端口连线 |
| ER 实体关系图 | HTML 节点 + er router | 表格式节点、字段展示 |
| 流程图/审批流 | 菱形判断节点 + 分支边 | 条件分支、多路径 |
| 组织架构图 | orth router + 树形布局 | 层级关系、折叠 |
| 血缘分析 | 左右布局 + smooth connector | 多层流转、端口 |
| 网络拓扑 | 圆形节点 + 星型结构 | 设备类型、连接状态 |
| 状态机 | 圆形节点 + 边标签 | 状态转换、事件触发 |
---
内置节点类型
| shape | 形状 | 适用场景 |
|---|---|---|
rect | 矩形 | 通用节点、流程步骤 |
circle | 圆形 | 状态节点、端点 |
ellipse | 椭圆 | 通用强调 |
polygon | 多边形 | 菱形(判断)、六边形 |
text | 纯文本 | 标注、注释 |
image | 图片 | 图标节点 |
html | HTML | 富文本、表格式节点 |
---
路由器与连接器
路由器(Router)— 决定边的路径走向
| 类型 | 效果 | 适用场景 |
|---|---|---|
normal | 直线(默认) | 简单图 |
orth | 正交折线 | 流程图、DAG |
manhattan | 智能正交(绕障) | 复杂布局 |
metro | 地铁线风格 | 地铁图 |
er | ER 图专用 | 实体关系图 |
连接器(Connector)— 决定边的线条样式
| 类型 | 效果 | 适用场景 |
|---|---|---|
normal | 直线段(默认) | 简单图 |
rounded | 圆角折线 | 流程图(推荐) |
smooth | 贝塞尔曲线 | 血缘图、关系图 |
jumpover | 跨线跳跃 | 复杂交叉 |
---
插件速查
| 插件 | 注册方式 | 功能 |
|---|---|---|
| Selection | graph.use(new Selection({ enabled: true, rubberband: true })) | 框选节点 |
| Snapline | graph.use(new Snapline({ enabled: true })) | 对齐辅助线 |
| History | graph.use(new History({ enabled: true })) | 撤销/重做 |
| Clipboard | graph.use(new Clipboard({ enabled: true })) | 复制/粘贴 |
| Keyboard | graph.use(new Keyboard({ enabled: true })) | 快捷键绑定 |
| Scroller | graph.use(new Scroller({ enabled: true })) | 滚动画布 |
| MiniMap | graph.use(new MiniMap({ enabled: true, container })) | 小地图导航 |
| Transform | graph.use(new Transform({ resizing: { enabled: true }, rotating: { enabled: true } })) | 节点缩放/旋转 |
| Export | graph.use(new Export()) | 导出 PNG/SVG |
| Stencil | graph.use(new Stencil({ target: graph, groups: [...] })) | 侧边栏拖拽面板 |
| Dnd | graph.use(new Dnd({ target: graph })) | 拖拽创建节点 |
核心概念
Anchor(锚点) 决定连线端点在目标元素上的参考位置。X6 中有两类锚点:
- nodeAnchor:边连接到节点时的锚点位置
- edgeAnchor:边连接到另一条边时的锚点位置
锚点与 connectionPoint 配合使用:anchor 确定参考点,connectionPoint 确定最终连接位置(通常是 anchor 到节点边界的交点)。
节点锚点(Node Anchor)
配置方式
在边的 source / target 中通过 anchor 字段设置:
graph.addEdge({
source: { cell: node1, anchor: 'center' },
target: { cell: node2, anchor: { name: 'midSide', args: { direction: 'H' } } },
});也可在 Graph 的 connecting 中设置全局默认:
const graph = new Graph({
container: 'container',
connecting: {
anchor: 'center', // 全局默认节点锚点
},
});内置节点锚点
| 名称 | 说明 | 参数 |
|---|---|---|
center | 节点 BBox 中心(默认值) | dx, dy, rotate |
top | 节点顶部中心 | dx, dy, rotate |
bottom | 节点底部中心 | dx, dy, rotate |
left | 节点左侧中心 | dx, dy, rotate |
right | 节点右侧中心 | dx, dy, rotate |
topLeft | 节点左上角 | dx, dy, rotate |
topRight | 节点右上角 | dx, dy, rotate |
bottomLeft | 节点左下角 | dx, dy, rotate |
bottomRight | 节点右下角 | dx, dy, rotate |
midSide | 距离对端最近的一侧中点 | direction, padding, rotate |
orth | 正交锚点,使连线保持正交 | padding |
nodeCenter | 节点实际中心(非 magnet BBox) | dx, dy |
BBox 锚点参数
center、top、bottom、left、right、topLeft、topRight、bottomLeft、bottomRight 共享参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dx | `number \ | string` | 0 |
dy | `number \ | string` | 0 |
rotate | boolean | false | 是否跟随节点旋转 |
midSide 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
direction | `'H' \ | 'V'` | 无 |
padding | number | 无 | BBox 膨胀值 |
rotate | boolean | false | 是否跟随节点旋转 |
orth 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
padding | number | 0 | 距 BBox 边界的内边距 |
边锚点(Edge Anchor)
当一条边连接到另一条边时使用。
内置边锚点
| 名称 | 说明 | 参数 |
|---|---|---|
ratio | 边路径上按比例定位(默认值) | ratio |
length | 边路径上按长度定位 | length |
closest | 边路径上距对端最近的点 | 无 |
orth | 正交锚点,从对端画正交线与边路径的交点 | fallbackAt |
ratio 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ratio | number | 0.5 | 位置比例,0~1 之间;大于 1 时自动除以 100 |
length 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
length | number | 20 | 从路径起点算起的长度(像素) |
orth 参数(边锚点)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
fallbackAt | `number \ | string` | 无 |
orth 边锚点会从对端参考点画水平线和垂直线,取与边路径的交点中最近的一个。若无交点则使用 fallbackAt 指定的回退位置,若 fallbackAt 也未指定则退化为 closest。
完整示例
使用 midSide 实现自动侧边连线
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
connecting: {
anchor: { name: 'midSide', args: { direction: 'H' } },
connectionPoint: 'boundary',
router: 'orth',
connector: 'rounded',
},
});
const node1 = graph.addNode({
shape: 'rect',
x: 100,
y: 100,
width: 120,
height: 60,
label: '开始',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 6, ry: 6 } },
});
const node2 = graph.addNode({
shape: 'rect',
x: 400,
y: 250,
width: 120,
height: 60,
label: '结束',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 6, ry: 6 } },
});
// midSide 自动选择离对端最近的一侧
graph.addEdge({
source: node1,
target: node2,
attrs: { line: { stroke: '#8f8f8f', targetMarker: 'classic' } },
});单独指定 source/target 锚点
graph.addEdge({
source: { cell: node1, anchor: 'right' },
target: { cell: node2, anchor: { name: 'left', args: { dy: 10 } } },
attrs: { line: { stroke: '#8f8f8f', targetMarker: 'classic' } },
});边连接到边
const edge1 = graph.addEdge({
source: node1,
target: node2,
});
// edge2 连接到 edge1 的中点
graph.addEdge({
source: node3,
target: { cell: edge1, anchor: { name: 'ratio', args: { ratio: 0.5 } } },
attrs: { line: { stroke: '#f5222d', targetMarker: 'classic' } },
});常见错误
❌ 混淆 anchor 与 connectionPoint
// 错误:anchor 不决定最终连接位置,它只是参考点
graph.addEdge({
source: { cell: node1, anchor: 'boundary' }, // ❌ boundary 是 connectionPoint,不是 anchor
target: node2,
});
// 正确:anchor 设置参考位置,connectionPoint 决定边界交点
graph.addEdge({
source: { cell: node1, anchor: 'center', connectionPoint: 'boundary' },
target: node2,
});❌ 字符串简写与对象格式混用错误
// 正确的两种写法
anchor: 'center' // 字符串简写
anchor: { name: 'midSide', args: { direction: 'H' } } // 对象格式(带参数时)X6 动画与过渡
X6 的 animate API 基于 Web Animations API 标准实现,提供强大的动画能力。
基本用法
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 400,
background: { color: '#F2F7FA' },
});
const node = graph.addNode({
shape: 'rect',
x: 100,
y: 140,
width: 100,
height: 50,
label: 'Hello X6',
attrs: { body: { strokeWidth: 1, rx: 6, ry: 6 } },
});
// 添加位置动画:节点从当前位置移动到 x=300
node.animate(
{ 'position/x': 300 },
{ duration: 1000, direction: 'alternate', iterations: Infinity },
);animate API 参数
cell.animate(keyframes, options);keyframes — 关键帧
指定动画属性及其目标值。属性路径使用 / 分隔,基于 cell.setPropByPath() 实现。
// 单一目标值(从当前值动画到目标值)
node.animate({ 'position/x': 300 }, { duration: 1000 });
// 数组形式(指定起始值和目标值)
node.animate({ 'position/x': [100, 300] }, { duration: 1000 });
// 多个属性同时动画
node.animate(
{ 'position/x': 300, 'position/y': 200 },
{ duration: 1000 },
);常用属性路径
| 路径 | 说明 |
|---|---|
position/x | 节点 X 坐标 |
position/y | 节点 Y 坐标 |
size/width | 节点宽度 |
size/height | 节点高度 |
attrs/body/fill | 节点填充色 |
attrs/body/opacity | 节点透明度 |
data/xxx | 自定义数据属性(用于 HTML 节点) |
options — 动画配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
duration | number | - | 动画持续时间(毫秒) |
delay | number | 0 | 延迟开始(毫秒) |
direction | string | 'normal' | 'normal'/'reverse'/'alternate'/'alternate-reverse' |
iterations | number | 1 | 重复次数,Infinity 表示无限 |
easing | string | 'linear' | 缓动函数,如 'ease'、'ease-in-out' |
fill | string | 'none' | 动画结束后状态:'forwards'/'backwards'/'both'/'none' |
配置式动画
直接在节点配置中声明动画,节点添加到画布后自动播放:
graph.addNode({
shape: 'rect',
x: 100,
y: 140,
width: 100,
height: 50,
label: 'Hello X6',
attrs: { body: { strokeWidth: 1, rx: 6, ry: 6 } },
// 配置式动画:数组中每项对应一个 animate 调用
animation: [
[
{ 'position/x': 300 },
{ duration: 1000, direction: 'alternate', iterations: Infinity },
],
],
});animation 是一个数组,每一项为 [keyframes, options] 元组,节点添加到画布后自动开始播放。
注册动画 Shape
为一批节点复用相同的动画效果:
import { Graph } from '@antv/x6';
Graph.registerNode(
'animated-rect',
{
inherit: 'rect',
width: 150,
height: 60,
attrs: {
body: { strokeWidth: 1, rx: 6, ry: 6 },
},
animation: [
[
{ 'position/x': 300 },
{ duration: 1000, direction: 'alternate', iterations: Infinity },
],
],
},
true,
);
// 所有使用 animated-rect 的节点都自动拥有动画
graph.addNode({ shape: 'animated-rect', x: 100, y: 50, label: 'Node 1' });
graph.addNode({ shape: 'animated-rect', x: 100, y: 150, label: 'Node 2' });自定义属性动画(HTML 节点)
对 data 中的自定义属性添加动画,配合 HTML 节点实现复杂效果:
import { Graph, Shape, Dom } from '@antv/x6';
Shape.HTML.register({
shape: 'progress-node',
width: 160,
height: 40,
effect: ['data'],
html(cell) {
const { progress } = cell.getData() ?? { progress: 0 };
const div = document.createElement('div');
Dom.css(div, {
width: '100%',
height: '100%',
background: `linear-gradient(to right, #1890ff ${progress * 100}%, #f0f0f0 ${progress * 100}%)`,
borderRadius: '4px',
border: '1px solid #d9d9d9',
});
return div;
},
});
const node = graph.addNode({
shape: 'progress-node',
x: 100,
y: 100,
data: { progress: 0 },
});
// 对 data/progress 添加动画
node.animate(
{ 'data/progress': 1 },
{ duration: 2000, fill: 'forwards' },
);动画控制
animate 返回一个动画对象,支持控制操作:
const animation = node.animate(
{ 'position/x': [100, 300] },
{ duration: 2000, iterations: Infinity },
);
// 暂停
animation.pause();
// 恢复播放
animation.play();
// 取消动画(恢复到初始状态)
animation.cancel();
// 立即结束动画(跳到最终状态)
animation.finish();
// 反向播放
animation.reverse();
// 更新播放速度(2倍速)
animation.updatePlaybackRate(2);获取节点上的所有动画
const animations = node.getAnimations(); // Animation[]
animations.forEach((anim) => anim.pause());动画事件
方式一:Web Animations API 风格
const animation = node.animate(
{ 'position/x': [100, 300] },
{ duration: 1000, iterations: 1 },
);
animation.onfinish = () => {
console.log('动画结束');
};
animation.oncancel = () => {
console.log('动画被取消');
};方式二:X6 事件系统
// 监听所有节点动画结束
graph.on('node:animation:finish', ({ node }) => {
console.log('Node animation finished:', node.id);
});
// 监听所有 cell 动画取消
graph.on('cell:animation:cancel', ({ cell }) => {
console.log('Animation cancelled:', cell.id);
});支持的事件:
cell:animation:finish— 动画结束cell:animation:cancel— 动画取消node:animation:finish— 节点动画结束node:animation:cancel— 节点动画取消edge:animation:finish— 边动画结束edge:animation:cancel— 边动画取消
translate / rotate 的内置过渡选项
⚠️ X6 3.x 不存在cell.transition(path, target, options)方法。常被误传的 "transition 方法"其实是node.translate()/node.rotate()等位置变换方法上的一个布尔/对象 options 字段,底层仍走animate。
真实 API(核对自 model/node.ts)
node.translate(tx: number, ty: number, options?: {
transition?: boolean | KeyframeEffectOptions // ← 这里才是 transition
restrict?: RectangleLike | null
exclude?: Cell[]
// ...
})当 options.transition 为 true 或对象时,X6 内部会自动调用一次 `node.animate({'position/x', 'position/y'}, animateOptions)`,默认 { duration: 100, fill: 'forwards' }。
用法示例
// 形式一:transition: true,使用默认动画参数(duration 100ms,fill forwards)
node.translate(200, 100, { transition: true });
// 形式二:transition: KeyframeEffectOptions,自定义动画参数
node.translate(200, 100, {
transition: { duration: 800, easing: 'ease-in-out', fill: 'forwards' },
});与 node.animate 的关系
| 方式 | 适用场景 |
|---|---|
node.translate(tx, ty, { transition }) | 仅做位置平移,且希望平移本身带过渡动画 |
node.animate({ 'position/x', 'position/y' }, options) | 任意属性、任意关键帧、需要拿到 animation 句柄做 pause/play/cancel |
配置式 animation: [[keyframes, options]] | 节点添加到画布后自动开始的常驻动画 |
translate({ transition }) 只是 animate 的一个语义糖,任何更复杂的动画都必须用 `animate`。
常见错误与修正
❌ 属性路径写法错误
// 错误:直接用 x 作为属性名
node.animate({ x: 300 }, { duration: 1000 });
// 正确:使用属性路径 position/x
node.animate({ 'position/x': 300 }, { duration: 1000 });❌ 动画结束后节点回到原位
// 错误:默认 fill='none',动画结束后属性恢复
node.animate({ 'position/x': 300 }, { duration: 1000 });
// 正确:设置 fill='forwards' 保持结束状态
node.animate({ 'position/x': 300 }, { duration: 1000, fill: 'forwards' });❌ 误用不存在的 node.transition(path, target, options) 方法
// 错误:X6 3.x 中 cell.transition(path, target, options) 不存在
// 运行时会报:node.transition is not a function
node.transition('position', { x: 300, y: 200 }, { duration: 1000 });
// 正确(位置过渡):用 translate + transition 选项
node.translate(300 - node.position().x, 200 - node.position().y, {
transition: { duration: 1000, easing: 'ease-in-out', fill: 'forwards' },
});
// 正确(通用过渡):用 animate
node.animate(
{ 'position/x': 300, 'position/y': 200 },
{ duration: 1000, easing: 'ease-in-out', fill: 'forwards' },
);❌ 容器选择器错误
// 错误:直接传入 DOM 元素变量(评测/Playground 环境中 container 由运行环境注入)
const container = document.getElementById('container');
const graph = new Graph({ container });
// 正确:直接使用字符串字面量 'container'
const graph = new Graph({ container: 'container' });❌ 用 complete 回调监听动画结束
// 错误:X6 / Web Animations API 都没有 complete 回调
node.animate({ 'position/x': 300 }, {
duration: 1000,
complete: () => console.log('done'),
});
// 正确:监听返回的 Animation 对象的 onfinish
const animation = node.animate(
{ 'position/x': 300 },
{ duration: 1000, fill: 'forwards' },
);
animation.onfinish = () => console.log('done');
// 或者监听 graph 事件(适合多节点场景)
graph.on('node:animation:finish', ({ node }) => {
console.log('Node animation finished:', node.id);
});自定义属性注册(Attr Registry)
概述
X6 通过属性注册表(Attr Registry)管理所有 attrs 中可使用的特殊属性。除了标准 SVG 属性(如 fill、stroke)会直接设置到 DOM 元素上外,X6 还内置了一系列高级属性(如 refX、refWidth、connection 等),并支持用户自定义注册新属性。
内置特殊属性
相对定位属性(ref 系列)
基于参考元素(通常是节点 body)的 BBox 进行相对定位和尺寸计算:
| 属性 | 说明 | 值范围 |
|---|---|---|
ref | 指定参考元素的选择器 | CSS 选择器字符串 |
refX | 相对 X 坐标 | 0~1 为百分比,其他为绝对偏移 |
refY | 相对 Y 坐标 | 同上 |
refDx | 相对于参考元素右侧的 X 偏移 | 像素值 |
refDy | 相对于参考元素底部的 Y 偏移 | 像素值 |
refWidth | 相对宽度 | 0~1 为百分比,其他为绝对调整 |
refHeight | 相对高度 | 同上 |
refRx | 相对圆角 rx | 0~1 为百分比 |
refRy | 相对圆角 ry | 同上 |
refCx | 相对圆心 cx | 0~1 为百分比 |
refCy | 相对圆心 cy | 同上 |
refR | 相对半径(内切) | 0~1 为百分比 |
refRCircumscribed | 相对半径(外接) | 0~1 为百分比 |
refD | 相对路径 d(缩放适配) | SVG path 字符串 |
refPoints | 相对多边形点(缩放适配) | 点坐标字符串 |
graph.addNode({
shape: 'rect',
x: 100, y: 100, width: 200, height: 80,
attrs: {
body: { fill: '#fff', stroke: '#333' },
icon: {
ref: 'body', // 参考 body 元素
refX: 0.5, // 水平居中(50%)
refY: 0.5, // 垂直居中(50%)
refWidth: 0.3, // 宽度为 body 的 30%
refHeight: 0.3, // 高度为 body 的 30%
},
},
});渐变色属性
fill 和 stroke 支持传入渐变对象,X6 会自动创建 SVG <defs> 中的渐变定义:
attrs: {
body: {
fill: {
type: 'linearGradient',
stops: [
{ offset: '0%', color: '#31d0c6' },
{ offset: '100%', color: '#7c68fc' },
],
},
},
}边连线属性
仅在边(Edge)的 attrs 中有效:
| 属性 | 说明 |
|---|---|
connection | 自动跟随边路径(设为 true 或 { stubs } 对象) |
atConnectionLength | 沿边路径指定长度处定位(保持切线方向) |
atConnectionRatio | 沿边路径指定比例处定位(保持切线方向) |
atConnectionLengthIgnoreGradient | 沿路径定位但不旋转 |
atConnectionRatioIgnoreGradient | 沿路径比例定位但不旋转 |
graph.addEdge({
source: node1,
target: node2,
attrs: {
line: { connection: true, stroke: '#333', strokeWidth: 2 },
label: {
atConnectionRatio: 0.5, // 标签定位在边的 50% 处
text: 'Hello',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
},
});其他内置属性
| 属性 | 说明 |
|---|---|
text | 设置文本内容(支持多行、text-path 等高级排版) |
textWrap | 文本自动换行配置 |
title | 设置 SVG <title> 子元素(tooltip) |
html | 设置元素的 innerHTML |
style | 设置 CSS 样式对象(通过 elem.style) |
filter | SVG 滤镜(支持对象形式的快捷语法) |
自定义属性注册
注册 API
通过 Graph.registerAttr(name, definition) 注册自定义属性:
import { Graph } from '@antv/x6';
Graph.registerAttr('myAttr', {
// qualify: 判断是否应用此属性处理器(可选)
qualify(value, { elem, attrs, cell, view }) {
return typeof value === 'number';
},
// set: 返回要设置的 SVG 属性对象
set(value, { elem, refBBox, cell, view }) {
return { opacity: value / 100 };
},
});三种属性定义类型
1. Set 属性 — 计算并设置 SVG 属性
Graph.registerAttr('highlightWidth', {
qualify(value) {
return typeof value === 'number';
},
set(value, { refBBox }) {
// 返回要设置到 DOM 元素的属性
return {
strokeWidth: value,
stroke: value > 2 ? 'red' : '#333',
};
},
});2. Position 属性 — 计算元素位置偏移
Graph.registerAttr('centerInParent', {
position(value, { refBBox }) {
if (value) {
return {
x: refBBox.x + refBBox.width / 2,
y: refBBox.y + refBBox.height / 2,
};
}
return null;
},
});3. Offset 属性 — 计算额外位移
Graph.registerAttr('circularOffset', {
offset(value, { refBBox }) {
const angle = (value * Math.PI) / 180;
const radius = Math.min(refBBox.width, refBBox.height) / 2;
return {
x: Math.cos(angle) * radius,
y: Math.sin(angle) * radius,
};
},
});qualify 函数
qualify 用于判断属性值是否应该由此自定义处理器处理。如果返回 false,该属性会作为普通 SVG 属性直接设置到元素上。
Graph.registerAttr('fill', {
// 只有当 fill 值是对象时才走渐变处理,字符串值直接作为 SVG fill
qualify(value) {
return typeof value === 'object' && value !== null;
},
set(fill, { view }) {
return `url(#${view.graph.defineGradient(fill)})`;
},
});完整示例:自定义进度条属性
import { Graph } from '@antv/x6';
// 注册一个 progress 属性,根据百分比动态设置宽度和颜色
Graph.registerAttr('progress', {
qualify(value) {
return typeof value === 'number';
},
set(value, { refBBox }) {
const percent = Math.max(0, Math.min(1, value));
const color = percent > 0.7 ? '#52c41a' : percent > 0.3 ? '#faad14' : '#f5222d';
return {
width: refBBox.width * percent,
fill: color,
};
},
});
const graph = new Graph({ container: 'container', width: 800, height: 600 });
graph.addNode({
shape: 'rect',
x: 100, y: 100, width: 200, height: 30,
markup: [
{ tagName: 'rect', selector: 'body' },
{ tagName: 'rect', selector: 'progress' },
{ tagName: 'text', selector: 'label' },
],
attrs: {
body: { width: 200, height: 30, fill: '#f0f0f0', stroke: '#d9d9d9' },
progress: { progress: 0.65, height: 30, rx: 0, ry: 0 },
label: { text: '65%', refX: 0.5, refY: 0.5, textAnchor: 'middle', textVerticalAnchor: 'middle' },
},
});常见错误
// ❌ 错误:refX/refY 使用像素值但期望百分比效果
attrs: { icon: { refX: 100, refY: 50 } }
// 当 refX > 1 时,被视为绝对偏移(像素),不是百分比
// ✅ 正确:使用 0~1 的小数表示百分比
attrs: { icon: { refX: 0.5, refY: 0.5 } } // 居中
// ❌ 错误:对非边元素使用 connection 属性
graph.addNode({
attrs: { body: { connection: true } } // connection 只对边有效
});
// ✅ 正确:connection 用于边的 attrs
graph.addEdge({
attrs: { line: { connection: true, stroke: '#333' } },
});基本用法
背景在 Graph 构造函数中通过 background 字段配置:
import { Graph } from '@antv/x6';
// 纯色背景
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
background: { color: '#F2F7FA' },
});配置项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color | string | - | 背景颜色(CSS 颜色值) |
image | string | - | 背景图片 URL |
position | string \ | object | 'center' |
size | string \ | object | 'auto auto' |
repeat | string | 'no-repeat' | 平铺模式:'repeat'、'no-repeat'、'repeat-x'、'repeat-y'、'flip-x'、'flip-y'、'flip-xy'、'watermark' |
opacity | number | 1 | 背景透明度(0~1) |
纯色背景
const graph = new Graph({
container: 'container',
background: { color: '#F2F7FA' },
});背景图片
const graph = new Graph({
container: 'container',
background: {
image: 'https://example.com/bg.png',
size: 'cover',
position: 'center',
opacity: 0.5,
},
});平铺模式
repeat(标准平铺)
const graph = new Graph({
container: 'container',
background: {
image: 'https://example.com/tile.png',
repeat: 'repeat',
size: { width: 100, height: 100 },
},
});flip-x / flip-y / flip-xy(翻转平铺)
图片在水平/垂直方向交替翻转,形成镜像平铺效果:
const graph = new Graph({
container: 'container',
background: {
image: 'https://example.com/pattern.png',
repeat: 'flip-xy', // 水平和垂直都翻转
size: { width: 200, height: 200 },
},
});watermark(水印)
图片以水印方式平铺,带旋转角度:
const graph = new Graph({
container: 'container',
background: {
image: 'https://example.com/watermark.png',
repeat: 'watermark',
opacity: 0.1,
},
});编程式 API
// 动态设置背景
graph.drawBackground({ color: '#fff' });
// 设置背景图片
graph.drawBackground({
image: 'https://example.com/bg.png',
repeat: 'repeat',
size: { width: 100, height: 100 },
});
// 清除背景
graph.clearBackground();完整示例:背景色 + 网格
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
background: { color: '#F2F7FA' },
grid: { visible: true, size: 10, type: 'dot' },
});
graph.addNode({
x: 200,
y: 150,
width: 120,
height: 60,
label: 'Hello',
attrs: { body: { fill: '#fff', stroke: '#5F95FF' } },
});常见错误
❌ background 与 grid 的颜色混淆
// 注意:grid 的颜色是网格线/点的颜色,不是背景色
const graph = new Graph({
container: 'container',
grid: { visible: true, args: { color: '#F2F7FA' } }, // ❌ 这不是设置背景色
});
// 正确:背景色用 background,网格颜色用 grid.args.color
const graph = new Graph({
container: 'container',
background: { color: '#F2F7FA' }, // ✅ 背景色
grid: { visible: true, args: { color: '#ddd' } }, // ✅ 网格点颜色
});❌ image 路径问题
// 注意:image 必须是可访问的 URL 或 Data URL
background: {
image: './bg.png', // ⚠️ 相对路径可能在某些环境下无法加载
}
// 推荐:使用绝对 URL 或 import 的资源
background: {
image: 'https://cdn.example.com/bg.png', // ✅
}Cell 数据操作 API
概述
X6 中每个 Cell(节点或边)都有三层数据操作 API:
| API | 作用 | 典型使用场景 |
|---|---|---|
prop() | 读写任意属性(shape、size、position 等) | 修改节点位置、大小 |
attr() | 读写 attrs 下的样式属性 | 修改填充色、边框、文字 |
getData() / setData() | 读写 data 字段(业务数据) | 存储业务状态、自定义数据 |
prop — 通用属性操作
prop() 是最底层的属性操作方法,可以读写 Cell 的任意属性。
读取属性
// 获取所有属性
const allProps = node.prop();
// 获取指定属性
const position = node.prop('position'); // { x: 100, y: 200 }
const shape = node.prop('shape'); // 'rect'
// 获取嵌套路径属性
const fill = node.prop('attrs/body/fill'); // '#fff'设置属性
// 设置单个属性
node.prop('position', { x: 200, y: 300 });
// 通过路径设置嵌套属性
node.prop('attrs/body/fill', '#f0f0f0');
// 批量设置多个属性(深度合并)
node.prop({
position: { x: 200, y: 300 },
size: { width: 120, height: 60 },
});删除属性
// 设置为 null 即删除
node.prop('attrs/body/stroke', null);setProp / removeProp
// setProp 等价于 prop(key, value)
node.setProp('label', 'Hello');
node.setProp({ label: 'Hello', size: { width: 100, height: 40 } });
// removeProp 删除指定属性
node.removeProp('data');
node.removeProp('attrs/body/stroke');attr — 样式属性操作
attr() 是 prop('attrs', ...) 的快捷方式,专门操作 attrs 下的 SVG 样式。
读取样式
// 获取所有 attrs
const attrs = node.attr();
// { body: { fill: '#fff', stroke: '#333' }, label: { text: 'Hello' } }
// 获取指定选择器的属性
const bodyAttrs = node.attr('body'); // { fill: '#fff', stroke: '#333' }
const fill = node.attr('body/fill'); // '#fff'设置样式
// 设置指定路径的值
node.attr('body/fill', '#ff0000');
node.attr('label/text', '新标题');
// 批量设置
node.attr({
body: { fill: '#ff0000', stroke: '#333' },
label: { text: '新标题', fontSize: 14 },
});边的 attr 操作
edge.attr('line/stroke', '#ff0000');
edge.attr('line/strokeWidth', 3);
edge.attr('line/targetMarker', 'classic');getData / setData — 业务数据操作
data 字段用于存储与渲染无关的业务数据,是最常用的状态存储方式。
初始化时设置 data
const node = graph.addNode({
shape: 'rect',
x: 100, y: 100, width: 120, height: 60,
data: {
status: 'running',
progress: 0.75,
taskId: 'task-001',
},
});读取 data
const data = node.getData();
// { status: 'running', progress: 0.75, taskId: 'task-001' }设置 data(深度合并,默认行为)
// 深度合并:只更新指定字段,保留其他字段
node.setData({ status: 'completed' });
// data 变为:{ status: 'completed', progress: 0.75, taskId: 'task-001' }设置 data(浅合并)
// 浅合并:Object.assign 行为
node.setData({ status: 'failed', error: 'timeout' }, { deep: false });替换 data(完全覆盖)
// 完全覆盖,丢弃旧数据
node.replaceData({ status: 'new', version: 2 });
// 等价于
node.setData({ status: 'new', version: 2 }, { overwrite: true });删除 data
node.removeData();监听数据变化
// 监听单个节点数据变化
node.on('change:data', ({ current, previous }) => {
console.log('data 从', previous, '变为', current);
});
// 通过 graph 监听所有节点数据变化
graph.on('node:change:data', ({ node, current, previous }) => {
console.log(`${node.id} data changed`);
});
// 监听 attrs 变化
graph.on('node:change:attrs', ({ node }) => {
console.log(`${node.id} attrs changed`);
});批量操作(Batch)
多次 prop/attr/setData 调用会触发多次事件。可以用 batch 合并为一次:
graph.startBatch('update');
node.prop('position', { x: 200, y: 300 });
node.attr('body/fill', '#ff0000');
node.setData({ status: 'updated' });
graph.stopBatch('update');
// 只触发一次 batch:stop 事件完整示例:动态状态更新
import { Graph, Shape } from '@antv/x6';
// 注册带状态渲染的 HTML 节点
Shape.HTML.register({
shape: 'status-node',
effect: ['data'],
html(node) {
const { status, label } = node.getData() || {};
const colors = { running: '#52c41a', error: '#f5222d', pending: '#faad14' };
const div = document.createElement('div');
div.style.cssText = `
width: 100%; height: 100%; display: flex; align-items: center;
padding: 8px; border: 2px solid ${colors[status] || '#d9d9d9'};
border-radius: 4px; background: #fff;
`;
div.innerHTML = `<span style="color:${colors[status] || '#333'}">${label || 'Node'}</span>`;
return div;
},
});
const graph = new Graph({ container: 'container', width: 800, height: 600 });
const node = graph.addNode({
shape: 'status-node',
x: 100, y: 100, width: 160, height: 50,
data: { status: 'pending', label: '数据处理' },
});
// 模拟状态更新 —— setData 触发 effect 重新渲染
setTimeout(() => node.setData({ status: 'running' }), 1000);
setTimeout(() => node.setData({ status: 'error', label: '数据处理(失败)' }), 3000);常见错误
// ❌ 错误:直接修改 getData() 返回的对象不会触发更新
const data = node.getData();
data.status = 'done'; // 不会触发重新渲染!
// ✅ 正确:通过 setData 修改
node.setData({ status: 'done' });
// ❌ 错误:attr 路径分隔符用 '.' 而非 '/'
node.attr('body.fill', '#fff'); // 错误,不生效
// ✅ 正确:使用 '/' 作为路径分隔符
node.attr('body/fill', '#fff');
// ❌ 错误:prop 设置 attrs 时只传部分会丢失其他
node.prop('attrs', { body: { fill: '#f00' } });
// 这会覆盖整个 attrs,丢失 label 等其他选择器!
// ✅ 正确:使用路径形式或 attr() 方法
node.prop('attrs/body/fill', '#f00'); // 只修改 body.fill
node.attr('body/fill', '#f00'); // 等价核心概念
ConnectionPoint(连接点) 是边路径与节点边界的实际交点。它与 anchor 的关系是:
1. Anchor → 确定参考点(如节点中心) 2. ConnectionPoint → 从对端方向画一条射线到 anchor,计算与节点边界的交点
对端 ─────────────── connectionPoint(边界交点) ─── anchor(参考点,节点内部)
↑
这是最终连线端点配置方式
全局配置
const graph = new Graph({
container: 'container',
connecting: {
connectionPoint: 'boundary', // 全局默认
},
});单边配置
graph.addEdge({
source: { cell: node1, connectionPoint: 'boundary' },
target: { cell: node2, connectionPoint: { name: 'boundary', args: { sticky: true } } },
});内置连接点类型
| 名称 | 说明 | 适用场景 |
|---|---|---|
'boundary' | 与节点实际形状边界的交点(默认值) | 圆形、椭圆、多边形等不规则形状 |
'bbox' | 与节点未旋转 BBox 的交点 | 简单矩形节点 |
'rect' | 与节点旋转后 BBox 的交点 | 旋转矩形节点 |
'anchor' | 直接使用 anchor 位置(不计算边界交点) | 需要连线穿入节点内部时 |
参数详解
boundary 参数
最常用的连接点策略,计算射线与节点 SVG 形状的精确交点。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
offset | `number \ | { x, y }` | 0 |
stroked | boolean | false | 是否将 strokeWidth 纳入计算 |
selector | `string \ | string[]` | 无 |
insideout | boolean | true | 参考点在形状内部时是否仍计算交点 |
extrapolate | boolean | false | 延长射线以确保与形状相交 |
sticky | boolean | false | 无交点时是否返回最近点(而非 anchor) |
precision | number | 2 | Path 元素的交点精度 |
bbox 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
offset | `number \ | { x, y }` | 0 |
stroked | boolean | false | 是否将 strokeWidth 纳入计算 |
rect 参数
与 bbox 相同,但会考虑节点旋转角度。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
offset | `number \ | { x, y }` | 0 |
stroked | boolean | false | 是否将 strokeWidth 纳入计算 |
anchor 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
offset | `number \ | { x, y }` | 0 |
align | `'top' \ | 'right' \ | 'bottom' \ |
alignOffset | number | 0 | 对齐偏移量 |
完整示例
boundary:精确形状交点
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
connecting: {
anchor: 'center',
connectionPoint: 'boundary',
router: 'orth',
connector: 'rounded',
},
});
// 圆形节点 - boundary 会精确计算圆弧交点
const circleNode = graph.addNode({
shape: 'circle',
x: 100,
y: 100,
width: 80,
height: 80,
label: '开始',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f' } },
});
const rectNode = graph.addNode({
shape: 'rect',
x: 350,
y: 100,
width: 120,
height: 60,
label: '处理',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 6, ry: 6 } },
});
graph.addEdge({
source: circleNode,
target: rectNode,
attrs: { line: { stroke: '#8f8f8f', targetMarker: 'classic' } },
});sticky 模式:确保始终有连接点
graph.addEdge({
source: {
cell: node1,
connectionPoint: {
name: 'boundary',
args: { sticky: true }, // 无交点时返回最近点
},
},
target: node2,
});anchor 类型:连线穿入节点
// 连线直接连接到 anchor 位置,不停留在边界
graph.addEdge({
source: {
cell: node1,
anchor: 'center',
connectionPoint: 'anchor', // 连线到达节点中心
},
target: node2,
});带偏移的连接点
graph.addEdge({
source: {
cell: node1,
connectionPoint: {
name: 'boundary',
args: { offset: 10 }, // 连接点从边界外移 10px
},
},
target: node2,
});connectionPoint 与 anchor 的配合关系
场景:node1 → node2
1. 确定 node2 的 anchor 位置(如 center = 节点中心)
2. 从 node1 方向画一条射线指向 node2 的 anchor
3. connectionPoint 计算射线与 node2 边界的交点
4. 该交点就是连线终止端的实际位置| 组合 | 效果 |
|---|---|
anchor: 'center' + connectionPoint: 'boundary' | 连线到达节点形状边界(最常用) |
anchor: 'center' + connectionPoint: 'anchor' | 连线穿入节点到达中心 |
anchor: 'left' + connectionPoint: 'boundary' | 从左侧方向计算边界交点 |
anchor: 'midSide' + connectionPoint: 'boundary' | 自动选择最近侧的边界交点 |
常见错误
❌ 混淆 connectionPoint 与 anchor
// 错误:想让连线连到节点边界却用了 anchor
graph.addEdge({
source: { cell: node1, anchor: 'boundary' }, // ❌ 'boundary' 不是 anchor 类型
target: node2,
});
// 正确:boundary 是 connectionPoint 类型
graph.addEdge({
source: { cell: node1, connectionPoint: 'boundary' },
target: node2,
});❌ 圆形节点使用 bbox 导致交点不精确
// 不推荐:对圆形节点 bbox 会计算矩形边界交点
connectionPoint: 'bbox' // 圆形节点会有间隙
// 推荐:使用 boundary 精确计算圆弧交点
connectionPoint: 'boundary'概念说明
当用户通过拖拽创建连线时,连线的 source/target 端点默认连接到节点的锚点(anchor)。连接策略(Connection Strategy)可以改变这个默认行为,让端点锚定到更精确的位置。
三种内置策略:
| 策略 | 说明 |
|---|---|
noop | 默认行为,不做额外处理,使用正常的 anchor 计算 |
pinAbsolute | 将端点固定到鼠标释放时的绝对坐标位置(相对于节点左上角的 x/y 偏移) |
pinRelative | 将端点固定到鼠标释放时的相对位置(0~1 比例值) |
基本用法
在 Graph 的 connecting 配置中设置:
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
connecting: {
connectionStrategy: 'pinRelative',
},
});pinAbsolute
端点固定到鼠标释放位置对应的绝对坐标(像素值):
const graph = new Graph({
container: 'container',
connecting: {
connectionStrategy: 'pinAbsolute',
},
});连线创建后,edge 的 source/target 数据会包含 anchor 字段:
// 连线数据示例
{
source: { cell: 'node1', anchor: { name: 'topLeft', args: { dx: 50, dy: 20 } } },
target: { cell: 'node2', anchor: { name: 'topLeft', args: { dx: 30, dy: 40 } } },
}pinRelative
端点固定到鼠标释放位置的相对比例(0~1):
const graph = new Graph({
container: 'container',
connecting: {
connectionStrategy: 'pinRelative',
},
});相对位置用比例表示,节点移动或缩放后连线端点会自动跟随:
// 连线数据示例(end 值为 -1~1 的相对量)
{
source: { cell: 'node1', anchor: { name: 'nodeCenter', args: { dx: '20%', dy: '30%' } } },
target: { cell: 'node2', anchor: { name: 'nodeCenter', args: { dx: '-10%', dy: '15%' } } },
}使用场景对比
| 场景 | 推荐策略 |
|---|---|
| 普通流程图/DAG(连线到端口) | noop(默认) |
| 自由连线到节点任意位置 | pinRelative |
| 精确定位(如电路图) | pinAbsolute |
与端口配合
当连线连接到端口(port)时,连接策略通常不需要配置(端口本身就是精确的锚点)。连接策略主要用于没有端口、直接连接到节点本体的场景。
const graph = new Graph({
container: 'container',
connecting: {
allowBlank: false,
// 有端口时通常不需要 connectionStrategy
// 无端口且需精确落点时使用:
connectionStrategy: 'pinRelative',
},
});自定义连接策略
可以注册自定义策略:
import { Graph } from '@antv/x6';
Graph.registerConnectionStrategy('myStrategy', (terminal, cellView, magnet, coords, edge, type, options) => {
// terminal: 当前的端点数据 { cell, port, ... }
// cellView: 目标节点/边的视图
// magnet: 触发连接的 DOM 元素
// coords: 鼠标释放时的画布坐标 { x, y }
// 返回修改后的 terminal 数据
return {
...terminal,
anchor: {
name: 'center',
},
};
});
const graph = new Graph({
container: 'container',
connecting: {
connectionStrategy: 'myStrategy',
},
});常见错误
❌ 对有端口的节点使用 pinAbsolute
// 不推荐:节点有端口时再用 pinAbsolute 会导致锚点计算混乱
const graph = new Graph({
container: 'container',
connecting: { connectionStrategy: 'pinAbsolute' },
});
graph.addNode({
x: 100, y: 100, width: 80, height: 40,
ports: { items: [{ id: 'p1', group: 'out' }] }, // 已有端口
});
// 连线时会忽略端口位置,连到鼠标释放的绝对位置// 正确:有端口时使用默认策略(noop),让连线自然连接到端口
const graph = new Graph({
container: 'container',
connecting: { allowBlank: false }, // ✅ 使用默认策略
});连接器完整列表
| Connector | 说明 | 典型场景 |
|---|---|---|
normal | 默认,直线连接各路由点 | 简单连线 |
rounded | 圆角折线 | 流程图 |
smooth | 贝塞尔曲线 | 平滑连线 |
jumpover | 跳线,交叉处产生弧形跳跃 | 复杂布线图 |
loop | 自环曲线 | 自环边 |
---
Loop 连接器
专为自环边设计的连接器,使用二次贝塞尔曲线(Q 命令)绘制弧线,配合 loop 路由器使用。
配置项
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
split | `boolean \ | number` | - |
示例
import { Graph } from '@antv/x6';
const graph = new Graph({ container: 'container' });
const node = graph.addNode({
shape: 'rect',
x: 150,
y: 100,
width: 100,
height: 50,
label: '状态 A',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 6, ry: 6 } },
});
// 自环边:必须同时使用 loop 路由器和 loop 连接器
graph.addEdge({
source: node,
target: node,
router: {
name: 'loop',
args: { width: 60, height: 100, angle: 'auto' },
},
connector: { name: 'loop' },
label: '重试',
attrs: {
line: { stroke: '#f5222d', strokeWidth: 2, targetMarker: 'classic' },
},
});关键说明
- 必须配合 `loop` 路由器使用,路由器提供中间控制点,连接器据此绘制曲线
- 生成的路径使用两段 Q(二次贝塞尔曲线)拼接
---
Jumpover 连接器
当多条边交叉时,在交叉点处绘制跳线弧形,避免视觉混淆。
配置项
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
size | number | 5 | 跳线弧的大小(半径) |
type | `'arc' \ | 'gap' \ | 'cubic'` |
radius | number | 0 | 折线圆角半径 |
ignoreConnectors | string[] | ['smooth'] | 忽略与哪些连接器类型的交叉 |
跳线类型说明
- `arc`:半圆弧跳过(默认),最常用
- `gap`:断开间隙
- `cubic`:三次曲线跳过,更平滑
示例
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
connecting: {
connector: {
name: 'jumpover',
args: {
size: 8,
type: 'arc',
},
},
},
});
// 创建多条交叉的边
const node1 = graph.addNode({
shape: 'rect', x: 50, y: 50, width: 80, height: 40, label: 'A',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 4, ry: 4 } },
});
const node2 = graph.addNode({
shape: 'rect', x: 300, y: 50, width: 80, height: 40, label: 'B',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 4, ry: 4 } },
});
const node3 = graph.addNode({
shape: 'rect', x: 50, y: 200, width: 80, height: 40, label: 'C',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 4, ry: 4 } },
});
const node4 = graph.addNode({
shape: 'rect', x: 300, y: 200, width: 80, height: 40, label: 'D',
attrs: { body: { fill: '#fff', stroke: '#8f8f8f', rx: 4, ry: 4 } },
});
// 两条交叉边
graph.addEdge({
source: node1,
target: node4,
connector: { name: 'jumpover', args: { size: 8, type: 'arc' } },
attrs: { line: { stroke: '#5b8ff9', strokeWidth: 2 } },
});
graph.addEdge({
source: node2,
target: node3,
connector: { name: 'jumpover', args: { size: 8, type: 'arc' } },
attrs: { line: { stroke: '#52c41a', strokeWidth: 2 } },
});单条边设置 jumpover
// 在单条边上设置
graph.addEdge({
source: node1,
target: node2,
connector: {
name: 'jumpover',
args: {
size: 6,
type: 'cubic',
radius: 4,
},
},
attrs: { line: { stroke: '#333', strokeWidth: 2 } },
});全局默认设置 jumpover
// 在 Graph 初始化时全局配置
const graph = new Graph({
container: 'container',
connecting: {
connector: {
name: 'jumpover',
args: { size: 5, type: 'arc' },
},
},
});---
连接器简写与对象写法
// 简写(无参数时)
graph.addEdge({ source, target, connector: 'rounded' });
// 对象写法(带参数时)
graph.addEdge({
source,
target,
connector: {
name: 'rounded',
args: { radius: 10 },
},
});---
常见错误与修正
错误 1: 自环边只用 loop 连接器不用 loop 路由器
// ❌ 错误:缺少 loop 路由器,连接器没有正确的控制点
graph.addEdge({
source: node,
target: node,
connector: { name: 'loop' },
});
// ✅ 正确:路由器和连接器配合使用
graph.addEdge({
source: node,
target: node,
router: { name: 'loop', args: { width: 50, height: 80 } },
connector: { name: 'loop' },
});错误 2: jumpover 不生效
// ❌ 错误:只给一条边设置 jumpover,另一条边用 smooth(默认被忽略)
// jumpover 默认忽略 smooth 连接器的交叉
// ✅ 正确:确保需要跳线的边都使用 jumpover 或非忽略的连接器
// 或修改 ignoreConnectors 参数
connector: {
name: 'jumpover',
args: { ignoreConnectors: [] }, // 不忽略任何连接器
}错误 3: jumpover 的 type 拼写错误
// ❌ 错误
connector: { name: 'jumpover', args: { type: 'curve' } }
// ✅ 正确:type 取值为 'arc' | 'gap' | 'cubic'
connector: { name: 'jumpover', args: { type: 'cubic' } }Connector 连接器完整参数
概述
连接器(Connector)决定边的线条样式——在路由器计算出的路径点之间如何绘制曲线。X6 3.x 内置 5 种连接器。
使用方式
// 字符串简写(使用默认参数)
graph.addEdge({ source, target, connector: 'rounded' });
// 对象形式(传递参数)
graph.addEdge({
source, target,
connector: { name: 'rounded', args: { radius: 20 } },
});normal — 直线段(默认)
在路径点之间用直线段连接,无额外参数。
graph.addEdge({ source, target, connector: 'normal' });参数: 无特殊参数。
---
rounded — 圆角折线
在折线的转角处用贝塞尔曲线绘制圆角。
graph.addEdge({
source, target,
router: 'orth',
connector: { name: 'rounded', args: { radius: 10 } },
});参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
radius | number | 10 | 转角圆角半径(px)。值越大圆角越大。实际圆角半径不会超过相邻两段线段长度的一半 |
示例对比:
// 小圆角
connector: { name: 'rounded', args: { radius: 5 } }
// 大圆角
connector: { name: 'rounded', args: { radius: 30 } }---
smooth — 贝塞尔曲线
用三次贝塞尔曲线连接起点和终点。如果有路由点则通过 Catmull-Rom 样条拟合。
graph.addEdge({
source, target,
connector: { name: 'smooth', args: { direction: 'H' } },
});参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
direction | 'H' \ | 'V' | 自动 |
direction 说明:
'H'(水平):控制点在 X 轴方向取中点,产生 S 形水平曲线。适合 DAG 图、血缘图等左右流向布局'V'(垂直):控制点在 Y 轴方向取中点,产生 S 形垂直曲线。适合组织架构图等上下流向布局- 不传:自动根据
|dx| >= |dy|选择'H',否则选择'V'
注意: 当存在路由点(routePoints)时,direction 参数无效,改为使用 Catmull-Rom 样条曲线经过所有路由点。
// 水平布局的血缘图
graph.addEdge({
source: { cell: leftNode, port: 'out' },
target: { cell: rightNode, port: 'in' },
connector: { name: 'smooth', args: { direction: 'H' } },
});
// 垂直布局的组织架构图
graph.addEdge({
source: { cell: parentNode, port: 'bottom' },
target: { cell: childNode, port: 'top' },
connector: { name: 'smooth', args: { direction: 'V' } },
});---
jumpover — 跳线
当两条边在画布上交叉时,在交叉点处绘制弧形跳线以区分不同路径。
graph.addEdge({
source, target,
connector: { name: 'jumpover', args: { size: 5, type: 'arc' } },
});参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
type | 'arc' \ | 'gap' \ | 'cubic' |
size | number | 5 | 跳线大小(半径或间隙宽度) |
---
loop — 自环连接器
当边的 source 和 target 是同一个节点时使用,绘制从节点出发再回到自身的环形路径。
graph.addEdge({
source: node,
target: node,
connector: { name: 'loop', args: { width: 50, height: 80, direction: 'top' } },
});参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width | number | 环的宽度 | |
height | number | 环的高度 | |
direction | string | 环的方向 |
---
自定义连接器
通过 Graph.registerConnector 注册自定义连接器:
import { Graph, Path } from '@antv/x6';
Graph.registerConnector('wobble', (sourcePoint, targetPoint, routePoints, options) => {
const path = new Path();
path.appendSegment(Path.createSegment('M', sourcePoint));
// 自定义路径逻辑
path.appendSegment(Path.createSegment('L', targetPoint));
return options.raw ? path : path.serialize();
});
graph.addEdge({
source, target,
connector: { name: 'wobble', args: {} },
});连接器函数签名:
(
sourcePoint: PointLike, // 起点坐标
targetPoint: PointLike, // 终点坐标
routePoints: PointLike[], // 路由器计算的中间路径点
options: T, // 用户传入的 args
edgeView: EdgeView, // 边视图实例
) => Path | string // 返回 Path 对象或 SVG path 字符串完整示例
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
connecting: {
router: 'orth',
connector: { name: 'rounded', args: { radius: 8 } },
},
});
const n1 = graph.addNode({ shape: 'rect', x: 50, y: 50, width: 80, height: 40, label: 'A' });
const n2 = graph.addNode({ shape: 'rect', x: 300, y: 50, width: 80, height: 40, label: 'B' });
const n3 = graph.addNode({ shape: 'rect', x: 300, y: 250, width: 80, height: 40, label: 'C' });
// rounded 圆角折线
graph.addEdge({ source: n1, target: n2, router: 'orth', connector: { name: 'rounded', args: { radius: 15 } } });
// smooth 贝塞尔曲线
graph.addEdge({ source: n1, target: n3, connector: { name: 'smooth', args: { direction: 'H' } } });
// 自环边
graph.addEdge({ source: n2, target: n2, connector: 'loop' });常见错误
// ❌ 错误:rounded 不搭配路由器使用时没有折线角可圆角化
graph.addEdge({ source, target, connector: 'rounded' });
// 只有起止两点的直线,rounded 无效果
// ✅ 正确:搭配 orth/manhattan 路由器产生折线
graph.addEdge({ source, target, router: 'orth', connector: 'rounded' });
// ❌ 错误:smooth 的 direction 拼写错误
connector: { name: 'smooth', args: { direction: 'horizontal' } } // 无效
// ✅ 正确:只接受 'H' 或 'V'
connector: { name: 'smooth', args: { direction: 'H' } }坐标系说明
X6 中存在四套坐标系:
| 坐标系 | 说明 | 应用场景 |
|---|---|---|
| local | 画布本地坐标,节点的 x/y 属于此坐标系 | 节点定位、addNode、节点属性 |
| graph | 经过平移/缩放变换后的视口坐标 | 画布实际渲染像素位置 |
| client | 浏览器窗口视口坐标(getBoundingClientRect) | 鼠标事件的 clientX/clientY |
| page | 文档坐标(含页面滚动偏移) | 鼠标事件的 pageX/pageY |
转换关系:
local --[matrix]--> graph --[offset]--> client --[scroll]--> page点坐标转换 API
local → 其他
// local → graph(应用缩放和平移)
graph.localToGraph({ x: 100, y: 100 }); // Point
graph.localToGraph(100, 100); // Point
// local → client(浏览器视口坐标)
graph.localToClient({ x: 100, y: 100 }); // Point
// local → page(文档坐标)
graph.localToPage({ x: 100, y: 100 }); // Point其他 → local
// graph → local(逆变换)
graph.graphToLocal({ x: 200, y: 150 }); // Point
// client → local(最常用:鼠标事件 → 画布坐标)
graph.clientToLocal({ x: e.clientX, y: e.clientY }); // Point
graph.clientToLocal(e.clientX, e.clientY); // Point
// client → graph
graph.clientToGraph({ x: e.clientX, y: e.clientY }); // Point
// page → local
graph.pageToLocal({ x: e.pageX, y: e.pageY }); // Point矩形坐标转换 API
所有点转换都有对应的矩形版本,返回 Rectangle 对象:
// local → graph
graph.localToGraphRect({ x: 100, y: 100, width: 200, height: 150 });
// local → client
graph.localToClientRect(100, 100, 200, 150);
// graph → local
graph.graphToLocalRect({ x: 200, y: 150, width: 300, height: 200 });
// client → local
graph.clientToLocalRect(e.clientX, e.clientY, width, height);
// client → graph
graph.clientToGraphRect({ x: 0, y: 0, width: 100, height: 100 });
// page → local
graph.pageToLocalRect({ x: 0, y: 0, width: 100, height: 100 });snapToGrid
将客户端坐标转换为 local 坐标并吸附到网格:
// 将鼠标位置吸附到网格
const pos = graph.snapToGrid(e.clientX, e.clientY);
// 返回吸附后的 local 坐标 Point { x, y }常用场景示例
场景 1:从外部拖拽元素到画布创建节点
document.getElementById('drag-source').addEventListener('drop', (e) => {
e.preventDefault();
// 将鼠标释放位置转换为画布坐标,并吸附到网格
const pos = graph.snapToGrid(e.clientX, e.clientY);
graph.addNode({
x: pos.x,
y: pos.y,
width: 100,
height: 50,
label: 'New Node',
});
});场景 2:自定义右键菜单定位
graph.on('node:contextmenu', ({ e, node }) => {
// 使用 client 坐标定位菜单(相对于浏览器视口)
const menu = document.getElementById('context-menu');
menu.style.left = `${e.clientX}px`;
menu.style.top = `${e.clientY}px`;
menu.style.display = 'block';
});场景 3:获取节点在屏幕上的实际位置
const node = graph.getCellById('node1');
const { x, y } = node.getPosition(); // local 坐标
// 转换为浏览器视口坐标(可用于定位浮层)
const clientPos = graph.localToClient({ x, y });
console.log(`节点在屏幕上的位置: (${clientPos.x}, ${clientPos.y})`);场景 4:计算画布可视区域内的节点
// 获取当前可视区域(graph 坐标系)
const visibleArea = graph.getGraphArea(); // Rectangle
// 转换为 local 坐标系
const localArea = graph.graphToLocalRect(visibleArea);
// 筛选在可视区域内的节点
const visibleNodes = graph.getNodes().filter((node) => {
const bbox = node.getBBox();
return localArea.isIntersectWithRect(bbox);
});常见错误
❌ 直接使用鼠标 clientX/clientY 作为节点坐标
// 错误:鼠标坐标是 client 坐标系,不能直接用于节点定位
document.addEventListener('click', (e) => {
graph.addNode({ x: e.clientX, y: e.clientY, width: 80, height: 40 }); // ❌ 位置不对
});// 正确:先转换坐标系
document.addEventListener('click', (e) => {
const pos = graph.clientToLocal(e.clientX, e.clientY);
graph.addNode({ x: pos.x, y: pos.y, width: 80, height: 40 }); // ✅
});❌ 混淆 localToGraph 和 localToClient
// localToGraph:加了画布缩放/平移变换,用于画布内部像素计算
// localToClient:转换到浏览器视口坐标,用于定位 DOM 元素(如弹窗、菜单)❌ X6 事件中的坐标已是 local 坐标
// X6 事件回调中的 x, y 已经是 local 坐标,无需再转换
graph.on('blank:click', ({ e, x, y }) => {
// x, y 已经是 local 坐标 ✅
graph.addNode({ x, y, width: 80, height: 40 });
});为什么需要 defs
SVG 的 <defs> 元素用于声明可被引用的"模板资源"(渐变、滤镜、marker),通过 url(#id) 在 fill / stroke / marker-end / filter 等属性上使用。
X6 把全部 <defs> 操作封装在 DefsManager(src/graph/defs.ts)里,对外暴露三个 Graph 方法:
| 方法 | 返回 | 内部行为 |
|---|---|---|
graph.defineGradient(options) | string(id) | 在 <defs> 中创建 <linearGradient> / <radialGradient> |
graph.defineMarker(options) | string(id) | 在 <defs> 中创建 <marker> |
graph.defineFilter(options) | string(id) | 在 <defs> 中创建 <filter> |
所有方法都是幂等的:内部用 StringExt.hashcode(JSON.stringify(options)) 拼 id,相同 options 重复调用只会创建一次。
⚠️ 禁止直接读写graph.defs/graph.svgDoc等内部字段;X6 3.x 没有这两个公开属性,强行访问会抛Cannot read properties of undefined。
graph.defineGradient
类型定义(核对自 src/graph/defs.ts)
interface GradientOptions {
id?: string
type: string // 'linearGradient' | 'radialGradient'
stops: { offset: number; color: string; opacity?: number }[]
attrs?: SimpleAttrs // 给 <linearGradient> 标签本身的额外属性
}大多数场景不用手动调用——直接在 attrs.fill / attrs.stroke 写渐变对象
X6 内置 fill attr 注册器(见 core/x6-core-attr-registry.md)会判断 fill 值为对象时自动调用 defineGradient:
attrs: {
body: {
fill: {
type: 'linearGradient',
stops: [
{ offset: 0, color: '#1890ff' },
{ offset: 1, color: '#13c2c2', opacity: 0.6 },
],
},
},
}offset既可以传0~1的数字,也可以传'0%' ~ '100%'的字符串,源码会原样拼到stop-offset。
需要显式调用 defineGradient 的场景
当渐变需要被自定义 marker 或自定义 attr 引用时,必须先拿到 id,再写到 marker 的 fill: 'url(#xxx)' 上:
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
background: { color: '#F2F7FA' },
});
const gradientId = graph.defineGradient({
type: 'linearGradient',
stops: [
{ offset: 0, color: '#ff7875' },
{ offset: 1, color: '#ff4d4f' },
],
});
graph.addEdge({
source: { x: 80, y: 80 },
target: { x: 320, y: 220 },
attrs: {
line: {
stroke: '#ff4d4f',
strokeWidth: 2,
targetMarker: {
// 自定义 marker,filling 用前面的渐变 id
tagName: 'path',
d: 'M 12 -6 0 0 12 6 z',
fill: `url(#${gradientId})`,
},
},
},
});
graph.centerContent();graph.defineMarker
类型定义(核对自 src/registry/marker/index.ts)
interface MarkerResult extends SimpleAttrs {
id?: string
tagName?: string // 默认 'path'
refX?: number
refY?: number
markerUnits?: 'userSpaceOnUse' | 'strokeWidth' // 默认 'userSpaceOnUse'
markerOrient?: 'auto' | 'auto-start-reverse' | number // 默认 'auto'
children?: { tagName: string; [attr: string]: any }[]
// 其他字段会作为 marker 内部 path 的 attrs(fill / stroke / d / size 等)
}大多数场景:直接在 edge 的 targetMarker / sourceMarker 中用内置名字
X6 内置 7 类 marker(src/registry/marker/):
| name | 形状 | 关键参数 |
|---|---|---|
'classic' | 经典三角箭头(默认) | size, width, height, offset, factor |
'block' | 实心三角块 | size, width, height, offset, open |
'diamond' | 菱形 | size, width, height, offset |
'cross' | 十字 | size, width, height, offset |
'circle' | 圆点 | r, size, offset |
'ellipse' | 椭圆 | rx, ry, offset |
'async' | 异步双箭头 | size, width, height, offset |
'path' | 自定义 path | d, offset, attrs |
graph.addEdge({
source: a, target: b,
attrs: {
line: {
stroke: '#333',
targetMarker: 'classic', // 字符串简写
sourceMarker: { name: 'circle', args: { r: 4 } }, // 对象 + args
},
},
});需要 defineMarker 的场景:完全自定义 marker(带 filter / children / 渐变)
const arrowId = graph.defineMarker({
tagName: 'path',
refX: 6,
refY: 4,
markerUnits: 'userSpaceOnUse',
markerOrient: 'auto',
d: 'M 0 0 L 8 4 L 0 8 z',
fill: '#1890ff',
});
graph.addEdge({
source: a, target: b,
attrs: {
line: {
stroke: '#1890ff',
'marker-end': `url(#${arrowId})`, // 直接用 SVG marker-end 引用
},
},
});带 children(适用于复合 marker,例如带边框的圆形终止符):
graph.defineMarker({
tagName: 'circle',
children: [
{ tagName: 'circle', r: 4, fill: '#fff', stroke: '#1890ff', 'stroke-width': 2 },
{ tagName: 'circle', r: 2, fill: '#1890ff' },
],
refX: 5,
refY: 0,
markerOrient: 'auto-start-reverse',
});源码defs.ts:127显示:若tagName !== 'path',会自动删除d属性,避免从 standard edge 继承的污染。
graph.defineFilter
类型定义(核对自 src/registry/filter/index.ts)
type FilterOptions = (FilterNativeItem | FilterManualItem) & {
id?: string
attrs?: SimpleAttrs // <filter> 标签本身的属性,默认 { x:-1, y:-1, width:3, height:3, filterUnits:'objectBoundingBox' }
}
interface FilterNativeItem {
name: 'outline' | 'highlight' | 'blur' | 'dropShadow'
| 'grayScale' | 'sepia' | 'saturate' | 'hueRotate'
| 'invert' | 'brightness' | 'contrast'
args?: { /* 不同 name 对应不同 args,见下表 */ }
}X6 内置 11 个 filter(核对自 src/registry/filter/main.ts)
| name | args 示例 | 效果 |
|---|---|---|
'outline' | { color, width, margin, opacity } | 描边 |
'highlight' | { color, width, blur, opacity } | 高亮发光 |
'blur' | { x, y } | 模糊 |
'dropShadow' | { dx, dy, color, blur, opacity } | 投影 |
'grayScale' | { amount } | 灰度 |
'sepia' | { amount } | 怀旧 |
'saturate' | { amount } | 饱和度 |
'hueRotate' | { angle } | 色相旋转 |
'invert' | { amount } | 反色 |
'brightness' | { amount } | 亮度 |
'contrast' | { amount } | 对比度 |
严格大小写:dropShadow不是drop-shadow、grayScale不是grayscale。
通过 attrs.filter 直接使用(推荐)
X6 在 attrs 中识别 filter 字段,传对象会自动调用 defineFilter:
graph.addNode({
shape: 'rect',
x: 100, y: 100, width: 120, height: 60,
attrs: {
body: {
fill: '#fff',
stroke: '#8f8f8f',
filter: {
name: 'dropShadow',
args: { dx: 2, dy: 2, blur: 4, color: 'rgba(0,0,0,0.2)' },
},
},
},
});显式调用 defineFilter(需要在多处共享或自定义 filter 时)
const shadowId = graph.defineFilter({
name: 'dropShadow',
args: { dx: 0, dy: 4, blur: 8, color: '#1890ff', opacity: 0.4 },
});
// 多个节点共享同一个滤镜引用
['n1', 'n2', 'n3'].forEach((id, i) => {
graph.addNode({
id, shape: 'rect',
x: 60 + i * 160, y: 100, width: 100, height: 50,
attrs: { body: { fill: '#fff', filter: `url(#${shadowId})` } },
});
});自定义 filter 标签(FilterManualItem)
如果内置 11 项不够,可以传一个不在 native 列表里的 name,然后通过 Registry 自行扩展 filter 工厂函数(高级用法,多数业务无需触及,详见 core/x6-core-filter.md)。
三个方法的共性
1. 返回值都是字符串 id,需要拼成 url(#id) 使用 2. 幂等:相同 options 多次调用只创建一次 DOM(基于 JSON.stringify hash) 3. DefsManager.remove(id) 可主动移除,但通常不需要
常见错误与修正
❌ 直接操作 DOM 创建 defs
// 错误:graph.defs / graph.svgDoc 都不是公开 API,会报 Cannot read properties of undefined
const defs = graph.defs;
const grad = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
defs.appendChild(grad);
// 正确:用 defineGradient
const id = graph.defineGradient({
type: 'linearGradient',
stops: [{ offset: 0, color: '#f00' }, { offset: 1, color: '#0f0' }],
});
attrs.body.fill = `url(#${id})`;❌ 渐变直接传字符串
// 错误:渐变对象不能被 fromJSON 解析为字符串
attrs: { body: { fill: 'linear-gradient(#f00, #0f0)' } } // ❌ 这是 CSS 语法
// 正确:传渐变对象
attrs: {
body: {
fill: {
type: 'linearGradient',
stops: [{ offset: 0, color: '#f00' }, { offset: 1, color: '#0f0' }],
},
},
}❌ defineMarker 漏写 tagName
// 错误:tagName 默认补 'path',但 d 属性必须配合 path 一起给
graph.defineMarker({ refX: 5, refY: 0 }); // 渲染为空
// 正确:path 类型
graph.defineMarker({ tagName: 'path', d: 'M0 0 L8 4 L0 8 z', fill: '#333' });
// 或者:非 path 元素必须显式指定 tagName 并避免 d
graph.defineMarker({ tagName: 'circle', r: 4, fill: '#333' });❌ filter 名字大小写错误
// 错误:内置名字严格匹配,写错会抛 Filter not found
filter: { name: 'drop-shadow', args: { dx: 2, dy: 2 } } // ❌
filter: { name: 'grayscale', args: { amount: 1 } } // ❌
// 正确
filter: { name: 'dropShadow', args: { dx: 2, dy: 2 } } // ✅
filter: { name: 'grayScale', args: { amount: 1 } } // ✅❌ 重复定义同样的渐变
// 错误:每次都拼 id,但 X6 内部已经去重,多此一举
for (const node of nodes) {
const id = graph.defineGradient({ type: 'linearGradient', stops: [...] });
// ...
}
// 正确:调用一次拿到 id 即可
const gradientId = graph.defineGradient({ type: 'linearGradient', stops: [...] });
nodes.forEach((n) => n.attr('body/fill', `url(#${gradientId})`));边锚点(Edge Anchor)
概述
当一条边的 source 或 target 连接到另一条边(而非节点)时,需要使用 Edge Anchor 来确定连接点在目标边路径上的位置。
内置 Edge Anchor 类型
| 类型 | 说明 | 参数 |
|---|---|---|
ratio | 按比例定位(默认 0.5 即中点) | { ratio: 0~1 } |
length | 按绝对长度定位(从起点开始的像素距离) | { length: number } |
closest | 距参考点最近的路径点 | 无 |
orth | 正交方向上距参考点最近的交点 | `{ fallbackAt?: number \ |
使用方式
边锚点通过 source.anchor 或 target.anchor 配置:
graph.addEdge({
source: { cell: edge1.id, anchor: { name: 'ratio', args: { ratio: 0.3 } } },
target: { cell: edge2.id, anchor: { name: 'closest' } },
});各类型详解
ratio — 按比例定位
在目标边路径上按比例取点,ratio 为 0~1 之间的小数(默认 0.5 即中点)。如果 ratio > 1,会自动除以 100 作为百分比处理。
graph.addEdge({
source: { cell: anotherEdge.id, anchor: { name: 'ratio', args: { ratio: 0.25 } } },
target: targetNode,
});length — 按绝对长度定位
从目标边起点沿路径前进指定像素距离的点(默认 20px)。
graph.addEdge({
source: { cell: anotherEdge.id, anchor: { name: 'length', args: { length: 50 } } },
target: targetNode,
});closest — 最近点
取目标边路径上距离参考点最近的点。
graph.addEdge({
source: { cell: anotherEdge.id, anchor: { name: 'closest' } },
target: targetNode,
});orth — 正交锚点
从参考点出发,沿水平或垂直方向与目标边路径的交点。如果找不到正交交点,回退到 fallbackAt 指定的位置(比例或长度),若未设置 fallbackAt 则回退到 closest。
graph.addEdge({
source: { cell: anotherEdge.id, anchor: { name: 'orth', args: { fallbackAt: 0.5 } } },
target: targetNode,
});与 Node Anchor 的区别
| 特性 | Node Anchor | Edge Anchor |
|---|---|---|
| 适用场景 | 边连接到节点 | 边连接到另一条边 |
| 配置位置 | source/target.anchor | 同左(自动根据目标类型选用) |
| 内置类型 | center、top、bottom、left、right 等 | ratio、length、closest、orth |
自定义 Edge Anchor
通过 Graph.registerEdgeAnchor 注册自定义边锚点:
import { Graph } from '@antv/x6';
Graph.registerEdgeAnchor('myAnchor', (view, magnet, ref, options, type) => {
// view: EdgeView 实例
// ref: 参考点
// 返回 Point 对象
const ratio = options.ratio || 0.5;
return view.getPointAtRatio(ratio);
});
// 使用
graph.addEdge({
source: { cell: edge1.id, anchor: { name: 'myAnchor', args: { ratio: 0.7 } } },
target: targetNode,
});常见错误
// ❌ 错误:edge anchor 只在边连边时生效,节点连接请用 node anchor
graph.addEdge({
source: { cell: node.id, anchor: { name: 'ratio' } }, // ratio 是 edge anchor,不适用于节点
target: targetNode,
});
// ✅ 正确:节点连接使用 node anchor
graph.addEdge({
source: { cell: node.id, anchor: { name: 'center' } },
target: targetNode,
});添加边
// 方式1:传入节点实例
graph.addEdge({ source: sourceNode, target: targetNode });
// 方式2:传入节点 ID
graph.addEdge({ source: 'node1', target: 'node2' });
// 方式3:连接到端口
graph.addEdge({
source: { cell: 'node1', port: 'out1' },
target: { cell: 'node2', port: 'in1' },
});
// 方式4:使用坐标点
graph.addEdge({
source: { x: 100, y: 50 },
target: { x: 400, y: 50 },
});
// 或用简写
graph.addEdge({
sourcePoint: [100, 50],
targetPoint: [400, 50],
});边样式
graph.addEdge({
source: node1,
target: node2,
attrs: {
line: {
stroke: '#8f8f8f', // 线条颜色
strokeWidth: 1, // 线宽
strokeDasharray: '5 3', // 虚线(5px 线 + 3px 间隔)
targetMarker: 'classic', // 目标端箭头
sourceMarker: null, // 源端无箭头
},
},
});箭头类型
// 内置箭头
targetMarker: 'classic' // 经典三角箭头
targetMarker: 'block' // 实心三角
targetMarker: 'circle' // 圆形
targetMarker: 'circlePlus' // 带+号圆形
targetMarker: 'diamond' // 菱形
targetMarker: 'ellipse' // 椭圆
targetMarker: 'cross' // 十字
targetMarker: 'async' // 异步标记
// 自定义箭头
targetMarker: {
name: 'block',
width: 12,
height: 8,
offset: -4,
fill: '#333',
}路由器(Router)
路由器决定边经过的路径点(拐点)。
// 正交路由(垂直/水平折线)
graph.addEdge({ source, target, router: 'orth' });
// Manhattan 路由(智能绕障)
graph.addEdge({ source, target, router: 'manhattan' });
// 路由器带配置
graph.addEdge({
source, target,
router: { name: 'orth', args: { padding: 20 } },
});
// ER 图专用路由
graph.addEdge({ source, target, router: 'er' });
// Metro 地铁线路由
graph.addEdge({ source, target, router: 'metro' });连接器(Connector)
连接器决定路径点之间如何绘制线条。
// 圆角折线
graph.addEdge({ source, target, router: 'orth', connector: 'rounded' });
// 贝塞尔曲线
graph.addEdge({ source, target, connector: 'smooth' });
// 跳线(交叉处跳跃)
graph.addEdge({ source, target, connector: 'jumpover' });
// 连接器带配置
graph.addEdge({
source, target,
connector: { name: 'rounded', args: { radius: 10 } },
});边标签
// 简写
graph.addEdge({ source, target, label: 'Yes' });
// 详细配置
graph.addEdge({
source, target,
labels: [
{
position: 0.5, // 标签在边上的位置(0-1)
attrs: {
text: { text: 'label text', fontSize: 12, fill: '#333' },
rect: { fill: '#fff', stroke: '#8f8f8f', rx: 3, ry: 3 },
},
},
],
});
// 多个标签
graph.addEdge({
source, target,
labels: [
{ position: 0.25, attrs: { text: { text: 'start' } } },
{ position: 0.75, attrs: { text: { text: 'end' } } },
],
});顶点(Vertices)
手动指定边的中间拐点:
graph.addEdge({
source: node1,
target: node2,
vertices: [
{ x: 200, y: 50 },
{ x: 200, y: 200 },
],
attrs: { line: { stroke: '#8f8f8f', strokeWidth: 1, targetMarker: 'classic' } },
});动态修改边
// 修改样式
edge.attr('line/stroke', '#f5222d');
edge.attr('line/strokeWidth', 2);
// 修改标签
edge.setLabels([{ attrs: { text: { text: 'Updated' } } }]);
// 修改路由器
edge.setRouter('manhattan');
// 修改连接器
edge.setConnector('smooth');
// 修改源/目标
edge.setSource(newSourceNode);
edge.setTarget({ cell: 'node3', port: 'in1' });常用边样式组合
流程图边
graph.addEdge({
source, target,
router: 'orth',
connector: 'rounded',
attrs: { line: { stroke: '#8f8f8f', strokeWidth: 1, targetMarker: 'classic' } },
});血缘图边
graph.addEdge({
source: { cell: srcNode, port: 'out1' },
target: { cell: tgtNode, port: 'in1' },
connector: 'smooth',
attrs: { line: { stroke: '#8f8f8f', strokeWidth: 1 } },
});虚线边(调用关系)
graph.addEdge({
source, target,
attrs: {
line: { stroke: '#aaa', strokeWidth: 1, strokeDasharray: '5 3', targetMarker: 'classic' },
},
});高亮状态边
graph.addEdge({
source, target,
attrs: { line: { stroke: '#1890ff', strokeWidth: 2, targetMarker: 'classic' } },
});事件回调格式
重要:X6 事件回调参数是对象解构,不是位置参数。
// ✅ 正确:对象解构
graph.on('node:click', ({ node, e }) => {
console.log('Clicked node:', node.id);
});
// ❌ 错误:位置参数
graph.on('node:click', (node, e) => { ... });节点事件
// 点击
graph.on('node:click', ({ node, e }) => {
console.log('Clicked:', node.id);
});
// 双击
graph.on('node:dblclick', ({ node, e }) => {
console.log('Double clicked:', node.id);
});
// 右键
graph.on('node:contextmenu', ({ node, e }) => {
e.preventDefault();
});
// 鼠标进入/离开
graph.on('node:mouseenter', ({ node }) => {
node.attr('body/stroke', '#1890ff');
});
graph.on('node:mouseleave', ({ node }) => {
node.attr('body/stroke', '#8f8f8f');
});
// 节点移动中
graph.on('node:moving', ({ node, x, y }) => {
console.log('Moving to:', x, y);
});
// 节点移动完成
graph.on('node:moved', ({ node }) => {
const pos = node.getPosition();
console.log('Moved to:', pos.x, pos.y);
});
// 节点大小改变
graph.on('node:resized', ({ node }) => {
const size = node.getSize();
console.log('Resized to:', size.width, size.height);
});边事件
// 点击
graph.on('edge:click', ({ edge, e }) => {
console.log('Edge:', edge.id);
});
// 鼠标进入/离开
graph.on('edge:mouseenter', ({ edge }) => {
edge.attr('line/stroke', '#1890ff');
edge.attr('line/strokeWidth', 2);
});
graph.on('edge:mouseleave', ({ edge }) => {
edge.attr('line/stroke', '#8f8f8f');
edge.attr('line/strokeWidth', 1);
});
// 连线完成
graph.on('edge:connected', ({ edge, isNew }) => {
if (isNew) {
console.log('New edge created:', edge.id);
}
});画布事件
// 点击空白区域
graph.on('blank:click', ({ e }) => {
// 取消选择
graph.cleanSelection();
});
// 画布缩放
graph.on('scale', ({ sx, sy }) => {
console.log('Scale:', sx, sy);
});
// 画布平移
graph.on('translate', ({ tx, ty }) => {
console.log('Translate:', tx, ty);
});元素变更事件
// 节点/边被添加
graph.on('cell:added', ({ cell }) => {
console.log('Added:', cell.id, cell.isNode() ? 'node' : 'edge');
});
// 节点/边被删除
graph.on('cell:removed', ({ cell }) => {
console.log('Removed:', cell.id);
});
// 属性变更
graph.on('cell:changed', ({ cell, options }) => {
console.log('Changed:', cell.id);
});Selection 事件
// 选中变化(需要启用 selecting 插件)
graph.on('selection:changed', ({ added, removed, selected }) => {
console.log('Selected nodes:', selected.length);
added.forEach(cell => cell.attr('body/stroke', '#1890ff'));
removed.forEach(cell => cell.attr('body/stroke', '#8f8f8f'));
});History 事件
// 撤销/重做(需要启用 history 插件)
graph.on('history:undo', () => {
console.log('Undo performed');
});
graph.on('history:redo', () => {
console.log('Redo performed');
});事件管理
// 监听一次
graph.once('node:click', ({ node }) => { ... });
// 移除监听
const handler = ({ node }) => { ... };
graph.on('node:click', handler);
graph.off('node:click', handler);
// 移除所有监听
graph.off('node:click');常用事件模式
节点状态切换
graph.on('node:click', ({ node }) => {
const data = node.getData() || {};
const isActive = !data.active;
node.setData({ active: isActive });
node.attr('body/fill', isActive ? '#e6f7ff' : '#fff');
node.attr('body/stroke', isActive ? '#1890ff' : '#8f8f8f');
});高亮相邻节点
graph.on('node:click', ({ node }) => {
// 重置所有节点样式
graph.getNodes().forEach(n => {
n.attr('body/fill', '#fff');
});
// 高亮当前节点
node.attr('body/fill', '#e6f7ff');
// 高亮相邻节点
const neighbors = graph.getNeighbors(node);
neighbors.forEach(n => {
n.attr('body/fill', '#d9f7be');
});
});删除选中元素
graph.on('blank:click', () => {
graph.cleanSelection();
});
// 配合 keyboard 插件
graph.bindKey('delete', () => {
const cells = graph.getSelectedCells();
if (cells.length) {
graph.removeCells(cells);
}
});最小可运行示例
import { Graph } from '@antv/x6'
// 创建画布
const graph = new Graph({
container: document.getElementById('container'),
width: 800,
height: 600,
background: { color: '#F2F7FA' },
grid: { visible: true },
})
// 监听画布事件
graph.on('blank:click', ({ e }) => {
console.log('点击空白区域')
})
graph.on('cell:added', ({ cell }) => {
console.log('添加元素:', cell.id)
})
graph.on('cell:removed', ({ cell }) => {
console.log('删除元素:', cell.id)
})
// 添加节点
graph.addNode({
shape: 'rect',
x: 100,
y: 80,
width: 100,
height: 40,
label: 'Node 1',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6
}
}
})
// 监听节点事件
graph.on('node:mouseenter', ({ node }) => {
node.attr('body/stroke', '#1890ff')
node.attr('body/strokeWidth', 2)
})
graph.on('node:mouseleave', ({ node }) => {
node.attr('body/stroke', '#8f8f8f')
node.attr('body/strokeWidth', 1)
})常见错误与修正
错误:Selection 构造函数使用错误
// ❌ 错误:直接使用 new Selection()
graph.use(new Selection({ enabled: true, rubberband: true }));
// ✅ 正确:使用 graph.use() 并正确配置插件
import { Selection } from '@antv/x6-plugin-selection'
graph.use(new Selection({ enabled: true, rubberband: true }))错误:插件初始化方式错误
// ❌ 错误:使用 plugins 数组初始化插件
const graph = new Graph({
plugins: [
new Selection(),
new Snapline(),
new Keyboard(),
new Clipboard(),
new History()
]
});
// ✅ 正确:使用 graph.use() 方法初始化插件
import { Selection, Snapline, History } from '@antv/x6-plugin-selection'
graph.use(new Selection({ enabled: true, rubberband: true }));
graph.use(new Snapline({ enabled: true }));
graph.use(new History({ enabled: true }));错误:节点注册方式错误
// ❌ 错误:使用 graph.registerNode 注册节点
graph.registerNode('start-event', {
// ...
}, true);
// ✅ 正确:直接使用内置 shape 或通过继承创建节点
const start = graph.addNode({
shape: 'circle',
x: 80,
y: 200,
width: 40,
height: 40,
attrs: {
body: { fill: '#52c41a', stroke: '#389e0d' },
label: { text: 'Start', fill: '#fff', fontSize: 11 }
},
ports: {
groups: {
out: {
position: 'right',
attrs: {
circle: { r: 4, magnet: true, stroke: '#52c41a', fill: '#fff' }
}
}
},
items: [{ id: 'out', group: 'out' }]
}
});错误:创建边时未正确绑定上下文
// ❌ 错误:在 createEdge 中使用 graph.createEdge
connecting: {
createEdge() {
return graph.createEdge({ ... }); // 错误:this 指向问题
}
}
// ✅ 正确:使用 this.createEdge
connecting: {
createEdge() {
return this.createEdge({ ... }); // 正确:this 指向 graph 实例
}
}错误:节点属性设置不完整导致渲染异常
// ❌ 错误:缺少必要的属性设置
const start = graph.addNode({
shape: 'circle',
x: 80,
y: 200,
width: 40,
height: 40
});
// ✅ 正确:设置完整的节点属性
const start = graph.addNode({
shape: 'circle',
x: 80,
y: 200,
width: 40,
height: 40,
attrs: {
body: { fill: '#52c41a', stroke: '#389e0d' },
label: { text: 'Start', fill: '#fff', fontSize: 11 }
},
ports: {
groups: {
out: {
position: 'right',
attrs: {
circle: { r: 4, magnet: true, stroke: '#52c41a', fill: '#fff' }
}
}
},
items: [{ id: 'out', group: 'out' }]
}
});基本用法
通过节点/边的 attrs 中 filter 属性使用内置滤镜:
graph.addNode({
x: 100,
y: 100,
width: 120,
height: 60,
attrs: {
body: {
fill: '#fff',
stroke: '#5F95FF',
filter: {
name: 'dropShadow',
args: { dx: 2, dy: 2, blur: 3, color: 'rgba(0,0,0,0.2)' },
},
},
},
});内置滤镜列表
dropShadow(阴影)
为元素添加投影阴影:
attrs: {
body: {
filter: {
name: 'dropShadow',
args: {
dx: 2, // 水平偏移,默认 0
dy: 2, // 垂直偏移,默认 0
blur: 4, // 模糊半径,默认 4
color: 'black', // 阴影颜色,默认 'black'
opacity: 0.3, // 阴影透明度,默认 1
},
},
},
}outline(外描边)
在元素外围添加一圈描边(不影响元素本身):
attrs: {
body: {
filter: {
name: 'outline',
args: {
color: 'blue', // 描边颜色,默认 'blue'
width: 2, // 描边宽度,默认 1
margin: 3, // 描边与元素的间距,默认 2
opacity: 1, // 描边透明度,默认 1
},
},
},
}highlight(高亮光晕)
在元素外围添加发光效果:
attrs: {
body: {
filter: {
name: 'highlight',
args: {
color: 'red', // 高亮颜色,默认 'red'
width: 2, // 高亮扩展宽度,默认 1
blur: 5, // 模糊半径,默认 0
opacity: 0.8, // 高亮透明度,默认 1
},
},
},
}blur(高斯模糊)
attrs: {
body: {
filter: {
name: 'blur',
args: {
x: 3, // 水平模糊量,默认 2
y: 3, // 垂直模糊量(可选,默认与 x 相同)
},
},
},
}grayScale(灰度)
attrs: {
body: {
filter: {
name: 'grayScale',
args: {
amount: 1, // 灰度程度,0~1,1 为完全灰度
},
},
},
}sepia(褐色/复古)
attrs: {
body: {
filter: {
name: 'sepia',
args: {
amount: 1, // 0~1
},
},
},
}saturate(饱和度)
attrs: {
body: {
filter: {
name: 'saturate',
args: {
amount: 0.5, // < 1 降低饱和度,> 1 增加饱和度
},
},
},
}hueRotate(色相旋转)
attrs: {
body: {
filter: {
name: 'hueRotate',
args: {
angle: 90, // 旋转角度(度)
},
},
},
}invert(反色)
attrs: {
body: {
filter: {
name: 'invert',
args: {
amount: 1, // 0~1,1 为完全反色
},
},
},
}brightness(亮度)
attrs: {
body: {
filter: {
name: 'brightness',
args: {
amount: 1.5, // < 1 变暗,> 1 变亮
},
},
},
}contrast(对比度)
attrs: {
body: {
filter: {
name: 'contrast',
args: {
amount: 2, // < 1 降低对比度,> 1 增加对比度
},
},
},
}动态添加/移除滤镜
const node = graph.addNode({
x: 100, y: 100, width: 120, height: 60,
attrs: { body: { fill: '#EFF4FF', stroke: '#5F95FF' } },
});
// 鼠标悬停时添加阴影
graph.on('node:mouseenter', ({ node }) => {
node.attr('body/filter', {
name: 'dropShadow',
args: { dx: 0, dy: 4, blur: 8, color: 'rgba(0,0,0,0.15)' },
});
});
// 鼠标离开时移除滤镜
graph.on('node:mouseleave', ({ node }) => {
node.attr('body/filter', null);
});禁用状态示例
使用灰度滤镜表示节点"禁用":
function setNodeDisabled(node, disabled) {
if (disabled) {
node.attr('body/filter', { name: 'grayScale', args: { amount: 1 } });
node.attr('body/opacity', 0.6);
} else {
node.attr('body/filter', null);
node.attr('body/opacity', 1);
}
}常见错误
❌ filter 直接写 CSS filter 字符串
// 错误:不支持 CSS filter 字符串语法
attrs: {
body: {
filter: 'drop-shadow(2px 2px 4px rgba(0,0,0,0.3))', // ❌
},
}// 正确:使用 X6 的对象语法
attrs: {
body: {
filter: {
name: 'dropShadow',
args: { dx: 2, dy: 2, blur: 4, color: 'rgba(0,0,0,0.3)' },
}, // ✅
},
}❌ 滤镜名称拼写错误
// 错误:名称拼写不正确
filter: { name: 'drop-shadow', args: {...} } // ❌ 应为 'dropShadow'
filter: { name: 'grayscale', args: {...} } // ❌ 应为 'grayScale'
filter: { name: 'hue-rotate', args: {...} } // ❌ 应为 'hueRotate'正确的滤镜名称(驼峰命名):dropShadow、grayScale、hueRotate
基本用法
网格在 Graph 构造函数中通过 grid 字段配置:
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
grid: {
visible: true,
size: 10, // 网格步长(像素)
},
});配置项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
visible | boolean | false | 是否显示网格 |
size | number | 10 | 网格步长(节点移动时吸附到的最小间隔) |
type | string | 'dot' | 网格类型:'dot'、'fixedDot'、'mesh'、'doubleMesh' |
args | object | - | 网格类型对应的参数 |
注意:即使 visible: false,size 仍然生效——节点拖拽时会吸附到 size 为步长的网格点上。
网格类型
dot(点阵,默认)
显示为均匀分布的点,缩放时点的大小随之变化:
const graph = new Graph({
container: 'container',
grid: {
visible: true,
size: 10,
type: 'dot',
args: {
color: '#aaaaaa', // 点的颜色
thickness: 1, // 点的大小
},
},
});fixedDot(固定点阵)
与 dot 类似,但缩放比例 ≤ 1 时点的大小保持不变(不会太小看不清):
const graph = new Graph({
container: 'container',
grid: {
visible: true,
size: 10,
type: 'fixedDot',
args: {
color: '#aaaaaa',
thickness: 2,
},
},
});mesh(网格线)
显示为交叉网格线:
const graph = new Graph({
container: 'container',
grid: {
visible: true,
size: 10,
type: 'mesh',
args: {
color: 'rgba(224, 224, 224, 1)', // 线条颜色
thickness: 1, // 线条粗细
},
},
});doubleMesh(双层网格)
显示两层网格线——主网格和次网格,次网格通过 factor 倍数放大间距:
const graph = new Graph({
container: 'container',
grid: {
visible: true,
size: 10,
type: 'doubleMesh',
args: [
// 第一层:细密网格
{
color: 'rgba(224, 224, 224, 1)',
thickness: 1,
},
// 第二层:粗疏网格(间距 = size * factor)
{
color: 'rgba(224, 224, 224, 0.2)',
thickness: 3,
factor: 4, // 间距为基础 size 的 4 倍
},
],
},
});编程式 API
// 获取网格步长
graph.getGridSize(); // number
// 设置网格步长
graph.setGridSize(20);
// 显示网格
graph.showGrid();
// 隐藏网格
graph.hideGrid();
// 重新绘制网格(切换类型)
graph.drawGrid({
type: 'mesh',
args: { color: '#ddd', thickness: 1 },
});完整示例
import { Graph } from '@antv/x6';
const graph = new Graph({
container: 'container',
width: 800,
height: 600,
grid: {
visible: true,
size: 20,
type: 'doubleMesh',
args: [
{ color: '#eee', thickness: 1 },
{ color: '#ddd', thickness: 1, factor: 4 },
],
},
});
// 节点会自动吸附到 20px 步长的网格点
graph.addNode({
x: 100, // 实际位置会吸附到 size 的整数倍
y: 100,
width: 80,
height: 40,
label: 'Snaps to grid',
});常见错误
❌ 混淆 size 和 visible
// 错误理解:以为 visible: false 就没有网格效果
const graph = new Graph({
container: 'container',
grid: { visible: false, size: 20 },
});
// 实际上节点拖拽时仍会吸附到 20px 网格!❌ doubleMesh 的 args 传对象而非数组
// 错误:doubleMesh 的 args 必须是数组
grid: {
type: 'doubleMesh',
args: { color: '#eee', thickness: 1 }, // ❌ 应为数组
}
// 正确
grid: {
type: 'doubleMesh',
args: [
{ color: '#eee', thickness: 1 },
{ color: '#ddd', thickness: 1, factor: 4 },
], // ✅
}