Feat:优化画布DOM结构,将节点、连线、关联线分层渲染

This commit is contained in:
wanglin2 2023-10-11 11:36:34 +08:00
parent 7ec720823f
commit 88fa6225eb
16 changed files with 97 additions and 46 deletions

View File

@ -48,8 +48,7 @@ class MindMap {
this.addCss()
// 画布
this.svg = SVG().addTo(this.el).size(this.width, this.height)
this.draw = this.svg.group()
this.initContainer()
// 初始化主题
this.initTheme()
@ -79,8 +78,7 @@ class MindMap {
// 视图操作类
this.view = new View({
mindMap: this,
draw: this.draw
mindMap: this
})
// 批量执行类
@ -111,6 +109,46 @@ class MindMap {
return opt
}
// 创建容器元素
initContainer() {
const { associativeLineIsAlwaysAboveNode } = this.opt
// 节点关联线容器
const createAssociativeLineDraw = () => {
this.associativeLineDraw = this.draw.group()
this.associativeLineDraw.addClass('smm-associative-line-container')
}
// 画布
this.svg = SVG().addTo(this.el).size(this.width, this.height)
// 容器
this.draw = this.svg.group()
this.draw.addClass('smm-container')
// 节点连线容器
this.lineDraw = this.draw.group()
this.lineDraw.addClass('smm-line-container')
// 默认处于节点下方
if (!associativeLineIsAlwaysAboveNode) {
createAssociativeLineDraw()
}
// 节点容器
this.nodeDraw = this.draw.group()
this.nodeDraw.addClass('smm-node-container')
// 关联线始终处于节点上方
if (associativeLineIsAlwaysAboveNode) {
createAssociativeLineDraw()
}
// 其他内容的容器
this.otherDraw = this.draw.group()
this.otherDraw.addClass('smm-other-container')
}
// 清空各容器
clearDraw() {
this.lineDraw.clear()
this.associativeLineDraw.clear()
this.nodeDraw.clear()
this.otherDraw.clear()
}
// 添加必要的css样式到页面
addCss() {
this.cssEl = document.createElement('style')
@ -136,7 +174,7 @@ class MindMap {
// 重新渲染
reRender(callback, source = '') {
this.batchExecution.push('render', () => {
this.draw.clear()
this.clearDraw()
this.initTheme()
this.renderer.reRender = true
this.renderer.render(callback, source)

View File

@ -209,5 +209,8 @@ export const defaultOpt = {
cooperateStyle: {
avatarSize: 22,// 头像大小
fontSize: 12,// 如果是文字头像,那么文字的大小
}
},
// 关联线是否始终显示在节点上层
// false即创建关联线和激活关联线时处于最顶层其他情况下处于节点下方
associativeLineIsAlwaysAboveNode: true
}

View File

@ -52,7 +52,6 @@ class Render {
this.opt = opt
this.mindMap = opt.mindMap
this.themeConfig = this.mindMap.themeConfig
this.draw = this.mindMap.draw
// 渲染树,操作过程中修改的都是这里的数据
this.renderTree = merge({}, this.mindMap.opt.data || {})
// 是否重新渲染
@ -624,9 +623,6 @@ class Render {
node.nodeData.children.push(newNode)
// 插入子节点时自动展开子节点
node.nodeData.data.expand = true
if (node.isRoot) {
node.destroy()
}
})
// 如果同时对多个节点插入子节点,需要清除原来激活的节点
if (handleMultiNodes || !openEdit) {
@ -663,9 +659,6 @@ class Render {
node.nodeData.children.push(...childList)
// 插入子节点时自动展开子节点
node.nodeData.data.expand = true
if (node.isRoot) {
node.destroy()
}
})
this.clearActive()
this.mindMap.render()
@ -1057,9 +1050,6 @@ class Render {
})
this.mindMap.emit('node_active', null, [...this.activeNodeList])
this.mindMap.render()
if (toNode.isRoot) {
toNode.destroy()
}
}
// 粘贴节点到节点

View File

@ -22,7 +22,9 @@ class Node {
// 渲染实例
this.renderer = opt.renderer
// 渲染器
this.draw = opt.draw || null
this.draw = this.mindMap.draw
this.nodeDraw = this.mindMap.nodeDraw
this.lineDraw = this.mindMap.lineDraw
// 样式实例
this.style = new Style(this)
// 形状实例
@ -613,11 +615,11 @@ class Node {
cursor: 'default'
})
this.bindGroupEvent()
this.draw.add(this.group)
this.nodeDraw.add(this.group)
this.layout()
this.update()
} else {
this.draw.add(this.group)
this.nodeDraw.add(this.group)
if (this.needLayout) {
this.needLayout = false
this.layout()
@ -788,7 +790,7 @@ class Node {
if (childrenLen > this._lines.length) {
// 创建缺少的线
new Array(childrenLen - this._lines.length).fill(0).forEach(() => {
this._lines.push(this.draw.path())
this._lines.push(this.lineDraw.path())
})
} else if (childrenLen < this._lines.length) {
// 删除多余的线

View File

@ -12,7 +12,7 @@ function createGeneralizationNode() {
return
}
if (!this._generalizationLine) {
this._generalizationLine = this.draw.path()
this._generalizationLine = this.lineDraw.path()
}
if (!this._generalizationNode) {
this._generalizationNode = new Node({
@ -22,7 +22,6 @@ function createGeneralizationNode() {
uid: createUid(),
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
isGeneralization: true
})
this._generalizationNodeWidth = this._generalizationNode.width
@ -79,7 +78,7 @@ function removeGeneralization() {
}
// hack修复当激活一个节点时创建概要然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题
if (this.generalizationBelongNode) {
this.draw
this.nodeDraw
.find('.generalization_' + this.generalizationBelongNode.uid)
.remove()
}

View File

@ -13,6 +13,7 @@ class Base {
this.mindMap = renderer.mindMap
// 绘图对象
this.draw = this.mindMap.draw
this.lineDraw = this.mindMap.lineDraw
// 根节点
this.root = null
this.lru = new Lru(this.mindMap.opt.maxNodeCacheCount)

View File

@ -247,14 +247,14 @@ class CatalogOrganization extends Base {
minx = Math.min(minx, x1)
maxx = Math.max(maxx, x1)
// 父节点的竖线
let line1 = this.draw.path()
let line1 = this.lineDraw.path()
node.style.line(line1)
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
node._lines.push(line1)
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.draw.path()
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
node._lines.push(lin2)
@ -315,7 +315,7 @@ class CatalogOrganization extends Base {
})
// 竖线
if (len > 0) {
let lin2 = this.draw.path()
let lin2 = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
node.style.line(lin2)
if (maxy < y1 + expandBtnSize) {

View File

@ -252,7 +252,7 @@ class Fishbone extends Base {
let nodeLineX = item.left
let offset = node.height / 2 + marginY
let offsetX = offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
let line = this.draw.path()
let line = this.lineDraw.path()
if (this.checkIsTop(item)) {
line.plot(
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
@ -273,7 +273,7 @@ class Fishbone extends Base {
// 从根节点出发的水平线
let nodeHalfTop = node.top + node.height / 2
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
let line = this.draw.path()
let line = this.lineDraw.path()
line.plot(
`M ${node.left + node.width},${nodeHalfTop} L ${
maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
@ -308,7 +308,7 @@ class Fishbone extends Base {
})
// 斜线
if (len >= 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
let lineLength = maxx - node.left - node.width * this.indent
lineLength = Math.max(lineLength, 0)

View File

@ -307,7 +307,7 @@ class Fishbone extends Base {
})
// 竖线
if (len > 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
let lineLength = maxx - node.left - node.width * 0.3
if (node.parent && node.parent.isRoot) {

View File

@ -276,7 +276,7 @@ class Fishbone extends Base {
})
// 竖线
if (len > 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
let lineLength = maxx - node.left - node.width * 0.3
if (

View File

@ -218,7 +218,7 @@ class OrganizationStructure extends Base {
minx = Math.min(x1, minx)
maxx = Math.max(x1, maxx)
// 父节点的竖线
let line1 = this.draw.path()
let line1 = this.lineDraw.path()
node.style.line(line1)
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
@ -226,7 +226,7 @@ class OrganizationStructure extends Base {
style && style(line1, node)
// 水平线
if (len > 0) {
let lin2 = this.draw.path()
let lin2 = this.lineDraw.path()
node.style.line(lin2)
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
node._lines.push(lin2)

View File

@ -275,7 +275,7 @@ class Timeline extends Base {
})
// 竖线
if (len > 0) {
let line = this.draw.path()
let line = this.lineDraw.path()
expandBtnSize = len > 0 ? expandBtnSize : 0
if (
node.parent &&

View File

@ -15,7 +15,7 @@ import associativeLineTextMethods from './associativeLine/associativeLineText'
class AssociativeLine {
constructor(opt = {}) {
this.mindMap = opt.mindMap
this.draw = this.mindMap.draw
this.associativeLineDraw = this.mindMap.associativeLineDraw
// 当前所有连接线
this.lineList = []
// 当前激活的连接线
@ -98,7 +98,7 @@ class AssociativeLine {
// 创建箭头
createMarker() {
return this.draw.marker(20, 20, add => {
return this.associativeLineDraw.marker(20, 20, add => {
add.ref(12, 5)
add.size(10, 10)
add.attr('orient', 'auto-start-reverse')
@ -194,7 +194,7 @@ class AssociativeLine {
toNode
)
// 虚线
let path = this.draw.path()
let path = this.associativeLineDraw.path()
path
.stroke({
width: associativeLineWidth,
@ -205,7 +205,7 @@ class AssociativeLine {
path.plot(pathStr)
path.marker('end', this.marker)
// 不可见的点击线
let clickPath = this.draw.path()
let clickPath = this.associativeLineDraw.path()
clickPath
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
.fill({ color: 'none' })
@ -276,6 +276,7 @@ class AssociativeLine {
controlPoints[1]
)
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
this.front()
}
// 移除所有连接线
@ -300,9 +301,10 @@ class AssociativeLine {
let { associativeLineWidth, associativeLineColor } =
this.mindMap.themeConfig
if (this.isCreatingLine || !fromNode) return
this.front()
this.isCreatingLine = true
this.creatingStartNode = fromNode
this.creatingLine = this.draw.path()
this.creatingLine = this.associativeLineDraw.path()
this.creatingLine
.stroke({
width: associativeLineWidth,
@ -357,6 +359,7 @@ class AssociativeLine {
height
}
}
// 检测当前移动到的目标节点
checkOverlapNode(x, y) {
this.overlapNode = null
@ -391,6 +394,7 @@ class AssociativeLine {
this.creatingLine.remove()
this.creatingLine = null
this.overlapNode = null
this.back()
}
// 添加连接线
@ -500,6 +504,7 @@ class AssociativeLine {
}
this.activeLine = null
this.removeControls()
this.back()
}
}
@ -526,6 +531,19 @@ class AssociativeLine {
this.showControls()
this.isNodeDragging = false
}
// 关联线顶层显示
front() {
if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return
this.associativeLineDraw.front()
}
// 关联线回到原有层级
back() {
if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return
this.associativeLineDraw.back() // 最底层
this.associativeLineDraw.forward() // 连线层上面
}
}
AssociativeLine.instanceName = 'associativeLine'

View File

@ -261,7 +261,7 @@ class Drag extends Base {
const lineColor = node.style.merge('lineColor', true)
// 如果当前被拖拽的节点数量大于1那么创建一个矩形示意
if (this.beingDragNodeList.length > 1) {
this.clone = this.draw
this.clone = this.mindMap.otherDraw
.rect()
.size(rectWidth, rectHeight)
.radius(rectHeight / 2)
@ -278,12 +278,12 @@ class Drag extends Base {
if (expandEl) {
expandEl.remove()
}
this.mindMap.draw.add(this.clone)
this.mindMap.otherDraw.add(this.clone)
}
this.clone.opacity(dragOpacityConfig.cloneNodeOpacity)
this.clone.css('z-index', 99999)
// 同级位置提示元素
this.placeholder = this.draw.rect().fill({
this.placeholder = this.mindMap.otherDraw.rect().fill({
color: dragPlaceholderRectFill || lineColor
})
// 当前被拖拽的节点的临时设置

View File

@ -9,10 +9,10 @@ import {
function createControlNodes() {
let { associativeLineActiveColor } = this.mindMap.themeConfig
// 连线
this.controlLine1 = this.draw
this.controlLine1 = this.associativeLineDraw
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
this.controlLine2 = this.draw
this.controlLine2 = this.associativeLineDraw
.line()
.stroke({ color: associativeLineActiveColor, width: 2 })
// 控制点
@ -23,7 +23,7 @@ function createControlNodes() {
// 创建控制点
function createOneControlNode(pointKey) {
let { associativeLineActiveColor } = this.mindMap.themeConfig
return this.draw
return this.associativeLineDraw
.circle(this.controlPointDiameter)
.stroke({ color: associativeLineActiveColor })
.fill({ color: '#fff' })

View File

@ -7,7 +7,7 @@ import {
// 创建文字节点
function createText(data) {
let g = this.draw.group()
let g = this.associativeLineDraw.group()
const setActive = () => {
if (
!this.activeLine ||