AntV X6 技术分享
[TOC]
一、本文目的
对 AntV x6 知识点的一个总结,也为其他开发同学提供一个说明文档。
二、知识要点
1、简介
X6 是 AntV 旗下的图编辑引擎,提供了一系列开箱即用的交互组件和简单易用的节点定制能力,方便我们快速搭建流程图、DAG 图、ER 图等图应用。
2、安装
npm i @antv/x6 -S
yarn add @antv/6</pre>
3、实例代码
import { Graph } from '@antv/x6';
const graph = new Graph({
container: document.getElementById('container'),
width: 800,
height: 600,
});
4、画布 Graph
在 X6 中,Graph 是图的载体,它包含了图上的所有元素(节点、边等),同时挂载了图的相关操作(如交互监听、元素操作、渲染等)。
graph.dispose() // 销毁画布
graph.centerContent() //内容居中,画布内容中心与视口中心对齐
graph.zoom() // 获取缩放级别
graph.zoom(0.2) // 在原来缩放级别上增加 0.2
graph.zoom(-0.2) // 在原来缩放级别上减少 0.2
graph.getCells() // 返回画布中所有节点和边
graph.getNodes() // 返回画布中所有节点
graph.getEdges() // 返回画布中所有边
graph.toJSON() // 导出图中的节点和边
graph.fromJSON() // 反序列化 按照指定的 JSON 数据渲染节点和边。
graph.clearCells() // 清空画布
graph.resetCells() // 清空画布并添加用指定的节点/边
graph.getCellById() 根据节点/边的 ID 获取节点/边。</pre>
b. 画布反序列化
const data = {
// 节点
nodes: [
{
id: 'node1',
x: 40,
y: 40,
width: 80,
height: 40,
label: 'Hello'
},
{
id: 'node2',
x: 160,
y: 180,
width: 80,
height: 40,
label: 'World'
}
],
// 边
edges: [
{
source: 'node1',
target: 'node2'
}
]
}
graph.fromJSON(data)</pre>
5、节点 Node
Node 是所有节点的基类,继承自 Cell,并定义了节点的通用属性和方法。
a. 常用方法:
node.isNode() // 判断是不是节点
node.getBBox() // 获取节点的包围盒
node.size() // 获取节点大小
node.resize() // 改变节点大小
node.scale() // 缩放节点
node.position() // 获取节点位置
node.position(30, 30) // 设置节点位置
b. 创建内置节点
// 方式一:构造函数
import { Shape } from '@antv/x6'
// 创建节点
const rect = new Shape.Rect({
x: 100,
y: 200,
width: 80,
height: 40,
angle: 30,
attrs: {
body: {
fill: 'blue',
},
label: {
text: 'Hello',
fill: 'white',
},
},
})
graph.addNode(rect)
// 方式二:graph.addNode
const rect = graph.addNode({
shape: 'rect', // 指定使用何种图形,默认值为 'rect'
x: 100,
y: 200,
width: 80,
height: 40,
angle: 30,
attrs: {
body: {
fill: 'blue',
},
label: {
text: 'Hello',
fill: 'white',
},
},
})
// 方式三:graph.createNode
const node = graph.createNode({
width: 75,
height: 38,
shape: 'html',
attrs: {
label: {
text: '开始', // 文本
style: {
visibility: 'hidden'
}
}
},
ports: {
groups,
items: startGroupItems
},
type: 'start',
nodeDescription: '开始节点描述',
html: 'start-html',
...startNodeSetting
});
this.dnd.start(node, e);</pre>
c. 注册自定义 HTML 节点
registerHTMLComponent() {
const startHtml = <div class="custom-node-wrap"> <div class="custom-node start-node"> <svg class="icon" aria-hidden="true" class="custom-node-body-img"> <use xlink:href="#iconkaishi"></use> </svg> <div class="text">开始</div> </div> </div>
;
const approvalHtml = <div class="custom-node-wrap"> <div class="custom-node approval-node"> <div class="custom-node-header approval-node-header"> <div class="custom-node-header-name approval-node-name">审批节点</div> <i class="custom-node-header-icon iconfont iconshenpi"></i> </div> <div class="custom-node-body"> <img class="custom-node-body-img" src="/static/images/avatar.png"/> </div> </div>
;
const aggregationHtml = <div class="custom-node-wrap"> <div class="custom-node aggregation-node"> <div class="custom-node-body aggregation-node-body"> <div class="custom-node-body-user aggregation-node-name">聚合节点</div> <svg class="icon" aria-hidden="true" class="custom-node-body-img"> <use xlink:href="#iconjuhejiedian"></use> </svg> </div> </div> </div>
;
const endHtml = <div class="custom-node-wrap"> <div class="custom-node end-node"> <svg class="icon" aria-hidden="true" class="custom-node-body-img"> <use xlink:href="#iconjieshu"></use> </svg> <div class="text">结束</div> </div> </div>
;
const branchHtml = <div class="branch-node-wrap"> <div class="branch-node"> <svg fill="#fff" width="110" height="54" version="1.1" xmlns="http://www.w3.org/2000/svg"> <polygon points="0,27 55,0 110,27 55,54"/> <g> <path d="M110,27 L55,54 Z" stroke="rgba(0, 0, 0, 0.1)" stroke-width="2" transform="translate(0, -1)"></path> <path d="M55,54 L0,27 Z" stroke="rgba(0, 0, 0, 0.1)" stroke-width="2" transform="translate(0, -1)"></path> </g> <use width="18px" height="18px" x="30" y="18" xlink:href="#iconfenzhijiedian-yuanxing"></use> </svg> </div> </div>
;
Graph.registerHTMLComponent('start-html', startHtml, true);
Graph.registerHTMLComponent('approval-html', approvalHtml, true);
Graph.registerHTMLComponent('aggregation-html', aggregationHtml, true);
Graph.registerHTMLComponent('end-html', endHtml, true);
Graph.registerHTMLComponent('branch-html', branchHtml, true);
}</pre>
d. 渲染 Vue 节点
npm install @antv/x6-vue-shape
在 vue2 下还需要安装 @vue/composition-api
import { Graph } from '@antv/x6'
import '@antv/x6-vue-shape'
import ServiceNode from './service-node.vue'
Graph.registerVueComponent(
'service-node',
{
template: '<service-node></service-node>',
components: {
ServiceNode
}
},
true
)
const graph = new Graph({
container: document.getElementById('graph')
})
const node1 = graph.addNode({
shape: "vue-shape",
x: 300,
y: 250,
component: "service-node"
});
const node2 = graph.addNode({
shape: "vue-shape",
x: 300,
y: 550,
component: "service-node"
});
graph.addEdge({
source: node1,
target: node2,
vertices: [
{ x: 300, y: 250 },
{ x: 300, y: 550 }
]
});
</pre>
6、边 Edge
Edge 是边的基类,继承自 Cell,并定义了边的通用属性和方法。
a. 连接到画布上的点const edge = new Shape.Edge({
source: { x: 40, y: 40 },
target: { x: 180, y: 80 },
})
b. 连接到节点/边
const edge = new Shape.Edge({
source: { cell: 'source-cell-id' },
target: { cell: 'target-cell-id' },
})
c. 连接到节点上的链接桩
const edge = new Shape.Edge({
source: { cell: 'source-cell-id', port: 'port-id' },
target: { cell: 'target-cell-id', port: 'port-id' },
})
d. 连接到节点上的某个元素
const edge = new Shape.Edge({
source: { cell: 'source-cell-id', selector: 'some-selector' },
target: { cell: 'target-cell-id', selector: 'some-selector' },
})
e. 自定义边:
import { Shape } from '@antv/x6';
export class TreeEdge extends Shape.Edge {
// ...
}
TreeEdge.config({
zIndex: 1
});
Edge.registry.register('tree-edge', TreeEdge, true);
f. 常用的方法有
edge.isEdge() // 判断是不是边
edge.getBBox() // 返回边的包围盒
edge.getSource() // 获取边的起始节点/起始点信息
edge.getTarget() // 获取边的终止节点/终止点信息
g. 箭头
// 内置箭头:https://x6.antv.vision/zh/docs/tutorial/intermediate/marker#%E5%86%85%E7%BD%AE%E7%AE%AD%E5%A4%B4
const markers = [
['block', { size: 6 }],
['classic', { size: 6 }],
['diamond', { size: 8 }],
['circle', { size: 6 }],
['circlePlus', { size: 6 }],
['ellipse', { rx: 6, ry: 4 }],
['cross', { size: 8, offset: 1 }],
['async', { size: 8, offset: 1 }],
]
markers.forEach(([marker, args], i) => {
graph.addEdge({
sourcePoint: [220, 30 + i * 40],
targetPoint: [500, 30 + i * 40],
label: marker,
attrs: {
line: {
sourceMarker: {
args,
name: marker,
},
targetMarker: {
args,
name: marker,
},
strokeWidth: 1,
},
},
})
})
7、基类 cell
Cell 是 Node 和 Edge 的基类,包含节点和边的通用属性和方法定义,如属性样式、可见性、业务数据等,并且在实例化、样式定制、默认选项、自定义选项等方面具有相同的行为。
a. 常用的方法有:
cell.isNode() // 监测是否是Node实例
cell.isEdge() // 监测是否是Edge实例
cell.attr() // 获取全部属性值
cell.attr('body/fill') // 获取某一属性值
cell.attr('body/fill', '#f5f5f5') // 设置某一属性值
cell.getProp().type // 获取指定的属性值。
cell.setProp('name', val); // 设置属性
cell.removeProp('zIndex'); // 删除单个属性
8、辅助工具
a. 自定义 tooltip 弹框
import { ToolsView, Graph } from '@antv/x6';
import insertCss from 'insert-css';
insertCss(.inner-box { box-sizing: border-box; width: 590px; z-index: 2; max-height: 175px; padding: 20px; color: rgba(255, 255, 255, 65); background-color: rgba(36, 40, 52, 1); box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.2); position: absolute; display: none; } .inner-box::before { width: 0; height: 0; content: ''; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 10px solid rgba(255, 255, 255, 0.2); position: absolute; top: -10px; left: 49px; } .inner-box::after { width: 0; height: 0; content: ''; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 8px solid rgba(36, 40, 52, 1); position: absolute; top: -8px; left: 50px; } .inner-box-main-title { font-size: 18px; font-weight: 600; color: rgba(65, 133, 255, 100); } .inner-box-main-text { font-size: 12px; line-height: 20px; }
);
export class TooltipTool extends ToolsView.ToolItem {
render() {
const dom = <div class="inner-box-main"> <div class="inner-box-main-title"></div> <p class="inner-box-main-text"></p> </div>
;
this.knob = ToolsView.createElement('div', false);
this.knob.setAttribute('class', 'inner-box');
this.knob.innerHTML = dom;
this.container.appendChild(this.knob);
this.updatePosition();
return this;
}
updatePosition() {
const cell = this.cell;
const { position, name, description } = cell.getProp();
if (description && name) {
const style = this.knob.style;
style.display = 'block';
style.left = ${position.x}px
;
style.top = ${position.y + 70}px
;
this.knob.querySelector('.inner-box-main-title').innerHTML = name;
this.knob.querySelector('.inner-box-main-text').innerHTML = description;
}
}
}
TooltipTool.config({
tagName: 'div',
isSVGElement: false
});
Graph.registerNodeTool('tooltip', TooltipTool, true);
// 页面实例化
new TooltipTool()
cell.addTools([{ name: 'tooltip' }]); // 添加tooltip
cell.removeTools(); // 删除tooltip