Feature:支持调整关联线的控制点
This commit is contained in:
parent
17ab977efb
commit
9a8e630654
@ -1,5 +1,14 @@
|
|||||||
import { walk, bfsWalk, throttle } from './utils/'
|
import { walk, bfsWalk, throttle } from './utils/'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
import {
|
||||||
|
getAssociativeLineTargetIndex,
|
||||||
|
computeCubicBezierPathPoints,
|
||||||
|
joinCubicBezierPath,
|
||||||
|
cubicBezierPath,
|
||||||
|
getNodePoint,
|
||||||
|
computeNodePoints,
|
||||||
|
getNodeLinePath
|
||||||
|
} from './utils/associativeLineUtils'
|
||||||
|
|
||||||
// 关联线类
|
// 关联线类
|
||||||
class AssociativeLine {
|
class AssociativeLine {
|
||||||
@ -20,6 +29,20 @@ class AssociativeLine {
|
|||||||
// 箭头图标
|
// 箭头图标
|
||||||
this.markerPath = null
|
this.markerPath = null
|
||||||
this.marker = this.createMarker()
|
this.marker = this.createMarker()
|
||||||
|
// 控制点
|
||||||
|
this.controlLine1 = null
|
||||||
|
this.controlLine2 = null
|
||||||
|
this.controlPoint1 = null
|
||||||
|
this.controlPoint2 = null
|
||||||
|
this.controlPointDiameter = 10
|
||||||
|
this.isControlPointMousedown = false
|
||||||
|
this.mousedownControlPointKey = ''
|
||||||
|
this.controlPointMousemoveState = {
|
||||||
|
pos: null,
|
||||||
|
startPoint: null,
|
||||||
|
endPoint: null,
|
||||||
|
targetIndex: ''
|
||||||
|
}
|
||||||
// 节流一下,不然很卡
|
// 节流一下,不然很卡
|
||||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
|
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
|
||||||
this.bindEvent()
|
this.bindEvent()
|
||||||
@ -34,6 +57,9 @@ class AssociativeLine {
|
|||||||
this.mindMap.on('data_change', this.renderAllLines)
|
this.mindMap.on('data_change', this.renderAllLines)
|
||||||
// 监听画布和节点点击事件,用于清除当前激活的连接线
|
// 监听画布和节点点击事件,用于清除当前激活的连接线
|
||||||
this.mindMap.on('draw_click', () => {
|
this.mindMap.on('draw_click', () => {
|
||||||
|
if (this.isControlPointMousedown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.clearActiveLine()
|
this.clearActiveLine()
|
||||||
})
|
})
|
||||||
this.mindMap.on('node_click', node => {
|
this.mindMap.on('node_click', node => {
|
||||||
@ -55,6 +81,13 @@ class AssociativeLine {
|
|||||||
// 节点拖拽事件
|
// 节点拖拽事件
|
||||||
this.mindMap.on('node_dragging', this.onNodeDragging.bind(this))
|
this.mindMap.on('node_dragging', this.onNodeDragging.bind(this))
|
||||||
this.mindMap.on('node_dragend', this.onNodeDragend.bind(this))
|
this.mindMap.on('node_dragend', this.onNodeDragend.bind(this))
|
||||||
|
// 拖拽控制点
|
||||||
|
window.addEventListener('mousemove', e => {
|
||||||
|
this.onControlPointMousemove(e)
|
||||||
|
})
|
||||||
|
window.addEventListener('mouseup', e => {
|
||||||
|
this.onControlPointMouseup(e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建箭头
|
// 创建箭头
|
||||||
@ -69,7 +102,10 @@ class AssociativeLine {
|
|||||||
|
|
||||||
// 渲染所有连线
|
// 渲染所有连线
|
||||||
renderAllLines() {
|
renderAllLines() {
|
||||||
|
// 先移除
|
||||||
this.removeAllLines()
|
this.removeAllLines()
|
||||||
|
this.removeControls()
|
||||||
|
this.clearActiveLine()
|
||||||
let tree = this.mindMap.renderer.root
|
let tree = this.mindMap.renderer.root
|
||||||
if (!tree) return
|
if (!tree) return
|
||||||
let idToNode = new Map()
|
let idToNode = new Map()
|
||||||
@ -98,7 +134,7 @@ class AssociativeLine {
|
|||||||
ids.forEach(id => {
|
ids.forEach(id => {
|
||||||
let toNode = idToNode.get(id)
|
let toNode = idToNode.get(id)
|
||||||
if (!node || !toNode) return
|
if (!node || !toNode) return
|
||||||
let [startPoint, endPoint] = this.computeNodePoints(node, toNode)
|
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||||
this.drawLine(startPoint, endPoint, node, toNode)
|
this.drawLine(startPoint, endPoint, node, toNode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -117,11 +153,11 @@ class AssociativeLine {
|
|||||||
.stroke({ color: associativeLineColor })
|
.stroke({ color: associativeLineColor })
|
||||||
.fill({ color: associativeLineColor })
|
.fill({ color: associativeLineColor })
|
||||||
// 路径
|
// 路径
|
||||||
let pathStr = this.cubicBezierPath(
|
let { path: pathStr, controlPoints } = getNodeLinePath(
|
||||||
startPoint.x,
|
startPoint,
|
||||||
startPoint.y,
|
endPoint,
|
||||||
endPoint.x,
|
node,
|
||||||
endPoint.y
|
toNode
|
||||||
)
|
)
|
||||||
// 虚线
|
// 虚线
|
||||||
let path = this.draw.path()
|
let path = this.draw.path()
|
||||||
@ -140,14 +176,26 @@ class AssociativeLine {
|
|||||||
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
|
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
|
||||||
.fill({ color: 'none' })
|
.fill({ color: 'none' })
|
||||||
clickPath.plot(pathStr)
|
clickPath.plot(pathStr)
|
||||||
|
// 点击事件
|
||||||
clickPath.click(e => {
|
clickPath.click(e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
// 如果当前存在激活节点,那么取消激活节点
|
||||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||||
this.clearActiveNodes()
|
this.clearActiveNodes()
|
||||||
} else {
|
} else {
|
||||||
|
// 否则清除当前的关联线的激活状态,如果有的话
|
||||||
this.clearActiveLine()
|
this.clearActiveLine()
|
||||||
|
// 保存当前激活的关联线信息
|
||||||
this.activeLine = [path, clickPath, node, toNode]
|
this.activeLine = [path, clickPath, node, toNode]
|
||||||
|
// 让不可见的点击线显示
|
||||||
clickPath.stroke({ color: associativeLineActiveColor })
|
clickPath.stroke({ color: associativeLineActiveColor })
|
||||||
|
// 渲染控制点和连线
|
||||||
|
this.renderControls(
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
controlPoints[0],
|
||||||
|
controlPoints[1]
|
||||||
|
)
|
||||||
this.mindMap.emit(
|
this.mindMap.emit(
|
||||||
'associative_line_click',
|
'associative_line_click',
|
||||||
path,
|
path,
|
||||||
@ -202,15 +250,22 @@ class AssociativeLine {
|
|||||||
|
|
||||||
// 更新创建过程中的连接线
|
// 更新创建过程中的连接线
|
||||||
updateCreatingLine(e) {
|
updateCreatingLine(e) {
|
||||||
|
let { x, y } = this.getTransformedEventPos(e)
|
||||||
|
let startPoint = getNodePoint(this.creatingStartNode)
|
||||||
|
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x, y)
|
||||||
|
this.creatingLine.plot(pathStr)
|
||||||
|
this.checkOverlapNode(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取转换后的鼠标事件对象的坐标
|
||||||
|
getTransformedEventPos(e) {
|
||||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||||
let { scaleX, scaleY, translateX, translateY } =
|
let { scaleX, scaleY, translateX, translateY } =
|
||||||
this.mindMap.draw.transform()
|
this.mindMap.draw.transform()
|
||||||
x = (x - translateX) / scaleX
|
return {
|
||||||
y = (y - translateY) / scaleY
|
x: (x - translateX) / scaleX,
|
||||||
let startPoint = this.getNodePoint(this.creatingStartNode)
|
y: (y - translateY) / scaleY
|
||||||
let pathStr = this.cubicBezierPath(startPoint.x, startPoint.y, x, y)
|
}
|
||||||
this.creatingLine.plot(pathStr)
|
|
||||||
this.checkOverlapNode(x, y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测当前移动到的目标节点
|
// 检测当前移动到的目标节点
|
||||||
@ -252,6 +307,7 @@ class AssociativeLine {
|
|||||||
// 添加连接线
|
// 添加连接线
|
||||||
addLine(fromNode, toNode) {
|
addLine(fromNode, toNode) {
|
||||||
if (!fromNode || !toNode) return
|
if (!fromNode || !toNode) return
|
||||||
|
// 目标节点如果没有id,则生成一个id
|
||||||
let id = toNode.nodeData.data.id
|
let id = toNode.nodeData.data.id
|
||||||
if (!id) {
|
if (!id) {
|
||||||
id = uuid()
|
id = uuid()
|
||||||
@ -259,10 +315,33 @@ class AssociativeLine {
|
|||||||
id
|
id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 将目标节点id保存起来
|
||||||
let list = fromNode.nodeData.data.associativeLineTargets || []
|
let list = fromNode.nodeData.data.associativeLineTargets || []
|
||||||
list.push(id)
|
list.push(id)
|
||||||
|
// 保存控制点
|
||||||
|
let [startPoint, endPoint] = computeNodePoints(fromNode, toNode)
|
||||||
|
let controlPoints = computeCubicBezierPathPoints(
|
||||||
|
startPoint.x,
|
||||||
|
startPoint.y,
|
||||||
|
endPoint.x,
|
||||||
|
endPoint.y
|
||||||
|
)
|
||||||
|
let offsetList =
|
||||||
|
fromNode.nodeData.data.associativeLineTargetControlOffsets || []
|
||||||
|
// 保存的实际是控制点和端点的差值,否则当节点位置改变了,控制点还是原来的位置,连线就不对了
|
||||||
|
offsetList[list.length - 1] = [
|
||||||
|
{
|
||||||
|
x: controlPoints[0].x - startPoint.x,
|
||||||
|
y: controlPoints[0].y - startPoint.y
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: controlPoints[1].x - endPoint.x,
|
||||||
|
y: controlPoints[1].y - endPoint.y
|
||||||
|
}
|
||||||
|
]
|
||||||
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
|
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
|
||||||
associativeLineTargets: list
|
associativeLineTargets: list,
|
||||||
|
associativeLineTargetControlOffsets: offsetList
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,13 +349,19 @@ class AssociativeLine {
|
|||||||
removeLine() {
|
removeLine() {
|
||||||
if (!this.activeLine) return
|
if (!this.activeLine) return
|
||||||
let [, , node, toNode] = this.activeLine
|
let [, , node, toNode] = this.activeLine
|
||||||
let id = toNode.nodeData.data.id
|
this.removeControls()
|
||||||
|
let { associativeLineTargets, associativeLineTargetControlOffsets } =
|
||||||
|
node.nodeData.data
|
||||||
|
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||||
associativeLineTargets: node.nodeData.data.associativeLineTargets.filter(
|
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||||
item => {
|
return index !== targetIndex
|
||||||
return item !== id
|
}),
|
||||||
}
|
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
|
||||||
)
|
? associativeLineTargetControlOffsets.filter((_, index) => {
|
||||||
|
return index !== targetIndex
|
||||||
|
})
|
||||||
|
: []
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +379,7 @@ class AssociativeLine {
|
|||||||
color: 'transparent'
|
color: 'transparent'
|
||||||
})
|
})
|
||||||
this.activeLine = null
|
this.activeLine = null
|
||||||
|
this.removeControls()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +391,7 @@ class AssociativeLine {
|
|||||||
line[0].hide()
|
line[0].hide()
|
||||||
line[1].hide()
|
line[1].hide()
|
||||||
})
|
})
|
||||||
|
this.hideControls()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理节点拖拽完成事件
|
// 处理节点拖拽完成事件
|
||||||
@ -314,97 +401,211 @@ class AssociativeLine {
|
|||||||
line[0].show()
|
line[0].show()
|
||||||
line[1].show()
|
line[1].show()
|
||||||
})
|
})
|
||||||
|
this.showControls()
|
||||||
this.isNodeDragging = false
|
this.isNodeDragging = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 三次贝塞尔曲线
|
// 创建控制点、连线节点
|
||||||
cubicBezierPath(x1, y1, x2, y2) {
|
createControlNodes() {
|
||||||
let cx1 = x1 + (x2 - x1) / 2
|
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||||
let cy1 = y1
|
// 连线
|
||||||
let cx2 = cx1
|
this.controlLine1 = this.draw
|
||||||
let cy2 = y2
|
.line()
|
||||||
if (Math.abs(x1 - x2) <= 5) {
|
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||||
cx1 = x1 + (y2 - y1) / 2
|
this.controlLine2 = this.draw
|
||||||
cx2 = cx1
|
.line()
|
||||||
}
|
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||||
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
|
// 控制点
|
||||||
|
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||||
|
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据两个节点的位置计算节点的连接点
|
// 创建控制点
|
||||||
computeNodePoints(fromNode, toNode) {
|
createOneControlNode(pointKey) {
|
||||||
let fromRect = this.getNodeRect(fromNode)
|
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||||
let fromCx = (fromRect.right + fromRect.left) / 2
|
return this.draw
|
||||||
let fromCy = (fromRect.bottom + fromRect.top) / 2
|
.circle(this.controlPointDiameter)
|
||||||
let toRect = this.getNodeRect(toNode)
|
.stroke({ color: associativeLineActiveColor })
|
||||||
let toCx = (toRect.right + toRect.left) / 2
|
.fill({ color: '#fff' })
|
||||||
let toCy = (toRect.bottom + toRect.top) / 2
|
.click(e => {
|
||||||
// 中心点坐标的差值
|
e.stopPropagation()
|
||||||
let offsetX = toCx - fromCx
|
})
|
||||||
let offsetY = toCy - fromCy
|
.mousedown(e => {
|
||||||
if (offsetX === 0 && offsetY === 0) return
|
this.onControlPointMousedown(e, pointKey)
|
||||||
let fromDir = ''
|
})
|
||||||
let toDir = ''
|
|
||||||
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
|
|
||||||
// left
|
|
||||||
fromDir = 'left'
|
|
||||||
toDir = 'right'
|
|
||||||
} else if (offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY) {
|
|
||||||
// right
|
|
||||||
fromDir = 'right'
|
|
||||||
toDir = 'left'
|
|
||||||
} else if (offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX) {
|
|
||||||
// up
|
|
||||||
fromDir = 'top'
|
|
||||||
toDir = 'bottom'
|
|
||||||
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
|
|
||||||
// down
|
|
||||||
fromDir = 'bottom'
|
|
||||||
toDir = 'top'
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
this.getNodePoint(fromNode, fromDir),
|
|
||||||
this.getNodePoint(toNode, toDir)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取节点的位置信息
|
// 控制点的鼠标按下事件
|
||||||
getNodeRect(node) {
|
onControlPointMousedown(e, pointKey) {
|
||||||
let { left, top, width, height } = node
|
e.stopPropagation()
|
||||||
return {
|
this.isControlPointMousedown = true
|
||||||
right: left + width,
|
this.mousedownControlPointKey = pointKey
|
||||||
bottom: top + height,
|
}
|
||||||
left,
|
|
||||||
top
|
// 控制点的鼠标移动事件
|
||||||
|
onControlPointMousemove(e) {
|
||||||
|
if (
|
||||||
|
!this.isControlPointMousedown ||
|
||||||
|
!this.mousedownControlPointKey ||
|
||||||
|
!this[this.mousedownControlPointKey]
|
||||||
|
)
|
||||||
|
return
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
let radius = this.controlPointDiameter / 2
|
||||||
|
// 转换鼠标当前的位置
|
||||||
|
let { x, y } = this.getTransformedEventPos(e)
|
||||||
|
this.controlPointMousemoveState.pos = {
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}
|
||||||
|
// 更新当前拖拽的控制点的位置
|
||||||
|
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||||
|
let [path, clickPath, node, toNode] = this.activeLine
|
||||||
|
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||||
|
this.controlPointMousemoveState.startPoint = startPoint
|
||||||
|
this.controlPointMousemoveState.endPoint = endPoint
|
||||||
|
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||||
|
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||||
|
let offsets =
|
||||||
|
node.nodeData.data.associativeLineTargetControlOffsets[targetIndex]
|
||||||
|
let point1 = null
|
||||||
|
let point2 = null
|
||||||
|
// 拖拽的是控制点1
|
||||||
|
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||||
|
point1 = {
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}
|
||||||
|
point2 = {
|
||||||
|
x: endPoint.x + offsets[1].x,
|
||||||
|
y: endPoint.y + offsets[1].y
|
||||||
|
}
|
||||||
|
// 更新控制点1的连线
|
||||||
|
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||||
|
} else {
|
||||||
|
// 拖拽的是控制点2
|
||||||
|
point1 = {
|
||||||
|
x: startPoint.x + offsets[0].x,
|
||||||
|
y: startPoint.y + offsets[0].y
|
||||||
|
}
|
||||||
|
point2 = {
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}
|
||||||
|
// 更新控制点2的连线
|
||||||
|
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||||
|
}
|
||||||
|
// 更新关联线
|
||||||
|
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||||
|
path.plot(pathStr)
|
||||||
|
clickPath.plot(pathStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 控制点的鼠标移动事件
|
||||||
|
onControlPointMouseup(e) {
|
||||||
|
if (!this.isControlPointMousedown) return
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
let { pos, startPoint, endPoint, targetIndex } =
|
||||||
|
this.controlPointMousemoveState
|
||||||
|
let [, , node] = this.activeLine
|
||||||
|
let offsetList =
|
||||||
|
node.nodeData.data.associativeLineTargetControlOffsets || []
|
||||||
|
let offset1 = null
|
||||||
|
let offset2 = null
|
||||||
|
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||||
|
// 更新控制点1数据
|
||||||
|
offset1 = {
|
||||||
|
x: pos.x - startPoint.x,
|
||||||
|
y: pos.y - startPoint.y
|
||||||
|
}
|
||||||
|
offset2 = offsetList[targetIndex][1]
|
||||||
|
} else {
|
||||||
|
// 更新控制点2数据
|
||||||
|
offset1 = offsetList[targetIndex][0]
|
||||||
|
offset2 = {
|
||||||
|
x: pos.x - endPoint.x,
|
||||||
|
y: pos.y - endPoint.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offsetList[targetIndex] = [offset1, offset2]
|
||||||
|
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||||
|
associativeLineTargetControlOffsets: offsetList
|
||||||
|
})
|
||||||
|
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||||
|
setTimeout(() => {
|
||||||
|
this.resetControlPoint()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复位控制点移动
|
||||||
|
resetControlPoint() {
|
||||||
|
this.isControlPointMousedown = false
|
||||||
|
this.mousedownControlPointKey = ''
|
||||||
|
this.controlPointMousemoveState = {
|
||||||
|
pos: null,
|
||||||
|
startPoint: null,
|
||||||
|
endPoint: null,
|
||||||
|
targetIndex: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取节点的连接点
|
// 渲染控制点
|
||||||
getNodePoint(node, dir = 'right') {
|
renderControls(startPoint, endPoint, point1, point2) {
|
||||||
let { left, top, width, height } = node
|
if (!this.controlLine1) {
|
||||||
switch (dir) {
|
this.createControlNodes()
|
||||||
case 'left':
|
|
||||||
return {
|
|
||||||
x: left,
|
|
||||||
y: top + height / 2
|
|
||||||
}
|
|
||||||
case 'right':
|
|
||||||
return {
|
|
||||||
x: left + width,
|
|
||||||
y: top + height / 2
|
|
||||||
}
|
|
||||||
case 'top':
|
|
||||||
return {
|
|
||||||
x: left + width / 2,
|
|
||||||
y: top
|
|
||||||
}
|
|
||||||
case 'bottom':
|
|
||||||
return {
|
|
||||||
x: left + width / 2,
|
|
||||||
y: top + height
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
let radius = this.controlPointDiameter / 2
|
||||||
|
// 控制点和起终点的连线
|
||||||
|
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||||
|
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||||
|
// 控制点
|
||||||
|
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
|
||||||
|
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除控制点
|
||||||
|
removeControls() {
|
||||||
|
if (!this.controlLine1) return
|
||||||
|
;[
|
||||||
|
this.controlLine1,
|
||||||
|
this.controlLine2,
|
||||||
|
this.controlPoint1,
|
||||||
|
this.controlPoint2
|
||||||
|
].forEach(item => {
|
||||||
|
item.remove()
|
||||||
|
})
|
||||||
|
this.controlLine1 = null
|
||||||
|
this.controlLine2 = null
|
||||||
|
this.controlPoint1 = null
|
||||||
|
this.controlPoint2 = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏控制点
|
||||||
|
hideControls() {
|
||||||
|
if (!this.controlLine1) return
|
||||||
|
;[
|
||||||
|
this.controlLine1,
|
||||||
|
this.controlLine2,
|
||||||
|
this.controlPoint1,
|
||||||
|
this.controlPoint2
|
||||||
|
].forEach(item => {
|
||||||
|
item.hide()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示控制点
|
||||||
|
showControls() {
|
||||||
|
if (!this.controlLine1) return
|
||||||
|
;[
|
||||||
|
this.controlLine1,
|
||||||
|
this.controlLine2,
|
||||||
|
this.controlPoint1,
|
||||||
|
this.controlPoint2
|
||||||
|
].forEach(item => {
|
||||||
|
item.show()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
162
simple-mind-map/src/utils/associativeLineUtils.js
Normal file
162
simple-mind-map/src/utils/associativeLineUtils.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// 获取目标节点在起始节点的目标数组中的索引
|
||||||
|
export const getAssociativeLineTargetIndex = (node, toNode) => {
|
||||||
|
return node.nodeData.data.associativeLineTargets.findIndex(item => {
|
||||||
|
return item === toNode.nodeData.data.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算贝塞尔曲线的控制点
|
||||||
|
export const computeCubicBezierPathPoints = (x1, y1, x2, y2) => {
|
||||||
|
let cx1 = x1 + (x2 - x1) / 2
|
||||||
|
let cy1 = y1
|
||||||
|
let cx2 = cx1
|
||||||
|
let cy2 = y2
|
||||||
|
if (Math.abs(x1 - x2) <= 5) {
|
||||||
|
cx1 = x1 + (y2 - y1) / 2
|
||||||
|
cx2 = cx1
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
x: cx1,
|
||||||
|
y: cy1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: cx2,
|
||||||
|
y: cy2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拼接贝塞尔曲线路径
|
||||||
|
export const joinCubicBezierPath = (startPoint, endPoint, point1, point2) => {
|
||||||
|
return `M ${startPoint.x},${startPoint.y} C ${point1.x},${point1.y} ${point2.x},${point2.y} ${endPoint.x},${endPoint.y}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节点的位置信息
|
||||||
|
const getNodeRect = node => {
|
||||||
|
let { left, top, width, height } = node
|
||||||
|
return {
|
||||||
|
right: left + width,
|
||||||
|
bottom: top + height,
|
||||||
|
left,
|
||||||
|
top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三次贝塞尔曲线
|
||||||
|
export const cubicBezierPath = (x1, y1, x2, y2) => {
|
||||||
|
let points = computeCubicBezierPathPoints(x1, y1, x2, y2)
|
||||||
|
return joinCubicBezierPath(
|
||||||
|
{ x: x1, y: y1 },
|
||||||
|
{ x: x2, y: y2 },
|
||||||
|
points[0],
|
||||||
|
points[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节点的连接点
|
||||||
|
export const getNodePoint = (node, dir = 'right') => {
|
||||||
|
let { left, top, width, height } = node
|
||||||
|
switch (dir) {
|
||||||
|
case 'left':
|
||||||
|
return {
|
||||||
|
x: left,
|
||||||
|
y: top + height / 2
|
||||||
|
}
|
||||||
|
case 'right':
|
||||||
|
return {
|
||||||
|
x: left + width,
|
||||||
|
y: top + height / 2
|
||||||
|
}
|
||||||
|
case 'top':
|
||||||
|
return {
|
||||||
|
x: left + width / 2,
|
||||||
|
y: top
|
||||||
|
}
|
||||||
|
case 'bottom':
|
||||||
|
return {
|
||||||
|
x: left + width / 2,
|
||||||
|
y: top + height
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据两个节点的位置计算节点的连接点
|
||||||
|
export const computeNodePoints = (fromNode, toNode) => {
|
||||||
|
let fromRect = getNodeRect(fromNode)
|
||||||
|
let fromCx = (fromRect.right + fromRect.left) / 2
|
||||||
|
let fromCy = (fromRect.bottom + fromRect.top) / 2
|
||||||
|
let toRect = getNodeRect(toNode)
|
||||||
|
let toCx = (toRect.right + toRect.left) / 2
|
||||||
|
let toCy = (toRect.bottom + toRect.top) / 2
|
||||||
|
// 中心点坐标的差值
|
||||||
|
let offsetX = toCx - fromCx
|
||||||
|
let offsetY = toCy - fromCy
|
||||||
|
if (offsetX === 0 && offsetY === 0) return
|
||||||
|
let fromDir = ''
|
||||||
|
let toDir = ''
|
||||||
|
if (offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY) {
|
||||||
|
// left
|
||||||
|
fromDir = 'left'
|
||||||
|
toDir = 'right'
|
||||||
|
} else if (offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY) {
|
||||||
|
// right
|
||||||
|
fromDir = 'right'
|
||||||
|
toDir = 'left'
|
||||||
|
} else if (offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX) {
|
||||||
|
// up
|
||||||
|
fromDir = 'top'
|
||||||
|
toDir = 'bottom'
|
||||||
|
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
|
||||||
|
// down
|
||||||
|
fromDir = 'bottom'
|
||||||
|
toDir = 'top'
|
||||||
|
}
|
||||||
|
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节点的关联线路径
|
||||||
|
export const getNodeLinePath = (startPoint, endPoint, node, toNode) => {
|
||||||
|
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||||
|
// 控制点
|
||||||
|
let controlPoints = []
|
||||||
|
let associativeLineTargetControlOffsets =
|
||||||
|
node.nodeData.data.associativeLineTargetControlOffsets
|
||||||
|
if (
|
||||||
|
associativeLineTargetControlOffsets &&
|
||||||
|
associativeLineTargetControlOffsets[targetIndex]
|
||||||
|
) {
|
||||||
|
// 节点保存了控制点差值
|
||||||
|
let offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||||
|
controlPoints = [
|
||||||
|
{
|
||||||
|
x: startPoint.x + offsets[0].x,
|
||||||
|
y: startPoint.y + offsets[0].y
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: endPoint.x + offsets[1].x,
|
||||||
|
y: endPoint.y + offsets[1].y
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
// 没有保存控制点则生成默认的
|
||||||
|
controlPoints = computeCubicBezierPathPoints(
|
||||||
|
startPoint.x,
|
||||||
|
startPoint.y,
|
||||||
|
endPoint.x,
|
||||||
|
endPoint.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 根据控制点拼接贝塞尔曲线路径
|
||||||
|
return {
|
||||||
|
path: joinCubicBezierPath(
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
controlPoints[0],
|
||||||
|
controlPoints[1]
|
||||||
|
),
|
||||||
|
controlPoints
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user