实现效果,如下图,边上存在两个图标,要求图标延边一直循环动画
实现方法:
1.注册一个自定义边,边上定义两个图标,并设置其初始位置
2.使用transition给边设置动画,利用complte方法实现循环动画
补充代码:
1.注册边的代码
Graph.registerEdge(
'arrow-edge',
{
markup: [
{
tagName: 'path',
selector: 'wrap',
attrs: {
fill: 'none',
cursor: 'pointer'
// stroke: '#ff0000'
}
},
{
tagName: 'path',
selector: 'line',
attrs: {
fill: 'none',
'pointer-events': 'none'
}
},
{
tagName: 'path',
groupSelector: 'arrow',
selector: 'arrow1'
},
{
tagName: 'path',
groupSelector: 'arrow',
selector: 'arrow2'
}
],
attrs: {
wrap: {
connection: true,
strokeWidth: 10,
strokeLinejoin: 'round',
style: {
animation: 'animation-line 30s infinite linear'
}
},
line: {
connection: true,
stroke: '#7eb2fb',
strokeLinejoin: 'round',
targetMarker: 'classic',
style: {
// animation: 'animation-line 30s infinite linear'
}
},
arrow: {
// d: 'M 0 2 2 1 4 0 2 -1 0 -2 -14 -1 -14 0 -14 1 z',
d: 'M 0 2 2 1 Q4 0 2 -1 0 -2 -24 -1 -24 0 -24 1 z',
fill: {
type: 'linearGradient',
stops: [
{ offset: '0%', color: 'rgba(73, 131, 236,0.7)' },
{ offset: '30%', color: '#4983EC' },
{ offset: '100%', color: '#FFFFFF' }
]
},
filter: {
name: 'dropShadow',
args: {
color: '#4983EC',
dx: 0,
dy: 0,
blur: 2,
opacity: 0.7
}
},
stroke: 'none',
pointerEvents: 'none',
style: {
boxShadow: '0px 0px 10px 1px #002DFF'
}
},
arrow1: {
// atConnectionRatio: 将边中的指定元素移动到指定比例 [0, 1] 位置处,并自动旋转元素,
// 使其方向与所在位置边的斜率保持一致。
atConnectionRatio: 0.25
},
arrow2: {
atConnectionRatio: 0.5
}
},
// connector: { name: 'smooth' },
zIndex: 0
},
true
)
2.动画代码:
import { Timing } from '@antv/x6'
render(){
const { edges: newEdges, nodes } = newData//接口获取或者自定义的拓扑数据
//根据连接节点的位置获取边的入口port和出口port ,根据自己的需要调整或者选择删除
newEdges.map(it => {
let sourceNode, targetNode
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].id === it.source.cell) {
sourceNode = nodes[i]
}
if (nodes[i].id === it.target.cell) {
targetNode = nodes[i]
}
if (sourceNode && targetNode) {
break
}
}
const sourceX = sourceNode.x
const sourceY = sourceNode.y
const targetX = targetNode.x
const targetY = targetNode.y
const sourceUsedPort = sourceNode.data?.usedPort || []
const targetUsedPort = targetNode.data?.usedPort || []
//getEdgePort2代码在下面
const { sourcePort, targetPort } = this.SystemFlow.getEdgePort2({ sourceX, sourceY, targetX, targetY, sourceUsedPort, targetUsedPort })
sourceUsedPort.push(sourcePort)
targetUsedPort.push(targetPort)
sourceNode.data.usedPort = sourceUsedPort
targetNode.data.usedPort = targetUsedPort
it.source.port = sourcePort
it.target.port = targetPort
})
graph.fromJSON(newData)
const edges = graph.getEdges()
//核心代码
edges.forEach((edge) => {
const options = {
delay: 0,
duration: 8000,
timing: Timing.linear,
complete: (p) => {
const attr = p.path.includes('arrow1') ? 'arrow1' : 'arrow2'
edge.updateAttrs({
[attr]: { atConnectionRatio: 0 }
}, { silent: true })
edge.transition(p.path, 1, options)
}
}
edge.transition('attrs/arrow1/atConnectionRatio', 1, options)
// 两个图标第一次需要在不同时间结束后面才能以相同速度动画。
edge.transition('attrs/arrow2/atConnectionRatio', 1, { ...options, duration: 6000 })
})
}
3.根据节点的未知计算边的出口port和入口port(getEdgePort2)
getEdgePort2 ({ sourceX, sourceY, targetX, targetY, sourceUsedPort = [], targetUsedPort = [] }) {
let sourceLt = 'center'
const sourceTb = 'output'
let targetLt = 'center'
let targetTb = 'input'
const sourceLeftOffset = []
const sourceRightOffset = []
sourceUsedPort.forEach(it => {
if (it.includes('output')) {
const arr = it?.split('-') || []
const offset = arr.length === 2 ? arr[1] : arr[2]
if (it?.includes('left')) {
sourceLeftOffset.push(offset)
}
if (it?.includes('right')) {
sourceRightOffset.push(offset)
}
}
})
// 判断目标节点是否已经有父元素连接过(没有被连接过的优先使用其中间port)
const hasTargetUseCenter = targetUsedPort.find(it => {
if (it.includes('input')) {
const arr = it?.split('-') || []
const offset = arr.length === 2 ? arr[1] : arr[2]
if (offset === 'center') {
return true
}
}
})
if (sourceY >= targetY) {
// sourceTb = 'input' 出必须在下面
targetTb = 'output'
}
const allOffset = ['2', '1']
let offset = allOffset[0]
if (sourceX >= targetX) {
if (sourceX <= targetX + 30) {
sourceLt = 'center'
targetLt = 'center'
} else {
// 如果改port已经使用过了,查看是否有未使用的port,如果有则在里面找一个右边对应已经使用过的来保持对称关系
offset = this.getOffset(sourceLeftOffset, sourceRightOffset, allOffset, offset)
sourceLt = `left-${offset}`
targetLt = `right-${offset}`
}
} else {
if (sourceX >= targetX - 30) {
sourceLt = 'center'
targetLt = 'center'
} else {
offset = this.getOffset(sourceRightOffset, sourceLeftOffset, allOffset, offset)
sourceLt = `right-${offset}`
targetLt = `left-${offset}`
}
}
return {
sourcePort: `${sourceTb}-${sourceLt}`,
targetPort: !hasTargetUseCenter ? `${targetTb}-center` : `${targetTb}-${targetLt}`
}
}
4.节点port
export const rotatePorts = {
groups: {
input: {
position: {
name: 'line',
args: {
start: { x: 0, y: largeScreenNodeSize.height / 2 - 40 },
end: { x: 80, y: largeScreenNodeSize.height / 2 - 40 },
strict: true
}
},
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
style: {
visibility: 'hidden'
}
}
}
},
output: {
position: {
name: 'line',
args: {
start: { x: 0, y: largeScreenNodeSize.height / 2 + 16 },
end: { x: 80, y: largeScreenNodeSize.height / 2 + 16 },
strict: true
}
},
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
style: {
visibility: 'hidden'
}
}
}
}
},
items: [
{
group: 'input',
id: 'input-left-2',
args: {
}
},
{
group: 'input',
id: 'input-left-1',
args: {
}
},
{
group: 'input',
id: 'input-center',
args: {
}
},
{
group: 'input',
id: 'input-right-1',
args: {
}
},
{
group: 'input',
id: 'input-right-2',
args: {
}
},
{
group: 'output',
id: 'output-left-2',
args: {
}
},
{
group: 'output',
id: 'output-left-1',
args: {
dy: 4
}
},
{
group: 'output',
id: 'output-center',
args: {
dy: 8
}
},
{
group: 'output',
id: 'output-right-1',
args: {
dy: 4
}
},
{
group: 'output',
id: 'output-right-2',
args: {
}
}
]
}