Files
mind-map/simple-mind-map/src/core/render/node/MindMapNode.js
2025-04-02 09:47:11 +08:00

1141 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Style from './Style'
import Shape from './Shape'
import { G, Rect, Text, SVG } from '@svgdotjs/svg.js'
import nodeGeneralizationMethods from './nodeGeneralization'
import nodeExpandBtnMethods from './nodeExpandBtn'
import nodeCommandWrapsMethods from './nodeCommandWraps'
import nodeCreateContentsMethods from './nodeCreateContents'
import nodeExpandBtnPlaceholderRectMethods from './nodeExpandBtnPlaceholderRect'
import nodeModifyWidthMethods from './nodeModifyWidth'
import nodeCooperateMethods from './nodeCooperate'
import quickCreateChildBtnMethods from './quickCreateChildBtn'
import nodeLayoutMethods from './nodeLayout'
import { CONSTANTS } from '../../../constants/constant'
import { copyNodeTree, createUid, addXmlns } from '../../../utils/index'
// 节点类
class MindMapNode {
// 构造函数
constructor(opt = {}) {
this.opt = opt
// 节点数据
this.nodeData = this.handleData(opt.data || {})
// 保存本次更新时的节点数据快照
this.nodeDataSnapshot = ''
// uid
this.uid = opt.uid
// 控制实例
this.mindMap = opt.mindMap
// 渲染实例
this.renderer = opt.renderer
// 渲染器
this.draw = this.mindMap.draw
this.nodeDraw = this.mindMap.nodeDraw
this.lineDraw = this.mindMap.lineDraw
// 样式实例
this.style = new Style(this)
// 节点当前生效的全部样式
this.effectiveStyles = {}
// 形状实例
this.shapeInstance = new Shape(this)
this.shapePadding = {
paddingX: 0,
paddingY: 0
}
// 是否是根节点
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
// 是否是概要节点
this.isGeneralization =
opt.isGeneralization === undefined ? false : opt.isGeneralization
this.generalizationBelongNode = null
// 节点层级
this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex
// 节点宽
this.width = opt.width || 0
// 节点高
this.height = opt.height || 0
// 自定义文本的宽度
this.customTextWidth = opt.data.data.customTextWidth || undefined
// left
this._left = opt.left || 0
// top
this._top = opt.top || 0
// 自定义位置
this.customLeft = opt.data.data.customLeft || undefined
this.customTop = opt.data.data.customTop || undefined
// 是否正在拖拽中
this.isDrag = false
// 父节点
this.parent = opt.parent || null
// 子节点
this.children = opt.children || []
// 当前同时操作该节点的用户列表
this.userList = []
// 节点内容的容器
this.group = null
this.shapeNode = null // 节点形状节点
this.hoverNode = null // 节点hover和激活的节点
// 节点内容对象
this._customNodeContent = null
this._imgData = null
this._iconData = null
this._textData = null
this._hyperlinkData = null
this._tagData = null
this._noteData = null
this.noteEl = null
this.noteContentIsShow = false
this._attachmentData = null
this._prefixData = null
this._postfixData = null
this._expandBtn = null
this._lastExpandBtnType = null
this._showExpandBtn = false
this._openExpandNode = null
this._closeExpandNode = null
this._fillExpandNode = null
this._userListGroup = null
this._lines = []
this._generalizationList = []
this._unVisibleRectRegionNode = null
this._isMouseenter = false
this._customContentAddToNodeAdd = null
// 尺寸信息
this._rectInfo = {
textContentWidth: 0,
textContentHeight: 0,
textContentWidthWithoutTag: 0
}
// 概要节点的宽高
this._generalizationNodeWidth = 0
this._generalizationNodeHeight = 0
// 展开收缩按钮尺寸
this.expandBtnSize = this.mindMap.opt.expandBtnSize
// 是否是多选节点
this.isMultipleChoice = false
// 是否需要重新layout
this.needLayout = false
// 当前是否是隐藏状态
this.isHide = false
const proto = Object.getPrototypeOf(this)
if (!proto.bindEvent) {
// 节点尺寸计算和布局相关方法
Object.keys(nodeLayoutMethods).forEach(item => {
proto[item] = nodeLayoutMethods[item]
})
// 概要相关方法
Object.keys(nodeGeneralizationMethods).forEach(item => {
proto[item] = nodeGeneralizationMethods[item]
})
// 展开收起按钮相关方法
Object.keys(nodeExpandBtnMethods).forEach(item => {
proto[item] = nodeExpandBtnMethods[item]
})
// 展开收起按钮占位元素相关方法
Object.keys(nodeExpandBtnPlaceholderRectMethods).forEach(item => {
proto[item] = nodeExpandBtnPlaceholderRectMethods[item]
})
// 命令的相关方法
Object.keys(nodeCommandWrapsMethods).forEach(item => {
proto[item] = nodeCommandWrapsMethods[item]
})
// 创建节点内容的相关方法
Object.keys(nodeCreateContentsMethods).forEach(item => {
proto[item] = nodeCreateContentsMethods[item]
})
// 协同相关
if (this.mindMap.cooperate) {
Object.keys(nodeCooperateMethods).forEach(item => {
proto[item] = nodeCooperateMethods[item]
})
}
// 拖拽调整节点宽度
Object.keys(nodeModifyWidthMethods).forEach(item => {
proto[item] = nodeModifyWidthMethods[item]
})
// 快捷创建子节点按钮
if (this.mindMap.opt.isShowCreateChildBtnIcon) {
Object.keys(quickCreateChildBtnMethods).forEach(item => {
proto[item] = quickCreateChildBtnMethods[item]
})
this.initQuickCreateChildBtn()
}
proto.bindEvent = true
}
// 初始化
this.getSize()
// 初始需要计算一下概要节点的大小,否则计算布局时获取不到概要的大小
this.updateGeneralization()
this.initDragHandle()
}
// 支持自定义位置
get left() {
return this.customLeft || this._left
}
set left(val) {
this._left = val
}
get top() {
return this.customTop || this._top
}
set top(val) {
this._top = val
}
// 复位部分布局时会重新设置的数据
reset() {
this.children = []
this.parent = null
this.isRoot = false
this.layerIndex = 0
this.left = 0
this.top = 0
}
// 节点被删除时需要复位的数据
resetWhenDelete() {
this._isMouseenter = false
}
// 处理数据
handleData(data) {
data.data.expand = data.data.expand === false ? false : true
data.data.isActive = data.data.isActive === true ? true : false
data.children = data.children || []
return data
}
// 创建节点的各个内容对象数据
// recreateTypes[] custom、image、icon、text、hyperlink、tag、note、attachment、numbers、prefix、postfix、checkbox
createNodeData(recreateTypes) {
// 自定义节点内容
const {
isUseCustomNodeContent,
customCreateNodeContent,
createNodePrefixContent,
createNodePostfixContent,
addCustomContentToNode
} = this.mindMap.opt
// 需要创建的内容类型
const typeList = [
'custom',
'image',
'icon',
'text',
'hyperlink',
'tag',
'note',
'attachment',
'prefix',
'postfix',
...this.mindMap.nodeInnerPrefixList.map(item => {
return item.name
})
]
const createTypes = {}
if (Array.isArray(recreateTypes)) {
// 重新创建指定的内容类型
typeList.forEach(item => {
if (recreateTypes.includes(item)) {
createTypes[item] = true
}
})
} else {
// 创建所有类型
typeList.forEach(item => {
createTypes[item] = true
})
}
if (
isUseCustomNodeContent &&
customCreateNodeContent &&
createTypes.custom
) {
this._customNodeContent = customCreateNodeContent(this)
}
// 如果没有返回内容,那么还是使用内置的节点内容
if (this._customNodeContent) {
addXmlns(this._customNodeContent)
return
}
if (createTypes.image) this._imgData = this.createImgNode()
if (createTypes.icon) this._iconData = this.createIconNode()
if (createTypes.text) this._textData = this.createTextNode()
if (createTypes.hyperlink) this._hyperlinkData = this.createHyperlinkNode()
if (createTypes.tag) this._tagData = this.createTagNode()
if (createTypes.note) this._noteData = this.createNoteNode()
if (createTypes.attachment)
this._attachmentData = this.createAttachmentNode()
this.mindMap.nodeInnerPrefixList.forEach(item => {
if (createTypes[item.name]) {
this[`_${item.name}Data`] = item.createContent(this)
}
})
if (createTypes.prefix) {
this._prefixData = createNodePrefixContent
? createNodePrefixContent(this)
: null
if (this._prefixData && this._prefixData.el) {
addXmlns(this._prefixData.el)
}
}
if (createTypes.postfix) {
this._postfixData = createNodePostfixContent
? createNodePostfixContent(this)
: null
if (this._postfixData && this._postfixData.el) {
addXmlns(this._postfixData.el)
}
}
if (
addCustomContentToNode &&
typeof addCustomContentToNode.create === 'function'
) {
this._customContentAddToNodeAdd = addCustomContentToNode.create(this)
if (
this._customContentAddToNodeAdd &&
this._customContentAddToNodeAdd.el
) {
addXmlns(this._customContentAddToNodeAdd.el)
}
}
}
// 计算节点的宽高
getSize(recreateTypes, opt = {}) {
const ignoreUpdateCustomTextWidth = opt.ignoreUpdateCustomTextWidth || false
if (!ignoreUpdateCustomTextWidth) {
this.customTextWidth = this.getData('customTextWidth') || undefined
}
this.customLeft = this.getData('customLeft') || undefined
this.customTop = this.getData('customTop') || undefined
// 这里不要更新概要,不然即使概要没修改,每次也会重新渲染
// this.updateGeneralization()
this.createNodeData(recreateTypes)
const { width, height } = this.getNodeRect()
// 判断节点尺寸是否有变化
const changed = this.width !== width || this.height !== height
this.width = width
this.height = height
return changed
}
// 给节点绑定事件
bindGroupEvent() {
// 单击事件,选中节点
this.group.on('click', e => {
this.mindMap.emit('node_click', this, e)
if (this.isMultipleChoice) {
e.stopPropagation()
this.isMultipleChoice = false
return
}
if (
this.mindMap.opt.onlyOneEnableActiveNodeOnCooperate &&
this.userList.length > 0
) {
return
}
this.active(e)
})
this.group.on('mousedown', e => {
const {
readonly,
enableCtrlKeyNodeSelection,
useLeftKeySelectionRightKeyDrag,
mousedownEventPreventDefault
} = this.mindMap.opt
if (mousedownEventPreventDefault) {
e.preventDefault()
}
// 只读模式不需要阻止冒泡
if (!readonly) {
if (this.isRoot) {
// 根节点,右键拖拽画布模式下不需要阻止冒泡
if (e.which === 3 && !useLeftKeySelectionRightKeyDrag) {
e.stopPropagation()
}
} else {
// 非根节点,且按下的是非鼠标中键,需要阻止事件冒泡
if (e.which !== 2) {
e.stopPropagation()
}
}
}
// 多选和取消多选
if (!readonly && (e.ctrlKey || e.metaKey) && enableCtrlKeyNodeSelection) {
this.isMultipleChoice = true
const isActive = this.getData('isActive')
if (!isActive)
this.mindMap.emit(
'before_node_active',
this,
this.renderer.activeNodeList
)
this.mindMap.renderer[
isActive ? 'removeNodeFromActiveList' : 'addNodeToActiveList'
](this, true)
this.renderer.emitNodeActiveEvent(isActive ? null : this)
}
this.mindMap.emit('node_mousedown', this, e)
})
this.group.on('mouseup', e => {
if (!this.isRoot && e.which !== 2 && !this.mindMap.opt.readonly) {
e.stopPropagation()
}
this.mindMap.emit('node_mouseup', this, e)
})
this.group.on('mouseenter', e => {
if (this.isDrag) return
this._isMouseenter = true
// 显示展开收起按钮
this.showExpandBtn()
if (this.isGeneralization) {
this.handleGeneralizationMouseenter()
}
this.mindMap.emit('node_mouseenter', this, e)
})
this.group.on('mouseleave', e => {
if (!this._isMouseenter) return
this._isMouseenter = false
this.hideExpandBtn()
if (this.isGeneralization) {
this.handleGeneralizationMouseleave()
}
this.mindMap.emit('node_mouseleave', this, e)
})
// 双击事件
this.group.on('dblclick', e => {
const { readonly, onlyOneEnableActiveNodeOnCooperate } = this.mindMap.opt
if (readonly || e.ctrlKey || e.metaKey) {
return
}
e.stopPropagation()
if (onlyOneEnableActiveNodeOnCooperate && this.userList.length > 0) {
return
}
this.mindMap.emit('node_dblclick', this, e)
})
// 右键菜单事件
this.group.on('contextmenu', e => {
const { readonly, useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
// Mac上按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
if (readonly || e.ctrlKey) {
return
}
e.stopPropagation()
e.preventDefault()
// 如果是多选节点结束,那么不要触发右键菜单事件
if (
this.mindMap.select &&
!useLeftKeySelectionRightKeyDrag &&
this.mindMap.select.hasSelectRange()
) {
return
}
// 如果有且只有当前节点激活了,那么不需要重新激活
if (
!(this.getData('isActive') && this.renderer.activeNodeList.length === 1)
) {
this.renderer.clearActiveNodeList()
this.active(e)
}
this.mindMap.emit('node_contextmenu', e, this)
})
}
// 激活节点
active(e) {
if (this.mindMap.opt.readonly) {
return
}
e && e.stopPropagation()
if (this.getData('isActive')) {
return
}
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActiveNodeList()
this.renderer.addNodeToActiveList(this, true)
this.renderer.emitNodeActiveEvent(this)
}
// 取消激活该节点
deactivate() {
this.mindMap.renderer.removeNodeFromActiveList(this)
this.mindMap.renderer.emitNodeActiveEvent()
}
// 更新节点
update(forceRender) {
if (!this.group) {
return
}
this.updateNodeActiveClass()
const {
alwaysShowExpandBtn,
notShowExpandBtn,
isShowCreateChildBtnIcon,
readonly
} = this.mindMap.opt
const childrenLength = this.getChildrenLength()
// 不显示展开收起按钮则不需要处理
if (!notShowExpandBtn) {
if (alwaysShowExpandBtn) {
// 需要移除展开收缩按钮
if (this._expandBtn && childrenLength <= 0) {
this.removeExpandBtn()
} else {
// 更新展开收起按钮
this.renderExpandBtn()
}
} else {
const { isActive, expand } = this.getData()
// 展开状态且非激活状态,且当前鼠标不在它上面,才隐藏
if (childrenLength <= 0) {
this.removeExpandBtn()
} else if (expand && !isActive && !this._isMouseenter) {
this.hideExpandBtn()
} else {
this.showExpandBtn()
}
}
}
// 更新快速创建子节点按钮
if (isShowCreateChildBtnIcon) {
if (childrenLength > 0) {
this.removeQuickCreateChildBtn()
} else {
const { isActive } = this.getData()
if (isActive) {
this.showQuickCreateChildBtn()
} else {
this.hideQuickCreateChildBtn()
}
}
}
// 更新拖拽手柄的显示与否
this.updateDragHandle()
// 更新概要
this.renderGeneralization(forceRender)
// 更新协同头像
if (this.updateUserListNode) this.updateUserListNode()
// 更新节点位置
const t = this.group.transform()
// 保存一份当前节点数据快照
this.nodeDataSnapshot = readonly ? '' : JSON.stringify(this.getData())
// 节点位置变化才更新,因为即使值没有变化属性设置操作也是耗时的
if (this.left !== t.translateX || this.top !== t.translateY) {
this.group.translate(this.left - t.translateX, this.top - t.translateY)
}
}
// 获取节点相当于画布的位置
getNodePosInClient(_left, _top) {
const drawTransform = this.mindMap.draw.transform()
const { scaleX, scaleY, translateX, translateY } = drawTransform
const left = _left * scaleX + translateX
const top = _top * scaleY + translateY
return {
left,
top
}
}
// 判断节点是否可见
checkIsInClient(padding = 0) {
const { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
return (
nx + this.width > 0 - padding &&
ny + this.height > 0 - padding &&
nx < this.mindMap.width + padding &&
ny < this.mindMap.height + padding
)
}
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
reRender(recreateTypes, opt) {
const sizeChange = this.getSize(recreateTypes, opt)
this.layout()
this.update()
return sizeChange
}
// 更新节点激活状态
updateNodeActiveClass() {
if (!this.group) return
const isActive = this.getData('isActive')
this.group[isActive ? 'addClass' : 'removeClass']('active')
}
// 根据是否激活更新节点
updateNodeByActive(active) {
if (this.group) {
const { isShowCreateChildBtnIcon } = this.mindMap.opt
// 切换激活状态,需要切换展开收起按钮的显隐
if (active) {
this.showExpandBtn()
if (isShowCreateChildBtnIcon) {
this.showQuickCreateChildBtn()
}
} else {
this.hideExpandBtn()
if (isShowCreateChildBtnIcon) {
this.hideQuickCreateChildBtn()
}
}
this.updateNodeActiveClass()
this.updateDragHandle()
}
}
// 递归渲染
// forceRender强制渲染无论是否处于画布可视区域
// async异步渲染
render(callback = () => {}, forceRender = false, async = false) {
// 节点
// 重新渲染连线
this.renderLine()
const { openPerformance, performanceConfig } = this.mindMap.opt
// 强制渲染、或没有开启性能模式、或不在画布可视区域内不渲染节点内容
// 根节点不进行懒加载,始终渲染,因为滚动条插件依赖根节点进行计算
if (
forceRender ||
!openPerformance ||
this.checkIsInClient(performanceConfig.padding) ||
this.isRoot
) {
if (!this.group) {
// 创建组
this.group = new G()
this.group.addClass('smm-node')
this.group.css({
cursor: 'default'
})
this.bindGroupEvent()
this.nodeDraw.add(this.group)
this.layout()
this.update(forceRender)
} else {
if (!this.nodeDraw.has(this.group)) {
this.nodeDraw.add(this.group)
}
if (this.needLayout) {
this.needLayout = false
this.layout()
}
this.updateExpandBtnPlaceholderRect()
this.update(forceRender)
}
} else if (openPerformance && performanceConfig.removeNodeWhenOutCanvas) {
this.removeSelf()
}
// 子节点
if (
this.children &&
this.children.length &&
this.getData('expand') !== false
) {
let index = 0
this.children.forEach(item => {
const renderChild = () => {
item.render(
() => {
index++
if (index >= this.children.length) {
callback()
}
},
forceRender,
async
)
}
if (async) {
setTimeout(renderChild, 0)
} else {
renderChild()
}
})
} else {
callback()
}
// 手动插入的节点立即获得焦点并且开启编辑模式
if (this.nodeData.inserting) {
delete this.nodeData.inserting
this.active()
// setTimeout(() => {
this.mindMap.emit('node_dblclick', this, null, true)
// }, 0)
}
}
// 删除自身,只是从画布删除,节点容器还在,后续还可以重新插回画布
removeSelf() {
if (!this.group) return
this.group.remove()
this.removeGeneralization()
}
// 递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布
remove() {
if (!this.group) return
this.group.remove()
this.removeGeneralization()
this.removeLine()
// 子节点
if (this.children && this.children.length) {
this.children.forEach(item => {
item.remove()
})
}
}
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
destroy() {
this.removeLine()
if (this.parent) {
this.parent.removeLine()
}
if (!this.group) return
if (this.emptyUser) {
this.emptyUser()
}
this.resetWhenDelete()
this.group.remove()
this.removeGeneralization()
this.group = null
this.style.onRemove()
}
// 隐藏节点
hide() {
if (this.group) this.group.hide()
this.hideGeneralization()
if (this.parent) {
const index = this.parent.children.indexOf(this)
this.parent._lines[index] && this.parent._lines[index].hide()
this._lines.forEach(item => {
item.hide()
})
}
// 子节点
if (this.children && this.children.length) {
this.children.forEach(item => {
item.hide()
})
}
}
// 显示节点
show() {
if (!this.group) {
return
}
this.group.show()
this.showGeneralization()
if (this.parent) {
const index = this.parent.children.indexOf(this)
this.parent._lines[index] && this.parent._lines[index].show()
this._lines.forEach(item => {
item.show()
})
}
// 子节点
if (this.children && this.children.length) {
this.children.forEach(item => {
item.show()
})
}
}
// 设置节点透明度
// 包括连接线和下级节点
setOpacity(val) {
// 自身及连线
if (this.group) this.group.opacity(val)
this._lines.forEach(line => {
line.opacity(val)
})
// 子节点
this.children.forEach(item => {
item.setOpacity(val)
})
// 概要节点
this.setGeneralizationOpacity(val)
}
// 隐藏子节点
hideChildren() {
this._lines.forEach(item => {
item.hide()
})
if (this.children && this.children.length) {
this.children.forEach(item => {
item.hide()
})
}
}
// 显示子节点
showChildren() {
this._lines.forEach(item => {
item.show()
})
if (this.children && this.children.length) {
this.children.forEach(item => {
item.show()
})
}
}
// 被拖拽中
startDrag() {
this.isDrag = true
if (this.group) this.group.addClass('smm-node-dragging')
}
// 拖拽结束
endDrag() {
this.isDrag = false
if (this.group) this.group.removeClass('smm-node-dragging')
}
// 连线
renderLine(deep = false) {
if (this.getData('expand') === false) {
return
}
let childrenLen = this.getChildrenLength()
// 切换为鱼骨结构时,清空根节点和二级节点的连线
if (
[CONSTANTS.LAYOUT.FISHBONE, CONSTANTS.LAYOUT.RIGHT_FISHBONE].includes(
this.mindMap.opt.layout
) &&
(this.isRoot || this.layerIndex === 1)
) {
childrenLen = 0
}
if (childrenLen > this._lines.length) {
// 创建缺少的线
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
this._lines.push(this.lineDraw.path())
})
} else if (childrenLen < this._lines.length) {
// 删除多余的线
this._lines.slice(childrenLen).forEach(line => {
line.remove()
})
this._lines = this._lines.slice(0, childrenLen)
}
// 画线
this.renderer.layout.renderLine(
this,
this._lines,
(...args) => {
// 添加样式
this.styleLine(...args)
},
this.style.getStyle('lineStyle', true)
)
// 子级的连线也需要更新
if (deep && this.children && this.children.length > 0) {
this.children.forEach(item => {
item.renderLine(deep)
})
}
}
// 获取节点形状
getShape() {
// 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形
return this.mindMap.themeConfig.nodeUseLineStyle
? CONSTANTS.SHAPE.RECTANGLE
: this.style.getStyle('shape', false, false)
}
// 检查节点是否存在自定义数据
hasCustomPosition() {
return this.customLeft !== undefined && this.customTop !== undefined
}
// 检查节点是否存在自定义位置的祖先节点,包含自身
ancestorHasCustomPosition() {
let node = this
while (node) {
if (node.hasCustomPosition()) {
return true
}
node = node.parent
}
return false
}
// 检查是否存在有概要的祖先节点
ancestorHasGeneralization() {
let node = this.parent
while (node) {
if (node.checkHasGeneralization()) {
return true
}
node = node.parent
}
return false
}
// 添加子节点
addChildren(node) {
this.children.push(node)
}
// 设置连线样式
styleLine(line, childNode, enableMarker) {
const { enableInheritAncestorLineStyle } = this.mindMap.opt
const getName = enableInheritAncestorLineStyle
? 'getSelfInhertStyle'
: 'getSelfStyle'
const width =
childNode[getName]('lineWidth') || childNode.getStyle('lineWidth', true)
const color =
childNode[getName]('lineColor') ||
this.getRainbowLineColor(childNode) ||
childNode.getStyle('lineColor', true)
const dasharray =
childNode[getName]('lineDasharray') ||
childNode.getStyle('lineDasharray', true)
this.style.line(
line,
{
width,
color,
dasharray
},
enableMarker,
childNode
)
}
// 获取彩虹线条颜色
getRainbowLineColor(node) {
return this.mindMap.rainbowLines
? this.mindMap.rainbowLines.getNodeColor(node)
: ''
}
// 移除连线
removeLine() {
this._lines.forEach(line => {
line.remove()
})
this._lines = []
}
// 检测当前节点是否是某个节点的祖先节点
isAncestor(node) {
if (this.uid === node.uid) {
return false
}
let parent = node.parent
while (parent) {
if (this.uid === parent.uid) {
return true
}
parent = parent.parent
}
return false
}
// 检查当前节点是否是某个节点的父节点
isParent(node) {
if (this.uid === node.uid) {
return false
}
const parent = node.parent
if (parent && this.uid === parent.uid) {
return true
}
return false
}
// 检测当前节点是否是某个节点的兄弟节点
isBrother(node) {
if (!this.parent || this.uid === node.uid) {
return false
}
return this.parent.children.find(item => {
return item.uid === node.uid
})
}
// 获取该节点在兄弟节点列表中的索引
getIndexInBrothers() {
return this.parent && this.parent.children
? this.parent.children.findIndex(item => {
return item.uid === this.uid
})
: -1
}
// 获取padding值
getPaddingVale() {
return {
paddingX: this.getStyle('paddingX'),
paddingY: this.getStyle('paddingY')
}
}
// 获取某个样式
getStyle(prop, root) {
const v = this.style.merge(prop, root)
return v === undefined ? '' : v
}
// 获取自定义样式
getSelfStyle(prop) {
return this.style.getSelfStyle(prop)
}
// 获取最近一个存在自身自定义样式的祖先节点的自定义样式
getParentSelfStyle(prop) {
if (this.parent) {
return (
this.parent.getSelfStyle(prop) || this.parent.getParentSelfStyle(prop)
)
}
return null
}
// 获取自身可继承的自定义样式
getSelfInhertStyle(prop) {
return (
this.getSelfStyle(prop) || // 自身
this.getParentSelfStyle(prop)
) // 父级
}
// 获取节点非节点状态的边框大小
getBorderWidth() {
return this.style.merge('borderWidth', false) || 0
}
// 获取数据
getData(key) {
return key ? this.nodeData.data[key] : this.nodeData.data
}
// 获取该节点的纯数据,即不包含对节点实例的引用
getPureData(removeActiveState = true, removeId = false) {
return copyNodeTree({}, this, removeActiveState, removeId)
}
// 获取祖先节点列表
getAncestorNodes() {
const list = []
let parent = this.parent
while (parent) {
list.unshift(parent)
parent = parent.parent
}
return list
}
// 是否存在自定义样式
hasCustomStyle() {
return this.style.hasCustomStyle()
}
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置是相对于浏览器窗口左上角的位置
getRect() {
return this.group ? this.group.rbox() : null
}
// 获取节点的尺寸和位置信息,宽高是应用了缩放效果后的实际宽高,位置信息相对于画布
getRectInSvg() {
const { scaleX, scaleY, translateX, translateY } =
this.mindMap.draw.transform()
let { left, top, width, height } = this
const right = (left + width) * scaleX + translateX
const bottom = (top + height) * scaleY + translateY
left = left * scaleX + translateX
top = top * scaleY + translateY
return {
left,
right,
top,
bottom,
width: width * scaleX,
height: height * scaleY
}
}
// 高亮节点
highlight() {
if (this.group) this.group.addClass('smm-node-highlight')
}
// 取消高亮节点
closeHighlight() {
if (this.group) this.group.removeClass('smm-node-highlight')
}
// 伪克隆节点
// 克隆出的节点并不能真正当做一个节点使用
fakeClone() {
const newNode = new MindMapNode({
...this.opt,
uid: createUid()
})
Object.keys(this).forEach(item => {
newNode[item] = this[item]
})
return newNode
}
// 创建SVG文本节点
createSvgTextNode(text = '') {
return new Text().text(text)
}
// 获取SVG.js库的一些对象
getSvgObjects() {
return {
SVG,
G,
Rect
}
}
// 检查是否支持拖拽调整宽度
// 1.富文本模式
// 2.自定义节点内容
checkEnableDragModifyNodeWidth() {
const {
enableDragModifyNodeWidth,
isUseCustomNodeContent,
customCreateNodeContent
} = this.mindMap.opt
return (
enableDragModifyNodeWidth &&
(this.mindMap.richText ||
(isUseCustomNodeContent && customCreateNodeContent))
)
}
// 是否存在自定义宽度
hasCustomWidth() {
return (
this.checkEnableDragModifyNodeWidth() &&
this.customTextWidth !== undefined
)
}
// 获取子节点的数量
getChildrenLength() {
return this.nodeData.children ? this.nodeData.children.length : 0
}
}
export default MindMapNode