mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-17 22:08:25 +08:00
Feat:新增性能模式
This commit is contained in:
@@ -194,7 +194,7 @@ class MindMap {
|
||||
this.renderer.reRender = true // 标记为重新渲染
|
||||
this.renderer.clearCache() // 清空节点缓存池
|
||||
this.clearDraw() // 清空画布
|
||||
this.render(callback, (source = ''))
|
||||
this.render(callback, source)
|
||||
}
|
||||
|
||||
// 获取或更新容器尺寸位置信息
|
||||
@@ -288,7 +288,9 @@ class MindMap {
|
||||
|
||||
// 更新配置
|
||||
updateConfig(opt = {}) {
|
||||
this.emit('before_update_config', this.opt)
|
||||
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
|
||||
this.emit('after_update_config', this.opt)
|
||||
}
|
||||
|
||||
// 获取当前布局结构
|
||||
@@ -379,6 +381,9 @@ class MindMap {
|
||||
// 导出
|
||||
async export(...args) {
|
||||
try {
|
||||
if (!this.doExport) {
|
||||
throw new Error('请注册Export插件!')
|
||||
}
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
} catch (error) {
|
||||
@@ -416,6 +421,11 @@ class MindMap {
|
||||
addContentToFooter,
|
||||
node
|
||||
} = {}) {
|
||||
const { watermarkConfig, openPerformance } = this.opt
|
||||
// 如果开启了性能模式,那么需要先渲染所有节点
|
||||
if (openPerformance) {
|
||||
this.renderer.forceLoadNode(node)
|
||||
}
|
||||
const { cssTextList, header, headerHeight, footer, footerHeight } =
|
||||
handleGetSvgDataExtraContent({
|
||||
addContentToHeader,
|
||||
@@ -459,7 +469,7 @@ class MindMap {
|
||||
if (!ignoreWatermark && hasWatermark) {
|
||||
this.watermark.isInExport = true
|
||||
// 是否是仅导出时需要水印
|
||||
const { onlyExport } = this.opt.watermarkConfig
|
||||
const { onlyExport } = watermarkConfig
|
||||
// 是否需要重新绘制水印
|
||||
const needReDrawWatermark =
|
||||
rect.width > origWidth || rect.height > origHeight
|
||||
|
||||
@@ -228,6 +228,14 @@ export const defaultOpt = {
|
||||
// 自定义超链接的跳转
|
||||
// 如果不传,默认会以新窗口的方式打开超链接,可以传递一个函数,函数接收两个参数:link(超链接的url)、node(所属节点实例),只要传递了函数,就会阻止默认的跳转
|
||||
customHyperlinkJump: null,
|
||||
// 是否开启性能模式,默认情况下所有节点都会直接渲染,无论是否处于画布可视区域,这样当节点数量比较多时(1000+)会比较卡,如果你的数据量比较大,那么可以通过该配置开启性能模式,即只渲染画布可视区域内的节点,超出的节点不渲染,这样会大幅提高渲染速度,当然同时也会带来一些其他问题,比如:1.当拖动或是缩放画布时会实时计算并渲染未节点的节点,所以会带来一定卡顿;2.导出图片、svg、pdf时需要先渲染全部节点,所以会比较慢;3.其他目前未发现的问题
|
||||
openPerformance: false,
|
||||
// 性能优化模式配置
|
||||
performanceConfig: {
|
||||
time: 250,// 当视图改变后多久刷新一次节点,单位:ms,
|
||||
padding: 100,// 超出画布四周指定范围内依旧渲染节点
|
||||
removeNodeWhenOutCanvas: true,// 节点移除画布可视区域后从画布删除
|
||||
},
|
||||
|
||||
// 【Select插件】
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
|
||||
@@ -32,7 +32,8 @@ import {
|
||||
checkIsNodeStyleDataKey,
|
||||
removeRichTextStyes,
|
||||
formatGetNodeGeneralization,
|
||||
sortNodeList
|
||||
sortNodeList,
|
||||
throttle
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
@@ -144,13 +145,40 @@ class Render {
|
||||
if (!this.mindMap.opt.enableDblclickBackToRootNode) return
|
||||
this.setRootNodeCenter()
|
||||
})
|
||||
// let timer = null
|
||||
// this.mindMap.on('view_data_change', () => {
|
||||
// clearTimeout(timer)
|
||||
// timer = setTimeout(() => {
|
||||
// this.render()
|
||||
// }, 300)
|
||||
// })
|
||||
// 性能模式
|
||||
this.performanceMode()
|
||||
}
|
||||
|
||||
// 性能模式,懒加载节点
|
||||
performanceMode() {
|
||||
const { openPerformance, performanceConfig } = this.mindMap.opt
|
||||
const onViewDataChange = throttle(() => {
|
||||
if (this.root) this.root.render()
|
||||
}, performanceConfig.time)
|
||||
let lastOpen = false
|
||||
this.mindMap.on('before_update_config', opt => {
|
||||
lastOpen = opt.openPerformance
|
||||
})
|
||||
this.mindMap.on('after_update_config', opt => {
|
||||
if (opt.openPerformance && !lastOpen) {
|
||||
// 动态开启性能模式
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
this.forceLoadNode()
|
||||
}
|
||||
if (!opt.openPerformance && lastOpen) {
|
||||
// 动态关闭性能模式
|
||||
this.mindMap.off('view_data_change', onViewDataChange)
|
||||
this.forceLoadNode()
|
||||
}
|
||||
})
|
||||
if (!openPerformance) return
|
||||
this.mindMap.on('view_data_change', onViewDataChange)
|
||||
}
|
||||
|
||||
// 强制渲染节点,不考虑是否在画布可视区域内
|
||||
forceLoadNode(node) {
|
||||
node = node || this.root
|
||||
if (node) node.render(() => {}, true)
|
||||
}
|
||||
|
||||
// 注册命令
|
||||
@@ -453,6 +481,7 @@ class Render {
|
||||
}
|
||||
this.mindMap.emit('node_tree_render_start')
|
||||
// 计算布局
|
||||
this.root = null
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
Object.keys(this.lastNodeCache).forEach(uid => {
|
||||
|
||||
@@ -683,7 +683,7 @@ class Node {
|
||||
}
|
||||
|
||||
// 更新节点
|
||||
update() {
|
||||
update(forceRender) {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
@@ -710,36 +710,11 @@ class Node {
|
||||
}
|
||||
}
|
||||
// 更新概要
|
||||
this.renderGeneralization()
|
||||
this.renderGeneralization(forceRender)
|
||||
// 更新协同头像
|
||||
if (this.updateUserListNode) this.updateUserListNode()
|
||||
// 更新节点位置
|
||||
let t = this.group.transform()
|
||||
// // 如果上次不在可视区内,且本次也不在,那么直接返回
|
||||
// let { left: ox, top: oy } = this.getNodePosInClient(
|
||||
// t.translateX,
|
||||
// t.translateY
|
||||
// )
|
||||
// let oldIsInClient =
|
||||
// ox > 0 && oy > 0 && ox < this.mindMap.width && oy < this.mindMap.height
|
||||
// let { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
|
||||
// let newIsNotInClient =
|
||||
// nx + this.width < 0 ||
|
||||
// ny + this.height < 0 ||
|
||||
// nx > this.mindMap.width ||
|
||||
// ny > this.mindMap.height
|
||||
// if (!oldIsInClient && newIsNotInClient) {
|
||||
// if (!this.isHide) {
|
||||
// this.isHide = true
|
||||
// this.group.hide()
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// // 如果当前是隐藏状态,那么先显示
|
||||
// if (this.isHide) {
|
||||
// this.isHide = false
|
||||
// this.group.show()
|
||||
// }
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
@@ -757,6 +732,17 @@ class Node {
|
||||
}
|
||||
}
|
||||
|
||||
// 判断节点是否可见
|
||||
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() {
|
||||
let sizeChange = this.getSize()
|
||||
@@ -786,31 +772,41 @@ class Node {
|
||||
}
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
render(callback = () => {}, forceRender = false) {
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
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()
|
||||
} else {
|
||||
if (!this.nodeDraw.has(this.group)) {
|
||||
const { openPerformance, performanceConfig } = this.mindMap.opt
|
||||
// 强制渲染、或没有开启性能模式、或不在画布可视区域内不渲染节点内容
|
||||
if (
|
||||
forceRender ||
|
||||
!openPerformance ||
|
||||
this.checkIsInClient(performanceConfig.padding)
|
||||
) {
|
||||
if (!this.group) {
|
||||
// 创建组
|
||||
this.group = new G()
|
||||
this.group.addClass('smm-node')
|
||||
this.group.css({
|
||||
cursor: 'default'
|
||||
})
|
||||
this.bindGroupEvent()
|
||||
this.nodeDraw.add(this.group)
|
||||
}
|
||||
if (this.needLayout) {
|
||||
this.needLayout = false
|
||||
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)
|
||||
}
|
||||
this.updateExpandBtnPlaceholderRect()
|
||||
this.update()
|
||||
} else if (openPerformance && performanceConfig.removeNodeWhenOutCanvas) {
|
||||
this.remove(true)
|
||||
}
|
||||
// 子节点
|
||||
if (
|
||||
@@ -825,7 +821,7 @@ class Node {
|
||||
if (index >= this.children.length) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}, forceRender)
|
||||
})
|
||||
} else {
|
||||
callback()
|
||||
@@ -841,11 +837,13 @@ class Node {
|
||||
}
|
||||
|
||||
// 递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布
|
||||
remove() {
|
||||
remove(keepLine = false) {
|
||||
if (!this.group) return
|
||||
this.group.remove()
|
||||
this.removeGeneralization()
|
||||
this.removeLine()
|
||||
if (!keepLine) {
|
||||
this.removeLine()
|
||||
}
|
||||
// 子节点
|
||||
if (this.children && this.children.length) {
|
||||
this.children.forEach(item => {
|
||||
@@ -856,6 +854,10 @@ class Node {
|
||||
|
||||
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
|
||||
destroy() {
|
||||
this.removeLine()
|
||||
if (this.parent) {
|
||||
this.parent.removeLine()
|
||||
}
|
||||
if (!this.group) return
|
||||
if (this.emptyUser) {
|
||||
this.emptyUser()
|
||||
@@ -863,11 +865,7 @@ class Node {
|
||||
this.resetWhenDelete()
|
||||
this.group.remove()
|
||||
this.removeGeneralization()
|
||||
this.removeLine()
|
||||
this.group = null
|
||||
if (this.parent) {
|
||||
this.parent.removeLine()
|
||||
}
|
||||
this.style.onRemove()
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ function updateGeneralization() {
|
||||
}
|
||||
|
||||
// 渲染概要节点
|
||||
function renderGeneralization() {
|
||||
function renderGeneralization(forceRender) {
|
||||
if (this.isGeneralization) return
|
||||
this.updateGeneralizationData()
|
||||
const list = this.formatGetGeneralization()
|
||||
@@ -100,7 +100,7 @@ function renderGeneralization() {
|
||||
this.renderer.layout.renderGeneralization(this._generalizationList)
|
||||
this._generalizationList.forEach(item => {
|
||||
this.style.generalizationLine(item.generalizationLine)
|
||||
item.generalizationNode.render()
|
||||
item.generalizationNode.render(() => {}, forceRender)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -297,6 +297,13 @@ class OuterFrame {
|
||||
if (range[0] === -1 || range[1] === -1) return
|
||||
const { left, top, width, height } =
|
||||
getNodeListBoundingRect(nodeList)
|
||||
if (
|
||||
!Number.isFinite(left) ||
|
||||
!Number.isFinite(top) ||
|
||||
!Number.isFinite(width) ||
|
||||
!Number.isFinite(height)
|
||||
)
|
||||
return
|
||||
const el = this.createOuterFrameEl(
|
||||
(left -
|
||||
outerFramePaddingX -
|
||||
|
||||
@@ -1382,22 +1382,24 @@ export const getNodeTreeBoundingRect = (
|
||||
let minY = Infinity
|
||||
let maxY = -Infinity
|
||||
const walk = (root, isRoot) => {
|
||||
if (!(isRoot && excludeSelf)) {
|
||||
const { x, y, width, height } = root.group
|
||||
.findOne('.smm-node-shape')
|
||||
.rbox()
|
||||
if (x < minX) {
|
||||
minX = x
|
||||
}
|
||||
if (x + width > maxX) {
|
||||
maxX = x + width
|
||||
}
|
||||
if (y < minY) {
|
||||
minY = y
|
||||
}
|
||||
if (y + height > maxY) {
|
||||
maxY = y + height
|
||||
}
|
||||
if (!(isRoot && excludeSelf) && root.group) {
|
||||
try {
|
||||
const { x, y, width, height } = root.group
|
||||
.findOne('.smm-node-shape')
|
||||
.rbox()
|
||||
if (x < minX) {
|
||||
minX = x
|
||||
}
|
||||
if (x + width > maxX) {
|
||||
maxX = x + width
|
||||
}
|
||||
if (y < minY) {
|
||||
minY = y
|
||||
}
|
||||
if (y + height > maxY) {
|
||||
maxY = y + height
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!excludeGeneralization && root._generalizationList.length > 0) {
|
||||
root._generalizationList.forEach(item => {
|
||||
|
||||
Reference in New Issue
Block a user