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.addCss()
// 画布 // 画布
this.svg = SVG().addTo(this.el).size(this.width, this.height) this.initContainer()
this.draw = this.svg.group()
// 初始化主题 // 初始化主题
this.initTheme() this.initTheme()
@ -79,8 +78,7 @@ class MindMap {
// 视图操作类 // 视图操作类
this.view = new View({ this.view = new View({
mindMap: this, mindMap: this
draw: this.draw
}) })
// 批量执行类 // 批量执行类
@ -111,6 +109,46 @@ class MindMap {
return opt 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样式到页面 // 添加必要的css样式到页面
addCss() { addCss() {
this.cssEl = document.createElement('style') this.cssEl = document.createElement('style')
@ -136,7 +174,7 @@ class MindMap {
// 重新渲染 // 重新渲染
reRender(callback, source = '') { reRender(callback, source = '') {
this.batchExecution.push('render', () => { this.batchExecution.push('render', () => {
this.draw.clear() this.clearDraw()
this.initTheme() this.initTheme()
this.renderer.reRender = true this.renderer.reRender = true
this.renderer.render(callback, source) this.renderer.render(callback, source)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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