官网:AntV X6
开发背景:业务想要开发一组类似下图的流程图组件
要求:
- 可创建多个根节点
- 节点可以衍生子节点 可展开收起 配置最大展开层级
- 节点内容可以编辑字体颜色(可选中部分文字修改)、节点编号
- 每个节点可创建多个标注 内容编辑效果同节点 标注可自定义宽高
- 节点拖拽可实现排序和切换父级
此处省略挣扎部分 后续有心情再补充哈 — 比较痛苦 边敲边pua
记录是关注 x6自定义节点
import {Graph, Path, Node, Markup, Cell, CellView, DataUri} from '@ant/x6'
import 'antv/x6-vue-shape'
import Hierarchy from '@antv/hierarchy'
/*注册节点*/
registerNode(){
let _this = this
// 标注
Graph.registerNode(
'mark',
{
inherit: 'rect',
markup: [
{
tagName: 'rect',
selector: 'body'
}, {
tagName: 'text',
selector: 'label'
}
],
attrs: {
body: {
rx: 16,
ry: 16,
stroke: '#5f95ff',
fill: '#f0f0f0',
strokeWidth: 1
},
label: {
fontSize: 14,
fill: '#262626'
}
}
},
true
)
// 连接器
Graph.registerConnector(
'mindmap',
(sourcePoint, targetPoint, routerPoints, options)=>{
const midX = sourcePoint.x + 10
const midY = sourcePoint.y
const ctrX = (targetPoint.x - midX) / 5 + midX
const ctrY = targetPoint.y
const pathData = `
M ${sourcePoint.x} ${sourcePoint.y}
L ${midX} ${midY}
Q ${ctrX} ${ctrY} ${targetPoint.x} ${targetPoint.y}
`
return options.raw ? Path
},
true
)
// 边
Graph.registereEdge(
'mindmao-edge',
{
inherit: 'edge',
connector: {
name: 'mindmap'
},
attrs: {
line: {
targetMarker: '',
stroke: '#a2b1c3',
strokeWidth: 2
}
},
zIndex: 0,
data: {
onlyId: null
}
},
true
)
// 标注组件
Graph.registerVueComponent('annotation',{
template: '<annotation @update="update"></annotation>',
components: { Annotation },
methods: {
update(label, data){
let Id = data.targetId || ''
let index = _this.marksData[data.sourceId].findIndex(it => it.id == Id)
if(index != -1)
_this.marksData[data.sourceId][index].data.label = label
const cell = _this.graph.getCellById(Id)
cell && cell.updateData({label: label})
}
}
})
// 自定义节点
Graph.registereVueComponent('nodediy', {
template: '<nodediy @collapse="collapse" @expand="expand"></nodediy>',
components: { Nodediy },
methods: {
// 收起
collapse(nodeId){
const res = _this.findItem(_this.data, nodeId)
const dataItem = res
if(dataItem){
dataItem.node.isExpand = false
}
_this.renderTree() // 加载页面树
},
// 展开
expand(nodeId){
const res = _this.findItem(_this.data, nodeId)
const dataItem = res
if(dataItem){
dataItem.node.isExpand = true
}
_this.renderTree()
}
}
})
}
/*节点挂载*/
initData(){
this.graph && this.graph.dispose()
let _this = this
this.graph = new Graph({
container: document.getElementById('mindmap-container'),
width: document.getElementById('content').offsetWidth || 1350,
height: document.getElementById('content').offsetHeight || 777,
connecting: {
connectionPoint: 'anchor',
},
scroller: true,
panning: {
enabled: true
},
selection: {
enabled: true
},
keyboard: {
enabled: true
},
embedding: {
enabled: true,
findParent({node}){
const bbox = node.getBBox()
return this.getNodes().filter(sunNode => {
const data = sunNode.getData()
const sunBbox = sunNode.getBBox()
return bbox.isIntersectWidthRect(sunBbox)
})
}
},
resizing: {
minWidth: 120,
height: 40,
enabled: (node) => {
if(node.component === 'annotation')
return true
else
return false
}
}
})
// 标注缩放
this.graph.on('node:resized', ({e, x, y, node, view}) => {
let Id = node.id || ''
let {width, height} = node.size()
let index = _this.marksData[node.data.sourceId].findIndex(it => it.id == Id)
if(index != -1){
_this.marksData[node.data.sourceId][index].x = node.position().x
_this.marksData[node.data.sourceId][index].y = node.position().y
_this.marksData[node.data.sourceId][index].data.width = width
_this.marksData[node.data.sourceId][index].data.height = height
}
})
// 节点单击
this.graph.on('node:click', ({cell e}) => {
this.graph.resetSelection(cell)
if(e.target.className.indexOf('close-icon') != -1 || e.target.className.indexOf('right-icon') != -1){
this.$refs.btnGroupRef.close()
return
}
let pageX = e.pageX
let pageY = e.pageY
let width = cell.getData().width || 160
let height = cell.getData().height || 40
pageX -= e.offsetX - width
pageY -= e.offsetY -+ 28
const offsetHeight = document.body.offsetHeight
const offsetWidth = document.body.offsetWidth
if(offsetWidth - pageX <= 200){
pageX -= 200
}
if(e.target.className === 'innode-div-text'){
if(cell.getData().rank === 0 || !cell.getData().rank || cell.getData().parentId == 'hide-root'){
pageX += 12
}
pageX -= 20
pageY -= (height - 16)*0.5
}
this.tooltipStyle = {
left: pageX + 'px',
top: pageY + 'px'
}
this.editInfo = cell.getData()
this.$refs.btnGroupRef.open(this.editInfo, this.tooltipStyle)
})
this.graph.on('node:moved', data => {
let node = data.node
if(node && node.component && node.componet ==='annotation' && _this.marksData[node.data.sourceId]){
let Id = node.data.targetId || ''
let {x,y} = _this.graph.getCellByID(Id).position()
let index = _this.marksData[node.data.sourceId].findIndex(it => it.id == Id)
if(index != -1){
_this.marksData[node.data.sourceId][index].x = x || data.x
_this.marksData[node.data.sourceId][index].y = y || data.y
}
}
})
this.graph.on('node:embedded', data => {
let {currentParent, node} = node
// 嵌入
if(currentParent && node){
let data_a = currentParent.getData()
let data_b = node.getData()
// 标注拖拽 要删除当前标注 并新增标注 marksData同步修改
if(data_a && data_b && node.component === 'annotation' && currentParent.component === 'nodediy'){
let nodeId = currentParent.id
let markId = node.id
_this.graph.renoveCells(markId)
_this.delTheMarks(data_b)
let x = data.x - 40
let y = data.y - 60
let _obj = {
id: markId,
x,
y,
data: {
...data_b,
targetId: markId,
sourceId: nodeId
}
}
let nowMarks = _this.marksData[nodeId] || []
nowMarks.push(_obj)
_this.marksData[nodeId] = nowMarks
_this.graph.addCell(_this.renderMarksByObj([_obj]))
}else if(data_a && data_b){ // 节点拖拽
const dataItem = _this.findItem(_this.data, data_b.a)
if(!dataItem) return
const {children} = dataItem.parent
const index = children.findIndex((item) => item.id == data_b.id)
children.splice(index, 1)
const res = _this.findIndex(_this.data, data_a.id)
if(res){
let len = res.node.children.length || 0
let newLeaf = _this.resetDataLevel([data_b], res.node.level)
res.node.children.push({...newLeaf[0], parentId: data_a.id})
_this.graph.clearCells()
_this.$nextTick(()=>{
_this.renderTree()
})
}
}
}else if(node.compoent == 'nodediy'){ // 排序
let {parentId, id} = data.cell.getData()
const res = this.findItem(_this.data, parentId)
const dataItem = res
if(!dataItem) return
const index = dataItem.node.children.findIndex(item => item.id === id)
let y0 = data.cell.position().y
let child = res.node.children
let j = null
if (child.length > 1) {
for(let i = 0; i < child.length; i++){
let y1 = 0
if(_this.graph.getCellById(child[i].id)){
y1 = _this.graph.getCellById(child[i].id).position().y
}
let y2 = null
if(i < child.length -1){
y2 = _this.graph.getCellById(child[i+1].id).position().y
}
if(y0>y1 && (y0<y2 || i == child.length - 1)){
j = i < index ? i + 1 : i
break;
} else if(y0<y1 && y0 === y2){
j = i
break;
}
}
if(j !== null){
const swapLine = dataItem.node.children.splice(index, 1)[0]
dataItem.node.children.splice(j, 0, swapLine)
_this.graph.clearCells()
_this.$nextTick(()=>{
_this.rendreTree()
})
}
}
}
})
}
/*渲染数据*/
rendereTree(translate){
let data = this.data
if(!data.children || data.children.length == 0){
this.graph.clearCells()
return
}
let _this = this
const result = Hierarchy.mindmap(data, {
direction: 'H',
getHeight(d){
return d.height
},
getWidth(d){
return d.width
},
getChildren(d){
if(d.id !== 'hide-root' && d.children && d.children.length > 0 && !d.isExpand){
return []
}else{
return d.children
}
},
getHGap(){
return 60
},
getVGap(){
return 25
},
getSide: ()=>{
return 'right'
}
})
let cells = []
const traverse = (hierarchyItem, index) => {
if(hierarchyItem){
const {data, children} = hierarchyItem
if(data.id !== 'hide-root'){
cells.push(
this.graph.createNOde({
id: data.id,
shape: 'vue-shape',
x: data.x || hierarchyItem.x,
y: data.y || hierarchyItem.y,
width: data.level == 1? 140: 120,
height: data.level == 1? 50: data.level == 2? 40: 30,
lable: data.label,
type: data.type,
data: {...data, rank: index},
component: 'nodediy'
})
)
let markObj = _this.marksData[data.id] || null
if(markObj){
cells = cells.concat(this.renderMarksByObj(markobj))
}
}
let _isExpand = data.isExpand? true: false
if(children && (_isExpand || data.id ==='hide-root')){
children.forEach((item, index) => {
const {id, data} = item
let text = this.edgesData[hieratchyItem.id + '_' + id] || ''
if(hierarchyItem.id !== 'hide-root'){
cells.push(
this.graph.createEdge({
shape: 'mindmap-edge',
id: hierarchyItem.id + '_' + id] || '',
labels: [{
attrs: {label: {text: text}},
position: {distance: 0.5}
}],
source: {
cell: hierarchyItem.id,
anchor:{nameL 'right'}
},
target: {
cell: id,
anchor: {name: 'right'}
}
})
)
}
let _rank = children.length ===1? 0: index+1 // 无同级节点不显示编号
traverse(item, _rank)
})
}
}
}
travekse(result)
this.graph.resetCells(cells)
if(translate){
this.graph.positionContent('left',{padding: {left: 50}})
}
}