leo-mind-map
Version:
一个简单的web在线思维导图
1,286 lines (1,247 loc) • 39.2 kB
JavaScript
import {
bfsWalk,
throttle,
getTopAncestorsFomNodeList,
getNodeIndexInNodeList,
sortNodeList
} from '../utils'
import Base from '../layouts/Base'
import { CONSTANTS } from '../constants/constant'
import AutoMove from '../utils/AutoMove'
// 节点拖动插件
class Drag extends Base {
// 构造函数
constructor({ mindMap }) {
super(mindMap.renderer)
this.mindMap = mindMap
this.autoMove = new AutoMove(mindMap)
this.reset()
this.bindEvent()
}
// 复位
reset() {
// 是否正在拖拽中
this.isDragging = false
// 鼠标按下的节点
this.mousedownNode = null
// 被拖拽中的节点列表
this.beingDragNodeList = []
// 当前画布节点列表
this.nodeList = []
// 当前重叠节点
this.overlapNode = null
// 当前上一个同级节点
this.prevNode = null
// 当前下一个同级节点
this.nextNode = null
// 画布的变换数据
this.drawTransform = null
// 克隆节点
this.clone = null
// 同级位置占位符
this.placeholder = null
this.placeholderWidth = 50
this.placeholderHeight = 10
this.placeHolderLine = null
this.placeHolderExtraLines = []
// 鼠标按下位置和节点左上角的偏移量
this.offsetX = 0
this.offsetY = 0
// 当前鼠标是否按下
this.isMousedown = false
// 拖拽的鼠标位置变量
this.mouseDownX = 0
this.mouseDownY = 0
this.mouseMoveX = 0
this.mouseMoveY = 0
// 鼠标移动的距离距鼠标按下的位置距离多少以上才认为是拖动事件
this.checkDragOffset = 10
this.minOffset = 10
}
// 绑定事件
bindEvent() {
this.onNodeMousedown = this.onNodeMousedown.bind(this)
this.onMousemove = this.onMousemove.bind(this)
this.onMouseup = this.onMouseup.bind(this)
this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this)
this.mindMap.on('node_mousedown', this.onNodeMousedown)
this.mindMap.on('mousemove', this.onMousemove)
this.mindMap.on('node_mouseup', this.onMouseup)
this.mindMap.on('mouseup', this.onMouseup)
}
// 解绑事件
unBindEvent() {
this.mindMap.off('node_mousedown', this.onNodeMousedown)
this.mindMap.off('mousemove', this.onMousemove)
this.mindMap.off('node_mouseup', this.onMouseup)
this.mindMap.off('mouseup', this.onMouseup)
}
// 节点鼠标按下事件
onNodeMousedown(node, e) {
// 只读模式、不是鼠标左键按下、按下的是概要节点或根节点直接返回
if (
this.mindMap.opt.readonly ||
e.which !== 1 ||
node.isGeneralization ||
node.isRoot
) {
return
}
this.isMousedown = true
// 记录鼠标按下时的节点
this.mousedownNode = node
// 记录鼠标按下的坐标
const { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.mouseDownX = x
this.mouseDownY = y
}
// 鼠标移动事件
onMousemove(e) {
if (this.mindMap.opt.readonly || !this.isMousedown) {
return
}
e.preventDefault()
const { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
this.mouseMoveX = x
this.mouseMoveY = y
// 还没开始移动时鼠标位移过小不认为是拖拽
if (
!this.isDragging &&
Math.abs(x - this.mouseDownX) <= this.checkDragOffset &&
Math.abs(y - this.mouseDownY) <= this.checkDragOffset
) {
return
}
this.mindMap.emit('node_dragging', this.mousedownNode)
this.handleStartMove()
this.onMove(x, y, e)
}
// 鼠标松开事件
async onMouseup(e) {
if (!this.isMousedown) {
return
}
const { autoMoveWhenMouseInEdgeOnDrag, enableFreeDrag, beforeDragEnd } =
this.mindMap.opt
// 停止自动移动
if (autoMoveWhenMouseInEdgeOnDrag && this.mindMap.select) {
this.autoMove.clearAutoMoveTimer()
}
this.isMousedown = false
// 恢复被拖拽节点的临时设置
this.beingDragNodeList.forEach(node => {
node.setOpacity(1)
node.showChildren()
node.endDrag()
})
this.removeCloneNode()
let overlapNodeUid = this.overlapNode ? this.overlapNode.getData('uid') : ''
let prevNodeUid = this.prevNode ? this.prevNode.getData('uid') : ''
let nextNodeUid = this.nextNode ? this.nextNode.getData('uid') : ''
if (this.isDragging && typeof beforeDragEnd === 'function') {
const isCancel = await beforeDragEnd({
overlapNodeUid,
prevNodeUid,
nextNodeUid,
beingDragNodeList: [...this.beingDragNodeList]
})
if (isCancel) {
this.reset()
return
}
}
// 存在重叠子节点,则移动作为其子节点
if (this.overlapNode) {
this.removeNodeActive(this.overlapNode)
this.mindMap.execCommand(
'MOVE_NODE_TO',
this.beingDragNodeList,
this.overlapNode
)
} else if (this.prevNode) {
// 存在前一个相邻节点,作为其下一个兄弟节点
this.removeNodeActive(this.prevNode)
this.mindMap.execCommand(
'INSERT_AFTER',
this.beingDragNodeList,
this.prevNode
)
} else if (this.nextNode) {
// 存在下一个相邻节点,作为其前一个兄弟节点
this.removeNodeActive(this.nextNode)
this.mindMap.execCommand(
'INSERT_BEFORE',
this.beingDragNodeList,
this.nextNode
)
} else if (
this.clone &&
enableFreeDrag &&
this.beingDragNodeList.length === 1
) {
// 如果只拖拽了一个节点,那么设置自定义位置
let { x, y } = this.mindMap.toPos(
e.clientX - this.offsetX,
e.clientY - this.offsetY
)
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
x = (x - translateX) / scaleX
y = (y - translateY) / scaleY
this.mousedownNode.left = x
this.mousedownNode.top = y
this.mousedownNode.customLeft = x
this.mousedownNode.customTop = y
this.mindMap.execCommand(
'SET_NODE_CUSTOM_POSITION',
this.mousedownNode,
x,
y
)
this.mindMap.render()
}
if (this.isDragging) {
this.mindMap.emit('node_dragend', {
overlapNodeUid,
prevNodeUid,
nextNodeUid
})
}
this.reset()
}
// 移除节点的激活状态
removeNodeActive(node) {
if (node.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
}
}
// 拖动中
onMove(x, y, e) {
if (!this.isMousedown || !this.isDragging) {
return
}
// 更新克隆节点的位置
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
let cloneNodeLeft = x - this.offsetX
let cloneNodeTop = y - this.offsetY
x = (cloneNodeLeft - translateX) / scaleX
y = (cloneNodeTop - translateY) / scaleY
let t = this.clone.transform()
this.clone.translate(x - t.translateX, y - t.translateY)
// 检测新位置
this.checkOverlapNode()
// 边缘自动移动画布
this.drawTransform = this.mindMap.draw.transform()
this.autoMove.clearAutoMoveTimer()
this.autoMove.onMove(e.clientX, e.clientY)
}
// 开始拖拽时初始化一些数据
async handleStartMove() {
if (!this.isDragging) {
// 鼠标按下的节点
let node = this.mousedownNode
// 计算鼠标按下的位置距离节点左上角的距离
this.drawTransform = this.mindMap.draw.transform()
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
this.offsetX = this.mouseDownX - (node.left * scaleX + translateX)
this.offsetY = this.mouseDownY - (node.top * scaleY + translateY)
// 如果鼠标按下的节点是激活节点,那么保存当前所有激活的节点
if (node.getData('isActive')) {
// 找出这些激活节点中的最顶层节点
// 并按索引从小到大排序
this.beingDragNodeList = sortNodeList(
getTopAncestorsFomNodeList(
// 过滤掉根节点和概要节点
this.mindMap.renderer.activeNodeList.filter(item => {
return !item.isRoot && !item.isGeneralization
})
)
)
} else {
// 否则只拖拽按下的节点
this.beingDragNodeList = [node]
}
// 拦截拖拽
const { beforeDragStart } = this.mindMap.opt
if (typeof beforeDragStart === 'function') {
const stop = await beforeDragStart([...this.beingDragNodeList])
if (stop) return
}
// 将节点树转为节点数组
this.nodeTreeToList()
// 创建克隆节点
this.createCloneNode()
// 清除当前所有激活的节点
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
this.isDragging = true
}
}
// 节点由树转换成数组,从子节点到根节点
nodeTreeToList() {
const list = []
bfsWalk(this.mindMap.renderer.root, node => {
// 过滤掉当前被拖拽的节点
if (this.checkIsInBeingDragNodeList(node)) {
return
}
if (!list[node.layerIndex]) {
list[node.layerIndex] = []
}
list[node.layerIndex].push(node)
})
this.nodeList = list.reduceRight((res, cur) => {
return [...res, ...cur]
}, [])
}
// 创建克隆节点
createCloneNode() {
if (!this.clone) {
const {
dragMultiNodeRectConfig,
dragPlaceholderRectFill,
dragPlaceholderLineConfig,
dragOpacityConfig,
handleDragCloneNode
} = this.mindMap.opt
const {
width: rectWidth,
height: rectHeight,
fill: rectFill
} = dragMultiNodeRectConfig
const node = this.beingDragNodeList[0]
const lineColor = node.style.merge('lineColor', true)
// 如果当前被拖拽的节点数量大于1,那么创建一个矩形示意
if (this.beingDragNodeList.length > 1) {
this.clone = this.mindMap.otherDraw
.rect()
.size(rectWidth, rectHeight)
.radius(rectHeight / 2)
.fill({
color: rectFill || lineColor
})
this.offsetX = rectWidth / 2
this.offsetY = rectHeight / 2
} else {
// 否则克隆当前的节点
this.clone = node.group.clone()
// 删除展开收起按钮元素
const expandEl = this.clone.findOne('.smm-expand-btn')
if (expandEl) {
expandEl.remove()
}
this.mindMap.otherDraw.add(this.clone)
if (typeof handleDragCloneNode === 'function') {
handleDragCloneNode(this.clone)
}
}
this.clone.opacity(dragOpacityConfig.cloneNodeOpacity)
this.clone.css('z-index', 99999)
// 同级位置提示元素
this.placeholder = this.mindMap.otherDraw
.rect()
.fill({
color: dragPlaceholderRectFill || lineColor
})
.radius(5)
this.placeHolderLine = this.mindMap.otherDraw
.path()
.stroke({
color: dragPlaceholderLineConfig.color || lineColor,
width: dragPlaceholderLineConfig.width
})
.fill({ color: 'none' })
// 当前被拖拽的节点的临时设置
this.beingDragNodeList.forEach(node => {
// 降低透明度
node.setOpacity(dragOpacityConfig.beingDragNodeOpacity)
// 隐藏连线及下级节点
node.hideChildren()
// 设置拖拽状态
node.startDrag()
})
}
}
// 移除克隆节点
removeCloneNode() {
if (!this.clone) {
return
}
this.clone.remove()
this.placeholder.remove()
this.placeHolderLine.remove()
this.removeExtraLines()
}
// 移除额外创建的连线
removeExtraLines() {
this.placeHolderExtraLines.forEach(item => {
item.remove()
})
this.placeHolderExtraLines = []
}
// 检测重叠节点
checkOverlapNode() {
if (!this.drawTransform || !this.placeholder) {
return
}
const {
LOGICAL_STRUCTURE,
LOGICAL_STRUCTURE_LEFT,
MIND_MAP,
ORGANIZATION_STRUCTURE,
CATALOG_ORGANIZATION,
TIMELINE,
TIMELINE2,
VERTICAL_TIMELINE,
VERTICAL_TIMELINE2,
VERTICAL_TIMELINE3,
FISHBONE,
FISHBONE2,
RIGHT_FISHBONE,
RIGHT_FISHBONE2
} = CONSTANTS.LAYOUT
this.overlapNode = null
this.prevNode = null
this.nextNode = null
this.placeholder.size(0, 0)
this.placeHolderLine.hide()
this.removeExtraLines()
this.nodeList.forEach(node => {
if (node.getData('isActive')) {
this.mindMap.execCommand('SET_NODE_ACTIVE', node, false)
}
if (this.overlapNode || (this.prevNode && this.nextNode)) {
return
}
switch (this.mindMap.opt.layout) {
case LOGICAL_STRUCTURE:
case LOGICAL_STRUCTURE_LEFT:
this.handleLogicalStructure(node)
break
case MIND_MAP:
this.handleMindMap(node)
break
case ORGANIZATION_STRUCTURE:
this.handleOrganizationStructure(node)
break
case CATALOG_ORGANIZATION:
this.handleCatalogOrganization(node)
break
case TIMELINE:
this.handleTimeLine(node)
break
case TIMELINE2:
this.handleTimeLine2(node)
break
case VERTICAL_TIMELINE:
case VERTICAL_TIMELINE2:
case VERTICAL_TIMELINE3:
this.handleLogicalStructure(node)
break
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
this.handleFishbone(node)
break
default:
this.handleLogicalStructure(node)
}
})
// 重叠节点,也就是添加为子节点
if (this.overlapNode) {
this.handleOverlapNode()
}
}
// 处理作为子节点的情况
handleOverlapNode() {
const {
LOGICAL_STRUCTURE,
LOGICAL_STRUCTURE_LEFT,
MIND_MAP,
ORGANIZATION_STRUCTURE,
CATALOG_ORGANIZATION,
TIMELINE,
TIMELINE2,
VERTICAL_TIMELINE,
VERTICAL_TIMELINE2,
VERTICAL_TIMELINE3,
FISHBONE,
FISHBONE2,
RIGHT_FISHBONE,
RIGHT_FISHBONE2
} = CONSTANTS.LAYOUT
const { LEFT, TOP, RIGHT, BOTTOM } = CONSTANTS.LAYOUT_GROW_DIR
const layerIndex = this.overlapNode.layerIndex
const children = this.overlapNode.children
const marginX = this.mindMap.renderer.layout.getMarginX(layerIndex + 1)
const marginY = this.mindMap.renderer.layout.getMarginY(layerIndex + 1)
const halfPlaceholderWidth = this.placeholderWidth / 2
const halfPlaceholderHeight = this.placeholderHeight / 2
let dir = ''
let x = ''
let y = ''
let rotate = false
let notRenderPlaceholder = false
// 目标节点存在子节点,那么基于最后一个子节点定位
if (children.length > 0) {
const lastChild = children[children.length - 1]
const lastNodeRect = this.getNodeRect(lastChild)
dir = this.getNewChildNodeDir(lastChild)
switch (this.mindMap.opt.layout) {
case LOGICAL_STRUCTURE:
case MIND_MAP:
x =
dir === LEFT
? lastNodeRect.originRight - this.placeholderWidth
: lastNodeRect.originLeft
y = lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
break
case LOGICAL_STRUCTURE_LEFT:
x = lastNodeRect.originRight - this.placeholderWidth
y = lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
break
case ORGANIZATION_STRUCTURE:
rotate = true
x = lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
y = lastNodeRect.originTop
break
case CATALOG_ORGANIZATION:
if (layerIndex === 0) {
rotate = true
x =
lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
y = lastNodeRect.originTop
} else {
x = lastNodeRect.originLeft
y =
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
}
break
case TIMELINE:
if (layerIndex === 0) {
rotate = true
x =
lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
y =
lastNodeRect.originTop +
lastNodeRect.originHeight / 2 -
halfPlaceholderWidth
} else {
x = lastNodeRect.originLeft
y =
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
}
break
case TIMELINE2:
if (layerIndex === 0) {
rotate = true
x =
lastNodeRect.originRight + this.minOffset - halfPlaceholderHeight
y =
lastNodeRect.originTop +
lastNodeRect.originHeight / 2 -
halfPlaceholderWidth
} else {
x = lastNodeRect.originLeft
if (layerIndex === 1) {
y =
dir === TOP
? lastNodeRect.originTop -
this.placeholderHeight -
this.minOffset +
halfPlaceholderHeight
: lastNodeRect.originBottom +
this.minOffset -
halfPlaceholderHeight
} else {
y =
lastNodeRect.originBottom +
this.minOffset -
halfPlaceholderHeight
}
}
break
case VERTICAL_TIMELINE:
case VERTICAL_TIMELINE2:
case VERTICAL_TIMELINE3:
if (layerIndex === 0) {
x =
lastNodeRect.originLeft +
lastNodeRect.originWidth / 2 -
halfPlaceholderWidth
y =
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
} else {
x =
dir === RIGHT
? lastNodeRect.originLeft
: lastNodeRect.originRight - this.placeholderWidth
y =
lastNodeRect.originBottom + this.minOffset - halfPlaceholderHeight
}
break
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
if (layerIndex <= 1) {
notRenderPlaceholder = true
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
} else {
x = lastNodeRect.originLeft
y =
dir === TOP
? lastNodeRect.originBottom +
this.minOffset -
halfPlaceholderHeight
: lastNodeRect.originTop -
this.placeholderHeight -
this.minOffset +
halfPlaceholderHeight
}
break
default:
}
} else {
// 目标节点不存在子节点,那么基于目标节点定位
const nodeRect = this.getNodeRect(this.overlapNode)
dir = this.getNewChildNodeDir(this.overlapNode)
switch (this.mindMap.opt.layout) {
case LOGICAL_STRUCTURE:
case MIND_MAP:
x =
dir === RIGHT
? nodeRect.originRight + marginX
: nodeRect.originLeft - this.placeholderWidth - marginX
y =
nodeRect.originTop +
(nodeRect.originHeight - this.placeholderHeight) / 2
break
case LOGICAL_STRUCTURE_LEFT:
x = nodeRect.originLeft - this.placeholderWidth - marginX
y =
nodeRect.originTop +
(nodeRect.originHeight - this.placeholderHeight) / 2
break
case ORGANIZATION_STRUCTURE:
rotate = true
x =
nodeRect.originLeft +
(nodeRect.originWidth - this.placeholderHeight) / 2
y = nodeRect.originBottom + marginX
break
case CATALOG_ORGANIZATION:
if (layerIndex === 0) {
rotate = true
}
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
y = nodeRect.originBottom + marginX
break
case TIMELINE:
if (layerIndex === 0) {
rotate = true
}
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
y = nodeRect.originBottom + marginY
break
case TIMELINE2:
if (layerIndex === 0) {
rotate = true
}
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
if (layerIndex === 1) {
y =
dir === TOP
? nodeRect.originTop - this.placeholderHeight - marginX
: nodeRect.originBottom + marginX
} else {
y = nodeRect.originBottom + marginX
}
break
case VERTICAL_TIMELINE:
case VERTICAL_TIMELINE2:
case VERTICAL_TIMELINE3:
if (layerIndex === 0) {
rotate = true
}
x =
dir === RIGHT
? nodeRect.originRight + marginX
: nodeRect.originLeft - this.placeholderWidth - marginX
y =
nodeRect.originTop +
nodeRect.originHeight / 2 -
halfPlaceholderHeight
break
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
if (layerIndex <= 1) {
notRenderPlaceholder = true
this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true)
} else {
x = nodeRect.originLeft + nodeRect.originWidth * 0.5
y =
dir === BOTTOM
? nodeRect.originTop -
this.placeholderHeight -
this.minOffset +
halfPlaceholderHeight
: nodeRect.originBottom + this.minOffset - halfPlaceholderHeight
}
break
default:
}
}
if (!notRenderPlaceholder) {
this.setPlaceholderRect({
x,
y,
dir,
rotate
})
}
}
// 获取节点的生长方向
getNewChildNodeDir(node) {
const {
LOGICAL_STRUCTURE,
LOGICAL_STRUCTURE_LEFT,
MIND_MAP,
TIMELINE2,
VERTICAL_TIMELINE,
VERTICAL_TIMELINE2,
VERTICAL_TIMELINE3,
FISHBONE,
FISHBONE2,
RIGHT_FISHBONE,
RIGHT_FISHBONE2
} = CONSTANTS.LAYOUT
switch (this.mindMap.opt.layout) {
case LOGICAL_STRUCTURE:
return CONSTANTS.LAYOUT_GROW_DIR.RIGHT
case LOGICAL_STRUCTURE_LEFT:
return CONSTANTS.LAYOUT_GROW_DIR.LEFT
case MIND_MAP:
case TIMELINE2:
case VERTICAL_TIMELINE:
case VERTICAL_TIMELINE2:
case VERTICAL_TIMELINE3:
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
return node.dir
default:
return ''
}
}
// 垂直方向比较
// isReverse:是否反向
handleVerticalCheck(node, checkList, isReverse = false) {
const { layout } = this.mindMap.opt
const { LAYOUT, LAYOUT_GROW_DIR } = CONSTANTS
const {
VERTICAL_TIMELINE,
VERTICAL_TIMELINE2,
VERTICAL_TIMELINE3,
FISHBONE,
FISHBONE2,
RIGHT_FISHBONE,
RIGHT_FISHBONE2
} = LAYOUT
const { LEFT } = LAYOUT_GROW_DIR
const mouseMoveX = this.mouseMoveX
const mouseMoveY = this.mouseMoveY
const nodeRect = this.getNodeRect(node)
const dir = this.getNewChildNodeDir(node)
const layerIndex = node.layerIndex
if (isReverse) {
checkList = checkList.reverse()
}
let oneFourthHeight = nodeRect.originHeight / 4
let { prevBrotherOffset, nextBrotherOffset } =
this.getNodeDistanceToSiblingNode(checkList, node, nodeRect, 'v')
if (nodeRect.left <= mouseMoveX && nodeRect.right >= mouseMoveX) {
// 检测兄弟节点位置
if (
!this.overlapNode &&
!this.prevNode &&
!this.nextNode &&
!node.isRoot
) {
let checkIsPrevNode =
nextBrotherOffset > 0 // 距离下一个兄弟节点的距离大于0
? mouseMoveY > nodeRect.bottom &&
mouseMoveY <= nodeRect.bottom + nextBrotherOffset // 那么在当前节点外底部判断
: mouseMoveY >= nodeRect.bottom - oneFourthHeight &&
mouseMoveY <= nodeRect.bottom // 否则在当前节点内底部1/4区间判断
let checkIsNextNode =
prevBrotherOffset > 0 // 距离上一个兄弟节点的距离大于0
? mouseMoveY < nodeRect.top &&
mouseMoveY >= nodeRect.top - prevBrotherOffset // 那么在当前节点外底部判断
: mouseMoveY >= nodeRect.top &&
mouseMoveY <= nodeRect.top + oneFourthHeight
const { scaleY } = this.drawTransform
let x =
dir === LEFT
? nodeRect.originRight - this.placeholderWidth
: nodeRect.originLeft
let notRenderLine = false
switch (layout) {
case VERTICAL_TIMELINE:
case VERTICAL_TIMELINE2:
case VERTICAL_TIMELINE3:
if (layerIndex === 1) {
x =
nodeRect.originLeft +
nodeRect.originWidth / 2 -
this.placeholderWidth / 2
}
break
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
x =
nodeRect.originLeft + nodeRect.originWidth - this.placeholderWidth
break
default:
}
if (checkIsPrevNode) {
if (isReverse) {
this.nextNode = node
} else {
this.prevNode = node
}
let y =
nodeRect.originBottom +
nextBrotherOffset / scaleY - //nextBrotherOffset已经是实际间距的一半了
this.placeholderHeight / 2
switch (layout) {
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
if (layerIndex === 2) {
notRenderLine = true
y =
nodeRect.originBottom +
this.minOffset -
this.placeholderHeight / 2
}
break
default:
}
this.setPlaceholderRect({
x,
y,
dir,
notRenderLine
})
} else if (checkIsNextNode) {
if (isReverse) {
this.prevNode = node
} else {
this.nextNode = node
}
let y =
nodeRect.originTop -
this.placeholderHeight -
prevBrotherOffset / scaleY +
this.placeholderHeight / 2
switch (layout) {
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
if (layerIndex === 2) {
notRenderLine = true
y =
nodeRect.originTop -
this.placeholderHeight -
this.minOffset +
this.placeholderHeight / 2
}
break
default:
}
this.setPlaceholderRect({
x,
y,
dir,
notRenderLine
})
}
}
// 检测是否重叠
this.checkIsOverlap({
node,
dir: 'v',
prevBrotherOffset,
nextBrotherOffset,
size: oneFourthHeight,
pos: mouseMoveY,
nodeRect
})
}
}
// 水平方向比较
handleHorizontalCheck(node, checkList) {
const { layout } = this.mindMap.opt
const { LAYOUT } = CONSTANTS
const {
FISHBONE,
FISHBONE2,
RIGHT_FISHBONE,
RIGHT_FISHBONE2,
TIMELINE,
TIMELINE2
} = LAYOUT
let mouseMoveX = this.mouseMoveX
let mouseMoveY = this.mouseMoveY
let nodeRect = this.getNodeRect(node)
let oneFourthWidth = nodeRect.originWidth / 4
let { prevBrotherOffset, nextBrotherOffset } =
this.getNodeDistanceToSiblingNode(checkList, node, nodeRect, 'h')
if (nodeRect.top <= mouseMoveY && nodeRect.bottom >= mouseMoveY) {
// 检测兄弟节点位置
if (
!this.overlapNode &&
!this.prevNode &&
!this.nextNode &&
!node.isRoot
) {
let checkIsPrevNode =
nextBrotherOffset > 0 // 距离下一个兄弟节点的距离大于0
? mouseMoveX < nodeRect.right + nextBrotherOffset &&
mouseMoveX >= nodeRect.right // 那么在当前节点外底部判断
: mouseMoveX <= nodeRect.right &&
mouseMoveX >= nodeRect.right - oneFourthWidth // 否则在当前节点内底部1/4区间判断
let checkIsNextNode =
prevBrotherOffset > 0 // 距离上一个兄弟节点的距离大于0
? mouseMoveX > nodeRect.left - prevBrotherOffset &&
mouseMoveX <= nodeRect.left // 那么在当前节点外底部判断
: mouseMoveX <= nodeRect.left + oneFourthWidth &&
mouseMoveX >= nodeRect.left
const { scaleX } = this.drawTransform
const layerIndex = node.layerIndex
let y = nodeRect.originTop
let notRenderLine = false
switch (layout) {
case TIMELINE:
case TIMELINE2:
y =
nodeRect.originTop +
nodeRect.originHeight / 2 -
this.placeholderWidth / 2
break
case FISHBONE:
case FISHBONE2:
case RIGHT_FISHBONE:
case RIGHT_FISHBONE2:
if (layerIndex === 1) {
notRenderLine = true
y =
nodeRect.originTop +
nodeRect.originHeight / 2 -
this.placeholderWidth / 2
}
break
default:
}
if (checkIsPrevNode) {
if ([RIGHT_FISHBONE, RIGHT_FISHBONE2].includes(layout)) {
this.nextNode = node
} else {
this.prevNode = node
}
this.setPlaceholderRect({
x:
nodeRect.originRight +
nextBrotherOffset / scaleX - //nextBrotherOffset已经是实际间距的一半了
this.placeholderHeight / 2,
y,
rotate: true,
notRenderLine
})
} else if (checkIsNextNode) {
if ([RIGHT_FISHBONE, RIGHT_FISHBONE2].includes(layout)) {
this.prevNode = node
} else {
this.nextNode = node
}
this.setPlaceholderRect({
x:
nodeRect.originLeft -
this.placeholderHeight -
prevBrotherOffset / scaleX +
this.placeholderHeight / 2,
y,
rotate: true,
notRenderLine
})
}
}
// 检测是否重叠
this.checkIsOverlap({
node,
dir: 'h',
prevBrotherOffset,
nextBrotherOffset,
size: oneFourthWidth,
pos: mouseMoveX,
nodeRect
})
}
}
// 获取节点距前一个和后一个节点的距离
getNodeDistanceToSiblingNode(checkList, node, nodeRect, dir) {
const { TOP, LEFT, BOTTOM, RIGHT } = CONSTANTS.LAYOUT_GROW_DIR
let { scaleX, scaleY } = this.drawTransform
let dir1 = dir === 'v' ? TOP : LEFT
let dir2 = dir === 'v' ? BOTTOM : RIGHT
let scale = dir === 'v' ? scaleY : scaleX
let minOffset = this.minOffset * scale
let index = getNodeIndexInNodeList(node, checkList)
let prevBrother = null
let nextBrother = null
if (index !== -1) {
if (index - 1 >= 0) {
prevBrother = checkList[index - 1]
}
if (index + 1 <= checkList.length - 1) {
nextBrother = checkList[index + 1]
}
}
// 和前一个兄弟节点的距离
let prevBrotherOffset = 0
if (prevBrother) {
let prevNodeRect = this.getNodeRect(prevBrother)
prevBrotherOffset = nodeRect[dir1] - prevNodeRect[dir2]
// 间距小于10就当它不存在
prevBrotherOffset =
prevBrotherOffset >= minOffset ? prevBrotherOffset / 2 : 0
} else {
// 没有前一个兄弟节点,那么假设和前一个节点的距离为20
prevBrotherOffset = minOffset
}
// 和后一个兄弟节点的距离
let nextBrotherOffset = 0
if (nextBrother) {
let nextNodeRect = this.getNodeRect(nextBrother)
nextBrotherOffset = nextNodeRect[dir1] - nodeRect[dir2]
nextBrotherOffset =
nextBrotherOffset >= minOffset ? nextBrotherOffset / 2 : 0
} else {
nextBrotherOffset = minOffset
}
return {
prevBrother,
prevBrotherOffset,
nextBrother,
nextBrotherOffset
}
}
// 设置提示元素的大小和位置
setPlaceholderRect({ x, y, dir, rotate, notRenderLine }) {
let w = this.placeholderWidth
let h = this.placeholderHeight
if (rotate) {
const tmp = w
w = h
h = tmp
}
this.placeholder.size(w, h).move(x, y)
if (notRenderLine) {
return
}
const { dragPlaceholderLineConfig } = this.mindMap.opt
let node = null
let parent = null
if (this.overlapNode) {
node = this.overlapNode
parent = this.overlapNode
} else {
node = this.prevNode || this.nextNode
parent = node.parent
}
parent = parent.fakeClone()
node = node.fakeClone()
const tmpNode = this.beingDragNodeList[0].fakeClone()
tmpNode.dir = dir
tmpNode.left = x
tmpNode.top = y
tmpNode.width = w
tmpNode.height = h
parent.children = [tmpNode]
parent._lines = []
this.placeHolderLine.show()
this.mindMap.renderer.layout.renderLine(
parent,
[this.placeHolderLine],
(...args) => {
// node.styleLine(...args)
},
node.style.getStyle('lineStyle', true)
)
this.placeHolderExtraLines = [...parent._lines]
this.placeHolderExtraLines.forEach(line => {
this.mindMap.otherDraw.add(line)
line
.stroke({
color: dragPlaceholderLineConfig.color,
width: dragPlaceholderLineConfig.width
})
.fill({ color: 'none' })
})
}
// 检测是否重叠
checkIsOverlap({
node,
dir,
prevBrotherOffset,
nextBrotherOffset,
size,
pos,
nodeRect
}) {
const { TOP, LEFT, BOTTOM, RIGHT } = CONSTANTS.LAYOUT_GROW_DIR
let dir1 = dir === 'v' ? TOP : LEFT
let dir2 = dir === 'v' ? BOTTOM : RIGHT
if (!this.overlapNode && !this.prevNode && !this.nextNode) {
if (
nodeRect[dir1] + (prevBrotherOffset > 0 ? 0 : size) <= pos &&
nodeRect[dir2] - (nextBrotherOffset > 0 ? 0 : size) >= pos
) {
this.overlapNode = node
}
}
}
// 处理逻辑结构图
handleLogicalStructure(node) {
const checkList = this.commonGetNodeCheckList(node)
this.handleVerticalCheck(node, checkList)
}
// 处理思维导图
handleMindMap(node) {
const checkList = node.parent
? node.parent.children.filter(item => {
let sameDir = true
if (node.layerIndex === 1) {
sameDir = item.dir === node.dir
}
return sameDir && !this.checkIsInBeingDragNodeList(item)
})
: []
this.handleVerticalCheck(node, checkList)
}
// 处理组织结构图
handleOrganizationStructure(node) {
const checkList = this.commonGetNodeCheckList(node)
this.handleHorizontalCheck(node, checkList)
}
// 处理目录组织图
handleCatalogOrganization(node) {
const checkList = this.commonGetNodeCheckList(node)
if (node.layerIndex === 1) {
this.handleHorizontalCheck(node, checkList)
} else {
this.handleVerticalCheck(node, checkList)
}
}
// 处理时间轴
handleTimeLine(node) {
let checkList = this.commonGetNodeCheckList(node)
if (node.layerIndex === 1) {
this.handleHorizontalCheck(node, checkList)
} else {
this.handleVerticalCheck(node, checkList)
}
}
// 处理时间轴2
handleTimeLine2(node) {
let checkList = this.commonGetNodeCheckList(node)
if (node.layerIndex === 1) {
this.handleHorizontalCheck(node, checkList)
} else {
// 处于上方的三级节点需要特殊处理,因为节点排列方向反向了
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP && node.layerIndex === 2) {
this.handleVerticalCheck(node, checkList, true)
} else {
this.handleVerticalCheck(node, checkList)
}
}
}
// 处理鱼骨图
handleFishbone(node) {
let checkList = node.parent
? node.parent.children.filter(item => {
return item.layerIndex > 1 && !this.checkIsInBeingDragNodeList(item)
})
: []
if (node.layerIndex === 1) {
this.handleHorizontalCheck(node, checkList)
} else {
// 处于上方的三级节点需要特殊处理,因为节点排列方向反向了
const is2LayerTop =
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP && node.layerIndex === 2
const is2MoreLayerBottom =
node.dir === CONSTANTS.LAYOUT_GROW_DIR.BOTTOM && node.layerIndex >= 3
if (is2LayerTop || is2MoreLayerBottom) {
this.handleVerticalCheck(node, checkList, true)
} else {
this.handleVerticalCheck(node, checkList)
}
}
}
// 获取节点的兄弟节点列表通用方法
commonGetNodeCheckList(node) {
return node.parent
? [...node.parent.children].filter(item => {
return !this.checkIsInBeingDragNodeList(item)
})
: []
}
// 计算节点的位置尺寸信息
getNodeRect(node) {
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
let { left, top, width, height } = node
let originWidth = width
let originHeight = height
let originLeft = left
let originTop = top
let originBottom = top + height
let originRight = left + width
let right = (left + width) * scaleX + translateX
let bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
return {
left,
top,
right,
bottom,
originWidth,
originHeight,
originLeft,
originTop,
originBottom,
originRight
}
}
// 检查某个节点是否在被拖拽节点内
checkIsInBeingDragNodeList(node) {
return !!this.beingDragNodeList.find(item => {
return item.uid === node.uid || item.isAncestor(node)
})
}
// 插件被移除前做的事情
beforePluginRemove() {
this.unBindEvent()
}
// 插件被卸载前做的事情
beforePluginDestroy() {
this.unBindEvent()
}
}
Drag.instanceName = 'drag'
export default Drag