mind-map/simple-mind-map/src/layouts/OrganizationStructure.js
2024-09-09 17:47:17 +08:00

324 lines
9.7 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 Base from './Base'
import { walk, asyncRun, getNodeIndexInNodeList } from '../utils'
// 组织结构图
// 和逻辑结构图基本一样只是方向变成向下生长所以先计算节点的top后计算节点的left、最后调整节点的left即可
class OrganizationStructure extends Base {
// 构造函数
constructor(opt = {}) {
super(opt)
}
// 布局
doLayout(callback) {
let task = [
() => {
this.computedBaseValue()
},
() => {
this.computedLeftValue()
},
() => {
this.adjustLeftValue()
},
() => {
callback(this.root)
}
]
asyncRun(task)
}
// 遍历数据计算节点的left、width、height
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(cur, parent, isRoot, layerIndex, index, ancestors) => {
let newNode = this.createNode(
cur,
parent,
isRoot,
layerIndex,
index,
ancestors
)
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
} else {
// 非根节点
// 定位到父节点下方
newNode.top =
parent._node.top + parent._node.height + this.getMarginX(layerIndex)
}
if (!cur.data.expand) {
return true
}
},
(cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaWidth也就是子节点所占的宽度之和包括外边距
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaWidth = len
? cur._node.children.reduce((h, item) => {
return h + item.width
}, 0) +
(len + 1) * this.getMarginY(layerIndex + 1)
: 0
// 如果存在概要,则和概要的高度取最大值
let generalizationNodeWidth = cur._node.checkHasGeneralization()
? cur._node._generalizationNodeWidth + this.getMarginY(layerIndex + 1)
: 0
cur._node.childrenAreaWidth2 = Math.max(
cur._node.childrenAreaWidth,
generalizationNodeWidth
)
},
true,
0
)
}
// 遍历节点树计算节点的left
computedLeftValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (node.getData('expand') && node.children && node.children.length) {
let marginX = this.getMarginY(layerIndex + 1)
// 第一个子节点的left值 = 该节点中心的left值 - 子节点的宽度之和的一半
let left = node.left + node.width / 2 - node.childrenAreaWidth / 2
let totalLeft = left + marginX
node.children.forEach(cur => {
cur.left = totalLeft
totalLeft += cur.width + marginX
})
}
},
null,
true
)
}
// 调整节点left
adjustLeftValue() {
walk(
this.root,
null,
(node, parent, isRoot, layerIndex) => {
if (!node.getData('expand')) {
return
}
// 判断子节点所占的宽度之和是否大于该节点自身,大于则需要调整位置
let difference =
node.childrenAreaWidth2 -
this.getMarginY(layerIndex + 1) * 2 -
node.width
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
},
null,
true
)
}
// 更新兄弟节点的left
updateBrothers(node, addWidth) {
if (node.parent) {
let childrenList = node.parent.children
let index = getNodeIndexInNodeList(node, childrenList)
childrenList.forEach((item, _index) => {
if (item.hasCustomPosition()) {
// 适配自定义位置
return
}
let _offset = 0
// 上面的节点往上移
if (_index < index) {
_offset = -addWidth
} else if (_index > index) {
// 下面的节点往下移
_offset = addWidth
}
item.left += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'left', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addWidth)
}
}
// 绘制连线,连接该节点到其子节点
renderLine(node, lines, style, lineStyle) {
if (lineStyle === 'curve') {
this.renderLineCurve(node, lines, style)
} else if (lineStyle === 'direct') {
this.renderLineDirect(node, lines, style)
} else {
this.renderLineStraight(node, lines, style)
}
}
// 曲线风格连线
renderLineCurve(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize } = node
const { alwaysShowExpandBtn, notShowExpandBtn } = this.mindMap.opt
if (!alwaysShowExpandBtn || notShowExpandBtn) {
expandBtnSize = 0
}
const {
nodeUseLineStyle,
rootLineStartPositionKeepSameInCurve,
rootLineKeepSameInCurve
} = this.mindMap.themeConfig
node.children.forEach((item, index) => {
if (node.layerIndex === 0) {
expandBtnSize = 0
}
let x1 = left + width / 2
let y1 =
node.layerIndex === 0 && !rootLineStartPositionKeepSameInCurve
? top + height / 2
: top + height + expandBtnSize
let x2 = item.left + item.width / 2
let y2 = item.top
let path = ''
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
if (node.isRoot && !rootLineKeepSameInCurve) {
path =
this.quadraticCurvePath(x1, y1, x2, y2, true) + nodeUseLineStylePath
} else {
path = this.cubicBezierPath(x1, y1, x2, y2, true) + nodeUseLineStylePath
}
this.setLineStyle(style, lines[index], path, item)
})
}
// 直连风格
renderLineDirect(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height } = node
const { nodeUseLineStyle } = this.mindMap.themeConfig
let x1 = left + width / 2
let y1 = top + height
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = item.top
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath
this.setLineStyle(style, lines[index], path, item)
})
}
// 直线风格连线
renderLineStraight(node, lines, style) {
if (node.children.length <= 0) {
return []
}
let { left, top, width, height, expandBtnSize, isRoot } = node
const { alwaysShowExpandBtn, notShowExpandBtn } = this.mindMap.opt
if (!alwaysShowExpandBtn || notShowExpandBtn) {
expandBtnSize = 0
}
let x1 = left + width / 2
let y1 = top + height
let marginX = this.getMarginX(node.layerIndex + 1)
let s1 = marginX * 0.7
let minx = Infinity
let maxx = -Infinity
let len = node.children.length
node.children.forEach((item, index) => {
let x2 = item.left + item.width / 2
let y2 = y1 + s1 > item.top ? item.top + item.height : item.top
if (x2 < minx) {
minx = x2
}
if (x2 > maxx) {
maxx = x2
}
// 节点使用横线风格,需要额外渲染横线
let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle
? ` L ${item.left},${y2} L ${item.left + item.width},${y2}`
: ''
let path = `M ${x2},${y1 + s1} L ${x2},${y2}` + nodeUseLineStylePath
this.setLineStyle(style, lines[index], path, item)
})
minx = Math.min(x1, minx)
maxx = Math.max(x1, maxx)
// 父节点的竖线
let line1 = this.lineDraw.path()
node.style.line(line1)
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
line1.plot(
this.transformPath(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
)
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(this.transformPath(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`))
node._lines.push(lin2)
style && style(lin2, node)
}
}
// 渲染按钮
renderExpandBtn(node, btn) {
let { width, height, expandBtnSize } = node
let { translateX, translateY } = btn.transform()
btn.translate(
width / 2 - expandBtnSize / 2 - translateX,
height + expandBtnSize / 2 - translateY
)
}
// 创建概要节点
renderGeneralization(list) {
list.forEach(item => {
let {
bottom,
left,
right,
generalizationLineMargin,
generalizationNodeMargin
} = this.getNodeGeneralizationRenderBoundaries(item, 'v')
let x1 = left
let y1 = bottom + generalizationLineMargin
let x2 = right
let y2 = bottom + generalizationLineMargin
let cx = x1 + (x2 - x1) / 2
let cy = y1 + 20
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
item.generalizationLine.plot(this.transformPath(path))
item.generalizationNode.top = bottom + generalizationNodeMargin
item.generalizationNode.left =
left + (right - left - item.generalizationNode.width) / 2
})
}
// 渲染展开收起按钮的隐藏占位元素
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
rect.size(width, expandBtnSize).x(0).y(height)
}
}
export default OrganizationStructure