用于实现流程图,可进行交互。本文记录的是
vue的用法。
安装
建议使用
yarn安装哈,一开始我也是贪cnpm速度快一点,结果安装不了x6-vue-shape
# yarn
$ yarn add @antv/x6
# yarn
yarn add @antv/x6-vue-shape
# 在 vue2 下还需要安装 @vue/composition-api
yarn add @vue/composition-api --dev
1.创建画布
keyboard开启监听键盘selecting设置选择节点,可通过filter去过滤。我只需要选择连接线(x6中叫边Edge)。graph.isEdge(node)判断传入的节点是否为边(Edge)scroller使用scroller模式,超出container会出现滚动条可移动查看节点node,pannable是否启用画布平移能力translating配置移动选项,我这里限制了子组件不可以拖动,限制在父组件里面,只能移动父组件
import '@antv/x6-vue-shape'
import { Graph, Line, Path, Curve, Addon } from '@antv/x6'
export default {
data () {
return {
graph: null
}
},
}
// 创建画布
this.graph = new Graph({
container: document.getElementById('drag-container'),
keyboard: true,
scroller: {
enabled: true,
pannable: true
},
selecting: {
enabled: true,
rubberband: true,
filter (node) {
// 只选连接线(边)
return that.graph.isEdge(node)
}
},
translating: {
restrict (view) {
const cell = view.cell
if (cell.isNode()) {
const parent = cell.getParent()
if (parent) {
// 限制子节点不能移动
return {
x: cell.getBBox().x,
y: cell.getBBox().y,
width: 0,
height: 0
}
}
}
return null
}
}
})
2.注册节点
我的节点都是用vue组件写的,并不是用原生svg的一些图形(circle、rect),实际开发的需求肯定不是这么简单,vue组件必须用registerVueComponent注册,否则后面做回显时,使用不了toJSON和fromJSON。回显时使用这两个api,不需要任何的操作,渲染即可
import Drag from '@/components/drag/drag.vue'
import Item from '@/components/drag/item.vue'
const that = this
// 注册父组件
Graph.registerVueComponent(
'Drag',
{
template: '<drag @parentnoderemove="parentnoderemove"></drag>',
methods: {
parentnoderemove ({ id }) {
// 删除对应的节点
that.saveNodes.splice(that.saveNodes.findIndex(item => item.id === id), 1)
}
},
components: {
Drag
}
},
true
)
// 注册子组件
Graph.registerVueComponent(
'Item',
{
template: '<item @edge="edge"></item>',
methods: {
// 两个节点之间连线
edge: that.edge
},
components: {
Item
}
},
true
)
3.生成节点
我这里用到了拖拽生成节点,对于拖拽,x6提供了Dnd。首先对Dnd进行创建配置
getDropNode配置拖拽结束,创建节点之前执行的函数,这个函数一定要return node节点
// 拖拽
this.dnd = new Addon.Dnd({
target: this.graph, // 画布对象
scaled: false,
animation: true,
getDropNode: that.handleEndDrag
})
- 对开始拖拽的目标节点
<div>添加mousedown事件去执行Dnd.start事件即可,start函数要传入mousedown事件的event对象
<template>
<div>
<div class="side-title">• 统计对象</div>
<div
class="side-item"
v-for="(item, index) in leftSide"
:key="index"
@mousedown="handleDrag(item, $event)"
>
{{item.title}}
</div>
</div>
</template>
methods
创建节点需要提交x、y坐标值,所以在handleEndDrag事件中我先让返回的父组件渲染完成后,获取父组件的x、y的值,再创建子节点。
<script>
export default {
methods: {
// 开始拖拽
handleDrag (item, e, weidu) {
// 业务逻辑
// item请求。。
this.createChildren = [1, 2, 3] // item请求回来的数据
this.handleCreateNode(item, e)
},
handleCreateNode (item, e) {
const that = this
// 创建父节点
const parent = this.graph.createNode({
shape: 'vue-shape',
x: 100,
y: 100,
height: that.createChildren.length * 60 + 58, // 根据实际的UI样式来
data: {
item,
height: that.createChildren.length * 60 + 58
},
component: 'Drag' // 这个名字对应registerVueComponent的名字
})
// 开始拖拽
this.dnd.start(parent, e)
},
// 拖拽结束,渲染节点之前,必须返回克隆的节点
handleEndDrag (node) {
const cloneNode = node.clone({ deep: true })
const that = this
// 父节点渲染之后再执行,因为需要父节点的位置
this.$nextTick(() => {
const { x, y } = cloneNode.position()
// 是否第一个节点
const cellCount = that.graph.getCellCount()
this.createChildren.forEach((item, index) => {
const child = this.graph.addNode({
shape: 'vue-shape',
x: x + 20,
y: index === 0 ? y + 58 : y + (index * 60 + 58),
width: 240,
height: 46,
data: {
item
},
component: 'Item'
})
cloneNode.addChild(child) // 添加到父节点
})
this.saveNodes.push(cloneNode)
if (cellCount === 1) {
this.firstNode = cloneNode
that.$message.warning('第一个统计对象为主体')
}
})
return cloneNode
}
}
}
</script>
4.两个节点之间连线(创建边)
需求是点击两个node,就让它们连线
点击的事件我放在了item组件里,点击后触发父组件这边的edge方法
connector决定你的线是怎样的,我这边是圆弧
// 连线规则,圆弧
Graph.registerConnector( // Graph不是创建的画布实例(this.graph)!!!
'smooth',
(
sourcePoint,
targetPoint,
routePoints,
options
) => {
const line = new Line(sourcePoint, targetPoint)
const diff = 5
const factor = 1
const vertice = line
.pointAtLength(line.length() / 2 + 12 * factor * Math.ceil(diff))
.rotate(90, line.getCenter())
const points = [sourcePoint, vertice, targetPoint]
const curves = Curve.throughPoints(points)
const path = new Path(curves)
return options.raw ? path : path.serialize()
},
true
)
// 两个node之间连线
edge (node) {
// console.log('node', node.id)
// console.log('AllEdges', this.graph.getEdges(node))
const allEdges = this.graph.getEdges(node)
this.waitEdgeNodes.push(node)
if (this.waitEdgeNodes.length === 2) {
// 改变active状态
this.$store.dispatch('callNodes', this.waitEdgeNodes.map(item => item.id))
// 判断不是同一父级
if (this.waitEdgeNodes[0]._parent.id !== this.waitEdgeNodes[1]._parent.id) {
// 要连线的目标id和来源id
const allTargetAndAllSource = allEdges.map((item) => [
item.getTargetCellId(),
item.getSourceCellId()
])
const flag = allTargetAndAllSource.filter(item =>
item.includes(this.waitEdgeNodes[0].id) && item.includes(this.waitEdgeNodes[1].id
))
// 如果两个点已经连过线,
if (flag.length) return (this.waitEdgeNodes.length = 0)
// 这里通过坐标决定连线的点
const sourceAnchor = this.waitEdgeNodes[0].getBBox().x < this.waitEdgeNodes[1].getBBox().x ? 'right' : 'left'
const targetAnchor = this.waitEdgeNodes[0].getBBox().x < this.waitEdgeNodes[1].getBBox().x ? 'left' : 'right'
// 设置箭头的大小
const args = {
size: 8
}
this.graph.addEdge({
source: { cell: this.waitEdgeNodes[0], anchor: sourceAnchor, connectionPoint: 'anchor' },
target: { cell: this.waitEdgeNodes[1], anchor: targetAnchor, connectionPoint: 'anchor' },
connector: { name: 'smooth' },
attrs: {
line: {
strokeDasharray: '5 5',
stroke: '#666',
strokeWidth: 1,
sourceMarker: {
args,
name: 'block' // 实心箭头
},
targetMarker: {
args,
name: 'block'
}
}
}
})
}
// 无论如何都清空
this.waitEdgeNodes.length = 0
}
},
5.选择连接线,并监听键盘删除键进行删除
因为一开始的配置就限制了只能选择edge,所以这里不用判断其他的cell
这部分主要是更改样式
// 选择连接线(边)事件
this.graph.on('selection:changed', ({ added, removed }) => {
this.selectLine = added
added.forEach((cell) => {
const args = { size: 15 }
cell.setAttrs({
line: {
sourceMarker: {
args,
name: 'block'
},
targetMarker: {
args,
name: 'block'
},
stroke: '#2D8CF0',
strokeWidth: 4
}
})
})
removed.forEach((cell) => {
const args = { size: 8 }
cell.setAttrs({
line: {
sourceMarker: {
args,
name: 'block'
},
targetMarker: {
args,
name: 'block'
},
stroke: '#666',
strokeWidth: 1
}
})
})
})
监听删除键删除
通过cell.remove方法删除
// 删除连接线(边)
this.graph.bindKey(['Backspace', 'Delete'], (e) => {
if (this.selectLine.length) {
this.$confirm('确认删除连线吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.selectLine[0].remove()
this.$message({
type: 'success',
message: '删除成功!'
})
})
}
})
点个赞支持一下吧🙏🏻