From 0d3c1b7417c1fb1f29689a561a698e467824d418 Mon Sep 17 00:00:00 2001 From: wanglin2 <1013335014@qq.com> Date: Tue, 10 Jan 2023 11:08:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A=EF=BC=8C?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E5=86=97=E4=BD=99=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simple-mind-map/index.js | 152 +- simple-mind-map/scripts/changeComments.js | 32 + simple-mind-map/src/BatchExecution.js | 30 +- simple-mind-map/src/Command.js | 67 +- simple-mind-map/src/Drag.js | 550 ++-- simple-mind-map/src/Event.js | 88 +- simple-mind-map/src/Export.js | 71 +- simple-mind-map/src/KeyCommand.js | 79 +- simple-mind-map/src/KeyboardNavigation.js | 70 +- simple-mind-map/src/MiniMap.js | 40 +- simple-mind-map/src/Node.js | 2550 ++++++++--------- simple-mind-map/src/Render.js | 2010 ++++++------- simple-mind-map/src/Select.js | 42 +- simple-mind-map/src/Shape.js | 71 +- simple-mind-map/src/Style.js | 405 ++- simple-mind-map/src/TextEdit.js | 51 +- simple-mind-map/src/View.js | 94 +- simple-mind-map/src/layouts/Base.js | 95 +- .../src/layouts/CatalogOrganization.js | 827 +++--- .../src/layouts/LogicalStructure.js | 594 ++-- simple-mind-map/src/layouts/MindMap.js | 100 +- .../src/layouts/OrganizationStructure.js | 92 +- simple-mind-map/src/parse/xmind.js | 21 +- simple-mind-map/src/svg/btns.js | 12 +- simple-mind-map/src/svg/icons.js | 6 +- simple-mind-map/src/themes/blueSky.js | 6 +- .../src/themes/brainImpairedPink.js | 6 +- simple-mind-map/src/themes/classic.js | 6 +- simple-mind-map/src/themes/classic2.js | 6 +- simple-mind-map/src/themes/classic3.js | 6 +- simple-mind-map/src/themes/classic4.js | 6 +- simple-mind-map/src/themes/classicBlue.js | 6 +- simple-mind-map/src/themes/classicGreen.js | 6 +- simple-mind-map/src/themes/dark.js | 6 +- simple-mind-map/src/themes/dark2.js | 6 +- simple-mind-map/src/themes/default.js | 288 +- simple-mind-map/src/themes/earthYellow.js | 6 +- simple-mind-map/src/themes/freshGreen.js | 6 +- simple-mind-map/src/themes/freshRed.js | 6 +- simple-mind-map/src/themes/gold.js | 6 +- simple-mind-map/src/themes/greenLeaf.js | 6 +- simple-mind-map/src/themes/minions.js | 6 +- simple-mind-map/src/themes/mint.js | 6 +- simple-mind-map/src/themes/pinkGrape.js | 6 +- simple-mind-map/src/themes/romanticPurple.js | 6 +- simple-mind-map/src/themes/skyGreen.js | 6 +- simple-mind-map/src/themes/vitalityOrange.js | 6 +- simple-mind-map/src/utils/constant.js | 19 +- simple-mind-map/src/utils/index.js | 77 +- 49 files changed, 3524 insertions(+), 5135 deletions(-) create mode 100644 simple-mind-map/scripts/changeComments.js diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index 8355c313..49bfd880 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -53,19 +53,9 @@ const defaultOpt = { */ } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 11:18:47 - * @Desc: 思维导图 - */ +// 思维导图 class MindMap { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 11:19:01 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt = {}) { // 合并选项 this.opt = this.handleOpt(merge(defaultOpt, opt)) @@ -149,11 +139,7 @@ class MindMap { }, 0) } - /** - * @Author: 王林 - * @Date: 2021-07-01 22:15:22 - * @Desc: 配置参数处理 - */ + // 配置参数处理 handleOpt(opt) { // 检查布局配置 if (!layoutValueList.includes(opt.layout)) { @@ -164,12 +150,7 @@ class MindMap { return opt } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 18:47:29 - * @Desc: 渲染,部分渲染 - */ + // 渲染,部分渲染 render() { this.batchExecution.push('render', () => { this.initTheme() @@ -178,11 +159,7 @@ class MindMap { }) } - /** - * @Author: 王林 - * @Date: 2021-07-08 22:05:11 - * @Desc: 重新渲染 - */ + // 重新渲染 reRender() { this.batchExecution.push('render', () => { this.draw.clear() @@ -192,11 +169,7 @@ class MindMap { }) } - /** - * @Author: 王林 - * @Date: 2021-07-11 21:16:52 - * @Desc: 容器尺寸变化,调整尺寸 - */ + // 容器尺寸变化,调整尺寸 resize() { this.elRect = this.el.getBoundingClientRect() this.width = this.elRect.width @@ -204,38 +177,22 @@ class MindMap { this.svg.size(this.width, this.height) } - /** - * @Author: 王林 - * @Date: 2021-04-24 13:25:50 - * @Desc: 监听事件 - */ + // 监听事件 on(event, fn) { this.event.on(event, fn) } - /** - * @Author: 王林 - * @Date: 2021-04-24 13:51:35 - * @Desc: 触发事件 - */ + // 触发事件 emit(event, ...args) { this.event.emit(event, ...args) } - /** - * @Author: 王林 - * @Date: 2021-04-24 13:53:54 - * @Desc: 解绑事件 - */ + // 解绑事件 off(event, fn) { this.event.off(event, fn) } - /** - * @Author: 王林 - * @Date: 2021-05-05 13:32:43 - * @Desc: 设置主题 - */ + // 设置主题 initTheme() { // 合并主题配置 this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig) @@ -243,70 +200,40 @@ class MindMap { Style.setBackgroundStyle(this.el, this.themeConfig) } - /** - * @Author: 王林 - * @Date: 2021-05-05 13:52:08 - * @Desc: 设置主题 - */ + // 设置主题 setTheme(theme) { this.renderer.clearAllActive() this.opt.theme = theme this.reRender() } - /** - * @Author: 王林 - * @Date: 2021-06-25 23:52:37 - * @Desc: 获取当前主题 - */ + // 获取当前主题 getTheme() { return this.opt.theme } - /** - * @Author: 王林 - * @Date: 2021-05-05 13:50:17 - * @Desc: 设置主题配置 - */ + // 设置主题配置 setThemeConfig(config) { this.opt.themeConfig = config this.reRender() } - /** - * @Author: 王林 - * @Date: 2021-08-01 10:38:34 - * @Desc: 获取自定义主题配置 - */ + // 获取自定义主题配置 getCustomThemeConfig() { return this.opt.themeConfig } - /** - * @Author: 王林 - * @Date: 2021-05-05 14:01:29 - * @Desc: 获取某个主题配置值 - */ + // 获取某个主题配置值 getThemeConfig(prop) { return prop === undefined ? this.themeConfig : this.themeConfig[prop] } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 16:17:06 - * @Desc: 获取当前布局结构 - */ + // 获取当前布局结构 getLayout() { return this.opt.layout } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 16:17:33 - * @Desc: 设置布局结构 - */ + // 设置布局结构 setLayout(layout) { // 检查布局配置 if (!layoutValueList.includes(layout)) { @@ -317,20 +244,12 @@ class MindMap { this.render() } - /** - * @Author: 王林 - * @Date: 2021-05-04 13:01:00 - * @Desc: 执行命令 - */ + // 执行命令 execCommand(...args) { this.command.exec(...args) } - /** - * @Author: 王林 - * @Date: 2021-08-03 22:58:12 - * @Desc: 动态设置思维导图数据,纯节点数据 - */ + // 动态设置思维导图数据,纯节点数据 setData(data) { this.execCommand('CLEAR_ACTIVE_NODE') this.command.clearHistory() @@ -338,12 +257,7 @@ class MindMap { this.reRender() } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-21 16:39:13 - * @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图 - */ + // 动态设置思维导图数据,包括节点数据、布局、主题、视图 setFullData(data) { if (data.root) { this.setData(data.root) @@ -364,12 +278,7 @@ class MindMap { } } - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-24 14:42:07 - * @Desc: 获取思维导图数据,节点树、主题、布局等 - */ + // 获取思维导图数据,节点树、主题、布局等 getData(withConfig) { let nodeData = this.command.getCopyData() let data = {} @@ -389,21 +298,13 @@ class MindMap { return simpleDeepClone(data) } - /** - * @Author: 王林 - * @Date: 2021-07-01 22:06:38 - * @Desc: 导出 - */ + // 导出 async export(...args) { let result = await this.doExport.export(...args) return result } - /** - * @Author: 王林 - * @Date: 2021-07-11 09:20:03 - * @Desc: 转换位置 - */ + // 转换位置 toPos(x, y) { return { x: x - this.elRect.left, @@ -411,12 +312,7 @@ class MindMap { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-06-08 14:12:38 - * @Desc: 设置只读模式、编辑模式 - */ + // 设置只读模式、编辑模式 setMode(mode) { if (!['readonly', 'edit'].includes(mode)) { return diff --git a/simple-mind-map/scripts/changeComments.js b/simple-mind-map/scripts/changeComments.js new file mode 100644 index 00000000..db084baa --- /dev/null +++ b/simple-mind-map/scripts/changeComments.js @@ -0,0 +1,32 @@ +// 将/** */类型的注释转换为//类型 +const path = require('path') +const fs = require('fs') + +const entryPath = path.resolve(__dirname, '../src') + +const transform = dir => { + let dirs = fs.readdirSync(dir) + dirs.forEach(item => { + let file = path.join(dir, item) + if (fs.statSync(file).isDirectory()) { + transform(file) + } else if (/\.js$/.test(file)) { + rewriteComments(file) + } + }) +} + +const rewriteComments = file => { + let content = fs.readFileSync(file, 'utf-8') + console.log('当前转换文件:', file) + content = content.replace(/\/\*\*[^/]+\*\//g, str => { + let res = /@Desc:([^\n]+)\n/g.exec(str) + if (res.length > 0) { + return '// ' + res[1] + } + }) + fs.writeFileSync(file, content) +} + +transform(entryPath) +rewriteComments(path.join(__dirname, '../index.js')) diff --git a/simple-mind-map/src/BatchExecution.js b/simple-mind-map/src/BatchExecution.js index 6ab02019..9b8ba2e2 100644 --- a/simple-mind-map/src/BatchExecution.js +++ b/simple-mind-map/src/BatchExecution.js @@ -1,8 +1,4 @@ -/** - * @Author: 王林 - * @Date: 2021-06-27 13:16:23 - * @Desc: 在下一个事件循环里执行任务 - */ +// 在下一个事件循环里执行任务 const nextTick = function (fn, ctx) { let pending = false let timerFunc = null @@ -33,28 +29,16 @@ const nextTick = function (fn, ctx) { } } -/** - * @Author: 王林 - * @Date: 2021-06-26 22:40:52 - * @Desc: 批量执行 - */ +// 批量执行 class BatchExecution { - /** - * @Author: 王林 - * @Date: 2021-06-26 22:41:41 - * @Desc: 构造函数 - */ + // 构造函数 constructor() { this.has = {} this.queue = [] this.nextTick = nextTick(this.flush, this) } - /** - * @Author: 王林 - * @Date: 2021-06-27 12:54:04 - * @Desc: 添加任务 - */ + // 添加任务 push(name, fn) { if (this.has[name]) { return @@ -67,11 +51,7 @@ class BatchExecution { this.nextTick() } - /** - * @Author: 王林 - * @Date: 2021-06-27 13:09:24 - * @Desc: 执行队列 - */ + // 执行队列 flush() { let fns = this.queue.slice(0) this.queue = [] diff --git a/simple-mind-map/src/Command.js b/simple-mind-map/src/Command.js index c0ca82f6..fa31d6ff 100644 --- a/simple-mind-map/src/Command.js +++ b/simple-mind-map/src/Command.js @@ -1,16 +1,8 @@ import { copyRenderTree, simpleDeepClone } from './utils' -/** - * @Author: 王林 - * @Date: 2021-05-04 13:10:06 - * @Desc: 命令类 - */ +// 命令类 class Command { - /** - * @Author: 王林 - * @Date: 2021-05-04 13:10:24 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt = {}) { this.opt = opt this.mindMap = opt.mindMap @@ -21,22 +13,14 @@ class Command { this.registerShortcutKeys() } - /** - * @Author: 王林 - * @Date: 2021-08-03 23:06:55 - * @Desc: 清空历史数据 - */ + // 清空历史数据 clearHistory() { this.history = [] this.activeHistoryIndex = 0 this.mindMap.emit('back_forward', 0, 0) } - /** - * @Author: 王林 - * @Date: 2021-08-02 23:23:19 - * @Desc: 注册快捷键 - */ + // 注册快捷键 registerShortcutKeys() { this.mindMap.keyCommand.addShortcut('Control+z', () => { this.mindMap.execCommand('BACK') @@ -46,11 +30,7 @@ class Command { }) } - /** - * @Author: 王林 - * @Date: 2021-05-04 13:12:30 - * @Desc: 执行命令 - */ + // 执行命令 exec(name, ...args) { if (this.commands[name]) { this.commands[name].forEach(fn => { @@ -63,11 +43,7 @@ class Command { } } - /** - * @Author: 王林 - * @Date: 2021-05-04 13:13:01 - * @Desc: 添加命令 - */ + // 添加命令 add(name, fn) { if (this.commands[name]) { this.commands[name].push(fn) @@ -76,11 +52,7 @@ class Command { } } - /** - * @Author: 王林 - * @Date: 2021-07-15 23:02:41 - * @Desc: 移除命令 - */ + // 移除命令 remove(name, fn) { if (!this.commands[name]) { return @@ -98,11 +70,7 @@ class Command { } } - /** - * @Author: 王林 - * @Date: 2021-05-04 14:35:43 - * @Desc: 添加回退数据 - */ + // 添加回退数据 addHistory() { let data = this.getCopyData() this.history.push(simpleDeepClone(data)) @@ -115,11 +83,7 @@ class Command { ) } - /** - * @Author: 王林 - * @Date: 2021-07-11 22:34:53 - * @Desc: 回退 - */ + // 回退 back(step = 1) { if (this.activeHistoryIndex - step >= 0) { this.activeHistoryIndex -= step @@ -132,12 +96,7 @@ class Command { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-12 10:45:31 - * @Desc: 前进 - */ + // 前进 forward(step = 1) { let len = this.history.length if (this.activeHistoryIndex + step <= len - 1) { @@ -147,11 +106,7 @@ class Command { } } - /** - * @Author: 王林 - * @Date: 2021-05-04 15:02:58 - * @Desc: 获取渲染树数据副本 - */ + // 获取渲染树数据副本 getCopyData() { return copyRenderTree({}, this.mindMap.renderer.renderTree) } diff --git a/simple-mind-map/src/Drag.js b/simple-mind-map/src/Drag.js index dc6a1334..955516c5 100644 --- a/simple-mind-map/src/Drag.js +++ b/simple-mind-map/src/Drag.js @@ -1,296 +1,254 @@ -import { bfsWalk, throttle } from './utils' -import Base from './layouts/Base' - -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 17:38:55 - * @Desc: 节点拖动类 - */ -class Drag extends Base { - /** - * @Author: 王林 - * @Date: 2021-07-10 22:35:16 - * @Desc: 构造函数 - */ - constructor({ mindMap }) { - super(mindMap.renderer) - this.mindMap = mindMap - this.reset() - this.bindEvent() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 19:33:56 - * @Desc: 复位 - */ - reset() { - // 当前拖拽节点 - this.node = null - // 当前重叠节点 - this.overlapNode = null - // 当前上一个同级节点 - this.prevNode = null - // 当前下一个同级节点 - this.nextNode = null - // 画布的变换数据 - this.drawTransform = null - // 克隆节点 - this.clone = null - // 连接线 - this.line = null - // 同级位置占位符 - this.placeholder = null - // 鼠标按下位置和节点左上角的偏移量 - this.offsetX = 0 - this.offsetY = 0 - // 克隆节点左上角的坐标 - this.cloneNodeLeft = 0 - this.cloneNodeTop = 0 - // 当前鼠标是否按下 - this.isMousedown = false - // 拖拽的鼠标位置变量 - this.mouseDownX = 0 - this.mouseDownY = 0 - this.mouseMoveX = 0 - this.mouseMoveY = 0 - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 22:36:36 - * @Desc: 绑定事件 - */ - bindEvent() { - this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this) - this.mindMap.on('node_mousedown', (node, e) => { - if (this.mindMap.opt.readonly || node.isGeneralization) { - return - } - if (e.which !== 1 || node.isRoot) { - return - } - e.preventDefault() - // 计算鼠标按下的位置距离节点左上角的距离 - this.drawTransform = this.mindMap.draw.transform() - let { scaleX, scaleY, translateX, translateY } = this.drawTransform - let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) - this.offsetX = x - (node.left * scaleX + translateX) - this.offsetY = y - (node.top * scaleY + translateY) - this.node = node - this.isMousedown = true - this.mouseDownX = x - this.mouseDownY = y - }) - this.mindMap.on('mousemove', e => { - if (this.mindMap.opt.readonly) { - return - } - if (!this.isMousedown) { - return - } - e.preventDefault() - let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) - this.mouseMoveX = x - this.mouseMoveY = y - if ( - Math.abs(x - this.mouseDownX) <= 10 && - Math.abs(y - this.mouseDownY) <= 10 && - !this.node.isDrag - ) { - return - } - this.mindMap.renderer.clearAllActive() - this.onMove(x, y) - }) - this.onMouseup = this.onMouseup.bind(this) - this.mindMap.on('node_mouseup', this.onMouseup) - this.mindMap.on('mouseup', this.onMouseup) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 19:38:02 - * @Desc: 鼠标松开事件 - */ - onMouseup(e) { - if (!this.isMousedown) { - return - } - this.isMousedown = false - let _nodeIsDrag = this.node.isDrag - this.node.isDrag = false - this.node.show() - this.removeCloneNode() - // 存在重叠子节点,则移动作为其子节点 - if (this.overlapNode) { - this.mindMap.renderer.setNodeActive(this.overlapNode, false) - this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode) - } else if (this.prevNode) { - // 存在前一个相邻节点,作为其下一个兄弟节点 - this.mindMap.renderer.setNodeActive(this.prevNode, false) - this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode) - } else if (this.nextNode) { - // 存在下一个相邻节点,作为其前一个兄弟节点 - this.mindMap.renderer.setNodeActive(this.nextNode, false) - this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode) - } else if (_nodeIsDrag) { - // 自定义位置 - let { x, y } = this.mindMap.toPos( - e.clientX - this.offsetX, - e.clientY - this.offsetY - ) - let { scaleX, scaleY, translateX, translateY } = this.drawTransform - x = (x - translateX) / scaleX - y = (y - translateY) / scaleY - this.node.left = x - this.node.top = y - this.node.customLeft = x - this.node.customTop = y - this.mindMap.execCommand('SET_NODE_CUSTOM_POSITION', this.node, x, y) - this.mindMap.render() - } - this.reset() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 19:34:53 - * @Desc: 创建克隆节点 - */ - createCloneNode() { - if (!this.clone) { - // 节点 - this.clone = this.node.group.clone() - this.clone.opacity(0.5) - this.clone.css('z-index', 99999) - this.node.isDrag = true - this.node.hide() - // 连接线 - this.line = this.draw.path() - this.line.opacity(0.5) - this.node.styleLine(this.line, this.node) - // 同级位置占位符 - this.placeholder = this.draw.rect().fill({ - color: this.node.style.merge('lineColor', true) - }) - this.mindMap.draw.add(this.clone) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 19:35:16 - * @Desc: 移除克隆节点 - */ - removeCloneNode() { - if (!this.clone) { - return - } - this.clone.remove() - this.line.remove() - this.placeholder.remove() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 18:53:47 - * @Desc: 拖动中 - */ - onMove(x, y) { - if (!this.isMousedown) { - return - } - this.createCloneNode() - let { scaleX, scaleY, translateX, translateY } = this.drawTransform - this.cloneNodeLeft = x - this.offsetX - this.cloneNodeTop = y - this.offsetY - x = (this.cloneNodeLeft - translateX) / scaleX - y = (this.cloneNodeTop - translateY) / scaleY - let t = this.clone.transform() - this.clone.translate(x - t.translateX, y - t.translateY) - // 连接线 - let parent = this.node.parent - this.line.plot( - this.quadraticCurvePath( - parent.left + parent.width / 2, - parent.top + parent.height / 2, - x + this.node.width / 2, - y + this.node.height / 2 - ) - ) - this.checkOverlapNode() - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 10:20:43 - * @Desc: 检测重叠节点 - */ - checkOverlapNode() { - if (!this.drawTransform) { - return - } - let { scaleX, scaleY, translateX, translateY } = this.drawTransform - let checkRight = this.cloneNodeLeft + this.node.width * scaleX - let checkBottom = this.cloneNodeTop + this.node.height * scaleX - this.overlapNode = null - this.prevNode = null - this.nextNode = null - this.placeholder.size(0, 0) - bfsWalk(this.mindMap.renderer.root, node => { - if (node.nodeData.data.isActive) { - this.mindMap.renderer.setNodeActive(node, false) - } - if (node === this.node || this.node.isParent(node)) { - return - } - if (this.overlapNode || (this.prevNode && this.nextNode)) { - return - } - let { left, top, width, height } = node - let _left = left - let _top = top - let _bottom = top + height - let right = (left + width) * scaleX + translateX - let bottom = (top + height) * scaleY + translateY - left = left * scaleX + translateX - top = top * scaleY + translateY - // 检测是否重叠 - if (!this.overlapNode) { - if ( - left <= checkRight && - right >= this.cloneNodeLeft && - top <= checkBottom && - bottom >= this.cloneNodeTop - ) { - this.overlapNode = node - } - } - // 检测兄弟节点位置 - if (!this.prevNode && !this.nextNode && !node.isRoot) { - // && this.node.isBrother(node) - if (left <= checkRight && right >= this.cloneNodeLeft) { - if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) { - this.prevNode = node - this.placeholder.size(node.width, 10).move(_left, _bottom) - } else if (checkBottom < top && checkBottom >= top - 10) { - this.nextNode = node - this.placeholder.size(node.width, 10).move(_left, _top - 10) - } - } - } - }) - if (this.overlapNode) { - this.mindMap.renderer.setNodeActive(this.overlapNode, true) - } - } -} - -export default Drag +import { bfsWalk, throttle } from './utils' +import Base from './layouts/Base' + +// 节点拖动类 +class Drag extends Base { + // 构造函数 + constructor({ mindMap }) { + super(mindMap.renderer) + this.mindMap = mindMap + this.reset() + this.bindEvent() + } + + // 复位 + reset() { + // 当前拖拽节点 + this.node = null + // 当前重叠节点 + this.overlapNode = null + // 当前上一个同级节点 + this.prevNode = null + // 当前下一个同级节点 + this.nextNode = null + // 画布的变换数据 + this.drawTransform = null + // 克隆节点 + this.clone = null + // 连接线 + this.line = null + // 同级位置占位符 + this.placeholder = null + // 鼠标按下位置和节点左上角的偏移量 + this.offsetX = 0 + this.offsetY = 0 + // 克隆节点左上角的坐标 + this.cloneNodeLeft = 0 + this.cloneNodeTop = 0 + // 当前鼠标是否按下 + this.isMousedown = false + // 拖拽的鼠标位置变量 + this.mouseDownX = 0 + this.mouseDownY = 0 + this.mouseMoveX = 0 + this.mouseMoveY = 0 + } + + // 绑定事件 + bindEvent() { + this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this) + this.mindMap.on('node_mousedown', (node, e) => { + if (this.mindMap.opt.readonly || node.isGeneralization) { + return + } + if (e.which !== 1 || node.isRoot) { + return + } + e.preventDefault() + // 计算鼠标按下的位置距离节点左上角的距离 + this.drawTransform = this.mindMap.draw.transform() + let { scaleX, scaleY, translateX, translateY } = this.drawTransform + let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) + this.offsetX = x - (node.left * scaleX + translateX) + this.offsetY = y - (node.top * scaleY + translateY) + this.node = node + this.isMousedown = true + this.mouseDownX = x + this.mouseDownY = y + }) + this.mindMap.on('mousemove', e => { + if (this.mindMap.opt.readonly) { + return + } + if (!this.isMousedown) { + return + } + e.preventDefault() + let { x, y } = this.mindMap.toPos(e.clientX, e.clientY) + this.mouseMoveX = x + this.mouseMoveY = y + if ( + Math.abs(x - this.mouseDownX) <= 10 && + Math.abs(y - this.mouseDownY) <= 10 && + !this.node.isDrag + ) { + return + } + this.mindMap.renderer.clearAllActive() + this.onMove(x, y) + }) + this.onMouseup = this.onMouseup.bind(this) + this.mindMap.on('node_mouseup', this.onMouseup) + this.mindMap.on('mouseup', this.onMouseup) + } + + // 鼠标松开事件 + onMouseup(e) { + if (!this.isMousedown) { + return + } + this.isMousedown = false + let _nodeIsDrag = this.node.isDrag + this.node.isDrag = false + this.node.show() + this.removeCloneNode() + // 存在重叠子节点,则移动作为其子节点 + if (this.overlapNode) { + this.mindMap.renderer.setNodeActive(this.overlapNode, false) + this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode) + } else if (this.prevNode) { + // 存在前一个相邻节点,作为其下一个兄弟节点 + this.mindMap.renderer.setNodeActive(this.prevNode, false) + this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode) + } else if (this.nextNode) { + // 存在下一个相邻节点,作为其前一个兄弟节点 + this.mindMap.renderer.setNodeActive(this.nextNode, false) + this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode) + } else if (_nodeIsDrag) { + // 自定义位置 + let { x, y } = this.mindMap.toPos( + e.clientX - this.offsetX, + e.clientY - this.offsetY + ) + let { scaleX, scaleY, translateX, translateY } = this.drawTransform + x = (x - translateX) / scaleX + y = (y - translateY) / scaleY + this.node.left = x + this.node.top = y + this.node.customLeft = x + this.node.customTop = y + this.mindMap.execCommand('SET_NODE_CUSTOM_POSITION', this.node, x, y) + this.mindMap.render() + } + this.reset() + } + + // 创建克隆节点 + createCloneNode() { + if (!this.clone) { + // 节点 + this.clone = this.node.group.clone() + this.clone.opacity(0.5) + this.clone.css('z-index', 99999) + this.node.isDrag = true + this.node.hide() + // 连接线 + this.line = this.draw.path() + this.line.opacity(0.5) + this.node.styleLine(this.line, this.node) + // 同级位置占位符 + this.placeholder = this.draw.rect().fill({ + color: this.node.style.merge('lineColor', true) + }) + this.mindMap.draw.add(this.clone) + } + } + + // 移除克隆节点 + removeCloneNode() { + if (!this.clone) { + return + } + this.clone.remove() + this.line.remove() + this.placeholder.remove() + } + + // 拖动中 + onMove(x, y) { + if (!this.isMousedown) { + return + } + this.createCloneNode() + let { scaleX, scaleY, translateX, translateY } = this.drawTransform + this.cloneNodeLeft = x - this.offsetX + this.cloneNodeTop = y - this.offsetY + x = (this.cloneNodeLeft - translateX) / scaleX + y = (this.cloneNodeTop - translateY) / scaleY + let t = this.clone.transform() + this.clone.translate(x - t.translateX, y - t.translateY) + // 连接线 + let parent = this.node.parent + this.line.plot( + this.quadraticCurvePath( + parent.left + parent.width / 2, + parent.top + parent.height / 2, + x + this.node.width / 2, + y + this.node.height / 2 + ) + ) + this.checkOverlapNode() + } + + // 检测重叠节点 + checkOverlapNode() { + if (!this.drawTransform) { + return + } + let { scaleX, scaleY, translateX, translateY } = this.drawTransform + let checkRight = this.cloneNodeLeft + this.node.width * scaleX + let checkBottom = this.cloneNodeTop + this.node.height * scaleX + this.overlapNode = null + this.prevNode = null + this.nextNode = null + this.placeholder.size(0, 0) + bfsWalk(this.mindMap.renderer.root, node => { + if (node.nodeData.data.isActive) { + this.mindMap.renderer.setNodeActive(node, false) + } + if (node === this.node || this.node.isParent(node)) { + return + } + if (this.overlapNode || (this.prevNode && this.nextNode)) { + return + } + let { left, top, width, height } = node + let _left = left + let _top = top + let _bottom = top + height + let right = (left + width) * scaleX + translateX + let bottom = (top + height) * scaleY + translateY + left = left * scaleX + translateX + top = top * scaleY + translateY + // 检测是否重叠 + if (!this.overlapNode) { + if ( + left <= checkRight && + right >= this.cloneNodeLeft && + top <= checkBottom && + bottom >= this.cloneNodeTop + ) { + this.overlapNode = node + } + } + // 检测兄弟节点位置 + if (!this.prevNode && !this.nextNode && !node.isRoot) { + // && this.node.isBrother(node) + if (left <= checkRight && right >= this.cloneNodeLeft) { + if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) { + this.prevNode = node + this.placeholder.size(node.width, 10).move(_left, _bottom) + } else if (checkBottom < top && checkBottom >= top - 10) { + this.nextNode = node + this.placeholder.size(node.width, 10).move(_left, _top - 10) + } + } + } + }) + if (this.overlapNode) { + this.mindMap.renderer.setNodeActive(this.overlapNode, true) + } + } +} + +export default Drag diff --git a/simple-mind-map/src/Event.js b/simple-mind-map/src/Event.js index eb1fce37..fd0860c8 100644 --- a/simple-mind-map/src/Event.js +++ b/simple-mind-map/src/Event.js @@ -1,18 +1,8 @@ import EventEmitter from 'eventemitter3' -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:53:09 - * @Desc: 事件类 - */ +// 事件类 class Event extends EventEmitter { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:53:25 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt = {}) { super() this.opt = opt @@ -34,12 +24,7 @@ class Event extends EventEmitter { this.bind() } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:52:24 - * @Desc: 绑定函数上下文 - */ + // 绑定函数上下文 bindFn() { this.onDrawClick = this.onDrawClick.bind(this) this.onMousedown = this.onMousedown.bind(this) @@ -51,12 +36,7 @@ class Event extends EventEmitter { this.onKeyup = this.onKeyup.bind(this) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:53:43 - * @Desc: 绑定事件 - */ + // 绑定事件 bind() { this.mindMap.svg.on('click', this.onDrawClick) this.mindMap.el.addEventListener('mousedown', this.onMousedown) @@ -73,12 +53,7 @@ class Event extends EventEmitter { window.addEventListener('keyup', this.onKeyup) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:40:51 - * @Desc: 解绑事件 - */ + // 解绑事件 unbind() { this.mindMap.svg.off('click', this.onDrawClick) this.mindMap.el.removeEventListener('mousedown', this.onMousedown) @@ -89,30 +64,17 @@ class Event extends EventEmitter { window.removeEventListener('keyup', this.onKeyup) } - /** - * @Author: 王林 - * @Date: 2021-04-24 13:19:39 - * @Desc: 画布的单击事件 - */ + // 画布的单击事件 onDrawClick(e) { this.emit('draw_click', e) } - /** - * @Author: 王林 - * @Date: 2021-07-16 13:37:30 - * @Desc: svg画布的鼠标按下事件 - */ + // svg画布的鼠标按下事件 onSvgMousedown(e) { this.emit('svg_mousedown', e) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:17:35 - * @Desc: 鼠标按下事件 - */ + // 鼠标按下事件 onMousedown(e) { // e.preventDefault() // 鼠标左键 @@ -124,12 +86,7 @@ class Event extends EventEmitter { this.emit('mousedown', e, this) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:18:32 - * @Desc: 鼠标移动事件 - */ + // 鼠标移动事件 onMousemove(e) { // e.preventDefault() this.mousemovePos.x = e.clientX @@ -142,23 +99,13 @@ class Event extends EventEmitter { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:18:57 - * @Desc: 鼠标松开事件 - */ + // 鼠标松开事件 onMouseup(e) { this.isLeftMousedown = false this.emit('mouseup', e, this) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:46:27 - * @Desc: 鼠标滚动 - */ + // 鼠标滚动 onMousewheel(e) { e.stopPropagation() e.preventDefault() @@ -171,22 +118,13 @@ class Event extends EventEmitter { this.emit('mousewheel', e, dir, this) } - /** - * @Author: 王林 - * @Date: 2021-07-10 22:34:13 - * @Desc: 鼠标右键菜单事件 - */ + // 鼠标右键菜单事件 onContextmenu(e) { e.preventDefault() this.emit('contextmenu', e) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 11:12:11 - * @Desc: 按键松开事件 - */ + // 按键松开事件 onKeyup(e) { this.emit('keyup', e) } diff --git a/simple-mind-map/src/Export.js b/simple-mind-map/src/Export.js index 2dcd3a9b..4f0ed230 100644 --- a/simple-mind-map/src/Export.js +++ b/simple-mind-map/src/Export.js @@ -3,27 +3,15 @@ import JsPDF from 'jspdf' import { SVG } from '@svgdotjs/svg.js' const URL = window.URL || window.webkitURL || window -/** - * @Author: 王林 - * @Date: 2021-07-01 22:05:16 - * @Desc: 导出类 - */ +// 导出类 class Export { - /** - * @Author: 王林 - * @Date: 2021-07-01 22:05:42 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt) { this.mindMap = opt.mindMap this.exportPadding = this.mindMap.opt.exportPadding } - /** - * @Author: 王林 - * @Date: 2021-07-02 07:44:06 - * @Desc: 导出 - */ + // 导出 async export(type, isDownload = true, name = '思维导图', ...args) { if (this[type]) { let result = await this[type](name, ...args) @@ -36,11 +24,7 @@ class Export { } } - /** - * @Author: 王林 - * @Date: 2021-07-04 14:57:40 - * @Desc: 获取svg数据 - */ + // 获取svg数据 async getSvgData() { let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap() // 把图片的url转换成data:url类型,否则导出会丢失图片 @@ -57,11 +41,7 @@ class Export { } } - /** - * @Author: 王林 - * @Date: 2021-07-04 15:25:19 - * @Desc: svg转png - */ + // svg转png svgToPng(svgSrc) { return new Promise((resolve, reject) => { const img = new Image() @@ -99,11 +79,7 @@ class Export { }) } - /** - * @Author: 王林 - * @Date: 2021-07-04 15:32:07 - * @Desc: 在canvas上绘制思维导图背景 - */ + // 在canvas上绘制思维导图背景 drawBackgroundToCanvas(ctx, width, height) { return new Promise((resolve, reject) => { let { @@ -139,10 +115,8 @@ class Export { }) } + // 导出为png /** - * @Author: 王林 - * @Date: 2021-07-01 22:09:51 - * @Desc: 导出为png * 方法1.把svg的图片都转化成data:url格式,再转换 * 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换 */ @@ -160,12 +134,7 @@ class Export { return imgDataUrl } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-08 19:23:08 - * @Desc: 导出为pdf - */ + // 导出为pdf async pdf(name) { let img = await this.png() let pdf = new JsPDF('', 'pt', 'a4') @@ -197,11 +166,7 @@ class Export { image.src = img } - /** - * @Author: 王林 - * @Date: 2021-07-04 15:32:07 - * @Desc: 在svg上绘制思维导图背景 - */ + // 在svg上绘制思维导图背景 drawBackgroundToSvg(svg) { return new Promise(async resolve => { let { @@ -223,11 +188,7 @@ class Export { }) } - /** - * @Author: 王林 - * @Date: 2021-07-04 14:54:07 - * @Desc: 导出为svg - */ + // 导出为svg async svg(name) { let { node } = await this.getSvgData() node.first().before(SVG(`${name}`)) @@ -240,11 +201,7 @@ class Export { return URL.createObjectURL(blob) } - /** - * @Author: 王林 - * @Date: 2021-08-03 22:19:17 - * @Desc: 导出为json - */ + // 导出为json json(name, withConfig = true) { let data = this.mindMap.getData(withConfig) let str = JSON.stringify(data) @@ -252,11 +209,7 @@ class Export { return URL.createObjectURL(blob) } - /** - * @Author: 王林 - * @Date: 2021-08-03 22:24:24 - * @Desc: 专有文件,其实就是json文件 - */ + // 专有文件,其实就是json文件 smm(name, withConfig) { return this.json(name, withConfig) } diff --git a/simple-mind-map/src/KeyCommand.js b/simple-mind-map/src/KeyCommand.js index c50048ca..79aad141 100644 --- a/simple-mind-map/src/KeyCommand.js +++ b/simple-mind-map/src/KeyCommand.js @@ -1,15 +1,7 @@ import { keyMap } from './utils/keyMap' -/** - * @Author: 王林 - * @Date: 2021-04-24 15:20:46 - * @Desc: 快捷按键、命令处理类 - */ +// 快捷按键、命令处理类 export default class KeyCommand { - /** - * @Author: 王林 - * @Date: 2021-04-24 15:21:32 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt) { this.opt = opt this.mindMap = opt.mindMap @@ -21,51 +13,29 @@ export default class KeyCommand { this.bindEvent() } - /** - * @Author: 王林 - * @Date: 2022-08-14 08:57:55 - * @Desc: 暂停快捷键响应 - */ + // 暂停快捷键响应 pause() { this.isPause = true } - /** - * @Author: 王林 - * @Date: 2022-08-14 08:58:43 - * @Desc: 恢复快捷键响应 - */ + // 恢复快捷键响应 recovery() { this.isPause = false } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-16 16:29:01 - * @Desc: 保存当前注册的快捷键数据,然后清空快捷键数据 - */ + // 保存当前注册的快捷键数据,然后清空快捷键数据 save() { this.shortcutMapCache = this.shortcutMap this.shortcutMap = {} } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-16 16:29:38 - * @Desc: 恢复保存的快捷键数据,然后清空缓存数据 - */ + // 恢复保存的快捷键数据,然后清空缓存数据 restore() { this.shortcutMap = this.shortcutMapCache this.shortcutMapCache = {} } - /** - * @Author: 王林 - * @Date: 2021-04-24 15:23:22 - * @Desc: 绑定事件 - */ + // 绑定事件 bindEvent() { window.addEventListener('keydown', e => { if (this.isPause) { @@ -83,11 +53,7 @@ export default class KeyCommand { }) } - /** - * @Author: 王林 - * @Date: 2021-04-24 19:24:53 - * @Desc: 检查键值是否符合 - */ + // 检查键值是否符合 checkKey(e, key) { let o = this.getOriginEventCodeArr(e) let k = this.getKeyCodeArr(key) @@ -107,11 +73,7 @@ export default class KeyCommand { return true } - /** - * @Author: 王林 - * @Date: 2021-04-24 19:15:19 - * @Desc: 获取事件对象里的键值数组 - */ + // 获取事件对象里的键值数组 getOriginEventCodeArr(e) { let arr = [] if (e.ctrlKey || e.metaKey) { @@ -129,11 +91,7 @@ export default class KeyCommand { return arr } - /** - * @Author: 王林 - * @Date: 2021-04-24 19:40:11 - * @Desc: 获取快捷键对应的键值数组 - */ + // 获取快捷键对应的键值数组 getKeyCodeArr(key) { let keyArr = key.split(/\s*\+\s*/) let arr = [] @@ -143,10 +101,8 @@ export default class KeyCommand { return arr } + // 添加快捷键命令 /** - * @Author: 王林 - * @Date: 2021-04-24 15:23:00 - * @Desc: 添加快捷键命令 * Enter * Tab | Insert * Shift + a @@ -161,12 +117,7 @@ export default class KeyCommand { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-27 14:06:16 - * @Desc: 移除快捷键命令 - */ + // 移除快捷键命令 removeShortcut(key, fn) { key.split(/\s*\|\s*/).forEach(item => { if (this.shortcutMap[item]) { @@ -185,11 +136,7 @@ export default class KeyCommand { }) } - /** - * @Author: 王林 - * @Date: 2022-08-14 08:49:58 - * @Desc: 获取指定快捷键的处理函数 - */ + // 获取指定快捷键的处理函数 getShortcutFn(key) { let res = [] key.split(/\s*\|\s*/).forEach(item => { diff --git a/simple-mind-map/src/KeyboardNavigation.js b/simple-mind-map/src/KeyboardNavigation.js index 9b757a02..f4fdfb01 100644 --- a/simple-mind-map/src/KeyboardNavigation.js +++ b/simple-mind-map/src/KeyboardNavigation.js @@ -1,19 +1,9 @@ import { isKey } from './utils/keyMap' import { bfsWalk } from './utils' -/** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 11:06:50 - * @Desc: 键盘导航类 - */ +// 键盘导航类 export default class KeyboardNavigation { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 11:07:24 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt) { this.opt = opt this.mindMap = opt.mindMap @@ -21,12 +11,7 @@ export default class KeyboardNavigation { this.mindMap.on('keyup', this.onKeyup) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 14:12:27 - * @Desc: 处理按键事件 - */ + // 处理按键事件 onKeyup(e) { ;['Left', 'Up', 'Right', 'Down'].forEach(dir => { if (isKey(e, dir)) { @@ -41,12 +26,7 @@ export default class KeyboardNavigation { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 14:12:39 - * @Desc: 聚焦到下一个节点 - */ + // 聚焦到下一个节点 focus(dir) { // 当前聚焦的节点 let currentActiveNode = this.mindMap.renderer.activeNodeList[0] @@ -99,12 +79,7 @@ export default class KeyboardNavigation { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-12 16:22:54 - * @Desc: 1.简单算法 - */ + // 1.简单算法 getFocusNodeBySimpleAlgorithm({ currentActiveNode, currentActiveNodeRect, @@ -143,12 +118,7 @@ export default class KeyboardNavigation { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-12 16:24:54 - * @Desc: 2.阴影算法 - */ + // 2.阴影算法 getFocusNodeByShadowAlgorithm({ currentActiveNode, currentActiveNodeRect, @@ -187,12 +157,7 @@ export default class KeyboardNavigation { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-13 16:15:36 - * @Desc: 3.区域算法 - */ + // 3.区域算法 getFocusNodeByAreaAlgorithm({ currentActiveNode, currentActiveNodeRect, @@ -229,12 +194,7 @@ export default class KeyboardNavigation { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 14:12:50 - * @Desc: 获取节点的位置信息 - */ + // 获取节点的位置信息 getNodeRect(node) { let { scaleX, scaleY, translateX, translateY } = this.mindMap.draw.transform() @@ -247,12 +207,7 @@ export default class KeyboardNavigation { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 14:13:04 - * @Desc: 获取两个节点的距离 - */ + // 获取两个节点的距离 getDistance(node1Rect, node2Rect) { let center1 = this.getCenter(node1Rect) let center2 = this.getCenter(node2Rect) @@ -261,12 +216,7 @@ export default class KeyboardNavigation { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 14:13:11 - * @Desc: 获取节点的中心点 - */ + // 获取节点的中心点 getCenter({ left, right, top, bottom }) { return { x: (left + right) / 2, diff --git a/simple-mind-map/src/MiniMap.js b/simple-mind-map/src/MiniMap.js index b4924975..fe9b12af 100644 --- a/simple-mind-map/src/MiniMap.js +++ b/simple-mind-map/src/MiniMap.js @@ -1,11 +1,6 @@ // 小地图类 class MiniMap { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:00:45 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt) { this.mindMap = opt.mindMap this.isMousedown = false @@ -19,12 +14,7 @@ class MiniMap { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:00:43 - * @Desc: 获取小地图相关数据 - */ + // 获取小地图相关数据 getMiniMap() { const svg = this.mindMap.svg const draw = this.mindMap.draw @@ -61,11 +51,8 @@ class MiniMap { } } + // 计算小地图的渲染数据 /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:05:51 - * @Desc: 计算小地图的渲染数据 * boxWidth:小地图容器的宽度 * boxHeight:小地图容器的高度 */ @@ -124,12 +111,7 @@ class MiniMap { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:22:40 - * @Desc: 小地图鼠标按下事件 - */ + // 小地图鼠标按下事件 onMousedown(e) { this.isMousedown = true this.mousedownPos = { @@ -144,12 +126,7 @@ class MiniMap { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:22:55 - * @Desc: 小地图鼠标移动事件 - */ + // 小地图鼠标移动事件 onMousemove(e, sensitivityNum = 5) { if (!this.isMousedown) { return @@ -161,12 +138,7 @@ class MiniMap { this.mindMap.view.translateYTo(oy * sensitivityNum + this.startViewPos.y) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:23:01 - * @Desc: 小地图鼠标松开事件 - */ + // 小地图鼠标松开事件 onMouseup() { this.isMousedown = false } diff --git a/simple-mind-map/src/Node.js b/simple-mind-map/src/Node.js index c47e13da..34b749e3 100644 --- a/simple-mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -1,1409 +1,1141 @@ -import Style from './Style' -import Shape from './Shape' -import { resizeImgSize, asyncRun } from './utils' -import { Image, SVG, Circle, A, G, Rect, Text } from '@svgdotjs/svg.js' -import btnsSvg from './svg/btns' -import iconsSvg from './svg/icons' - -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 11:26:00 - * @Desc: 节点类 - */ -class Node { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 11:26:17 - * @Desc: 构造函数 - */ - constructor(opt = {}) { - // 节点数据 - this.nodeData = this.handleData(opt.data || {}) - // id - this.uid = opt.uid - // 控制实例 - this.mindMap = opt.mindMap - // 渲染实例 - this.renderer = opt.renderer - // 渲染器 - this.draw = opt.draw || null - // 主题配置 - this.themeConfig = this.mindMap.themeConfig - // 样式实例 - this.style = new Style(this, this.themeConfig) - // 形状实例 - 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 - // 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.group = null - // 节点内容对象 - this._imgData = null - this._iconData = null - this._textData = null - this._hyperlinkData = null - this._tagData = null - this._noteData = null - this.noteEl = null - this._expandBtn = null - this._lines = [] - this._generalizationLine = null - this._generalizationNode = null - // 尺寸信息 - this._rectInfo = { - imgContentWidth: 0, - imgContentHeight: 0, - textContentWidth: 0, - textContentHeight: 0 - } - // 概要节点的宽高 - this._generalizationNodeWidth = 0 - this._generalizationNodeHeight = 0 - // 各种文字信息的间距 - this.textContentItemMargin = this.mindMap.opt.textContentMargin - // 图片和文字节点的间距 - this.blockContentMargin = this.mindMap.opt.imgTextMargin - // 展开收缩按钮尺寸 - this.expandBtnSize = this.mindMap.opt.expandBtnSize - // 初始渲染 - this.initRender = true - // 初始化 - // this.createNodeData() - this.getSize() - } - - // 支持自定义位置 - 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 - } - - /** - * @Author: 王林 - * @Date: 2021-07-12 07:40:47 - * @Desc: 更新主题配置 - */ - updateThemeConfig() { - // 主题配置 - this.themeConfig = this.mindMap.themeConfig - // 样式实例 - this.style.updateThemeConfig(this.themeConfig) - } - - /** - * @Author: 王林 - * @Date: 2021-07-05 23:11:39 - * @Desc: 复位部分布局时会重新设置的数据 - */ - reset() { - this.children = [] - this.parent = null - this.isRoot = false - this.layerIndex = 0 - this.left = 0 - this.top = 0 - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 10:12:31 - * @Desc: 处理数据 - */ - 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 - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-02 19:53:40 - * @Desc: 检查节点是否存在自定义数据 - */ - hasCustomPosition() { - return this.customLeft !== undefined && this.customTop !== undefined - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-04 09:06:56 - * @Desc: 检查节点是否存在自定义位置的祖先节点 - */ - ancestorHasCustomPosition() { - let node = this - while (node) { - if (node.hasCustomPosition()) { - return true - } - node = node.parent - } - return false - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 15:55:04 - * @Desc: 添加子节点 - */ - addChildren(node) { - this.children.push(node) - } - - /** - * @Author: 王林 - * @Date: 2021-07-06 22:08:09 - * @Desc: 创建节点的各个内容对象数据 - */ - createNodeData() { - this._imgData = this.createImgNode() - this._iconData = this.createIconNode() - this._textData = this.createTextNode() - this._hyperlinkData = this.createHyperlinkNode() - this._tagData = this.createTagNode() - this._noteData = this.createNoteNode() - this.createGeneralizationNode() - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 09:20:02 - * @Desc: 解绑所有事件 - */ - removeAllEvent() { - if (this._noteData) { - this._noteData.node.off(['mouseover', 'mouseout']) - } - if (this._expandBtn) { - this._expandBtn.off(['mouseover', 'mouseout', 'click']) - } - if (this.group) { - this.group.off([ - 'click', - 'dblclick', - 'contextmenu', - 'mousedown', - 'mouseup' - ]) - } - } - - /** - * @Author: 王林 - * @Date: 2021-07-07 21:27:24 - * @Desc: 移除节点内容 - */ - removeAllNode() { - // 节点内的内容 - ;[ - this._imgData, - this._iconData, - this._textData, - this._hyperlinkData, - this._tagData, - this._noteData - ].forEach(item => { - if (item && item.node) item.node.remove() - }) - this._imgData = null - this._iconData = null - this._textData = null - this._hyperlinkData = null - this._tagData = null - this._noteData = null - // 展开收缩按钮 - if (this._expandBtn) { - this._expandBtn.remove() - this._expandBtn = null - } - // 组 - if (this.group) { - this.group.clear() - this.group.remove() - this.group = null - } - // 概要 - this.removeGeneralization() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 09:46:23 - * @Desc: 计算节点的宽高 - */ - getSize() { - this.removeAllNode() - this.createNodeData() - let { width, height } = this.getNodeRect() - // 判断节点尺寸是否有变化 - let changed = this.width !== width || this.height !== height - this.width = width - this.height = height - return changed - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:52:17 - * @Desc: 计算节点尺寸信息 - */ - getNodeRect() { - // 宽高 - let imgContentWidth = 0 - let imgContentHeight = 0 - let textContentWidth = 0 - let textContentHeight = 0 - // 存在图片 - if (this._imgData) { - this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width - this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height - } - // 图标 - if (this._iconData.length > 0) { - textContentWidth += this._iconData.reduce((sum, cur) => { - textContentHeight = Math.max(textContentHeight, cur.height) - return (sum += cur.width + this.textContentItemMargin) - }, 0) - } - // 文字 - if (this._textData) { - textContentWidth += this._textData.width - textContentHeight = Math.max(textContentHeight, this._textData.height) - } - // 超链接 - if (this._hyperlinkData) { - textContentWidth += this._hyperlinkData.width - textContentHeight = Math.max( - textContentHeight, - this._hyperlinkData.height - ) - } - // 标签 - if (this._tagData.length > 0) { - textContentWidth += this._tagData.reduce((sum, cur) => { - textContentHeight = Math.max(textContentHeight, cur.height) - return (sum += cur.width + this.textContentItemMargin) - }, 0) - } - // 备注 - if (this._noteData) { - textContentWidth += this._noteData.width - textContentHeight = Math.max(textContentHeight, this._noteData.height) - } - // 文字内容部分的尺寸 - this._rectInfo.textContentWidth = textContentWidth - this._rectInfo.textContentHeight = textContentHeight - // 间距 - let margin = - imgContentHeight > 0 && textContentHeight > 0 - ? this.blockContentMargin - : 0 - let { paddingX, paddingY } = this.getPaddingVale() - // 纯内容宽高 - let _width = Math.max(imgContentWidth, textContentWidth) - let _height = imgContentHeight + textContentHeight - // 计算节点形状需要的附加内边距 - let { paddingX: shapePaddingX, paddingY: shapePaddingY } = - this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY) - this.shapePadding.paddingX = shapePaddingX - this.shapePadding.paddingY = shapePaddingY - return { - width: _width + paddingX * 2 + shapePaddingX * 2, - height: _height + paddingY * 2 + margin + shapePaddingY * 2 - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:06:17 - * @Desc: 创建图片节点 - */ - createImgNode() { - let img = this.nodeData.data.image - if (!img) { - return - } - let imgSize = this.getImgShowSize() - let node = new Image().load(img).size(...imgSize) - if (this.nodeData.data.imageTitle) { - node.attr('title', this.nodeData.data.imageTitle) - } - node.on('dblclick', e => { - this.mindMap.emit('node_img_dblclick', this, e) - }) - return { - node, - width: imgSize[0], - height: imgSize[1] - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 10:12:51 - * @Desc: 获取图片显示宽高 - */ - getImgShowSize() { - return resizeImgSize( - this.nodeData.data.imageSize.width, - this.nodeData.data.imageSize.height, - this.themeConfig.imgMaxWidth, - this.themeConfig.imgMaxHeight - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:10:48 - * @Desc: 创建icon节点 - */ - createIconNode() { - let _data = this.nodeData.data - if (!_data.icon || _data.icon.length <= 0) { - return [] - } - let iconSize = this.themeConfig.iconSize - return _data.icon.map(item => { - return { - node: SVG(iconsSvg.getNodeIconListIcon(item)).size(iconSize, iconSize), - width: iconSize, - height: iconSize - } - }) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:08:56 - * @Desc: 创建文本节点 - */ - createTextNode() { - let g = new G() - let fontSize = this.getStyle( - 'fontSize', - this.isRoot, - this.nodeData.data.isActive - ) - let lineHeight = this.getStyle( - 'lineHeight', - this.isRoot, - this.nodeData.data.isActive - ) - this.nodeData.data.text.split(/\n/gim).forEach((item, index) => { - let node = new Text().text(item) - this.style.text(node) - node.y(fontSize * lineHeight * index) - g.add(node) - }) - let { width, height } = g.bbox() - return { - node: g, - width, - height - } - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 15:28:54 - * @Desc: 创建超链接节点 - */ - createHyperlinkNode() { - let { hyperlink, hyperlinkTitle } = this.nodeData.data - if (!hyperlink) { - return - } - let iconSize = this.themeConfig.iconSize - let node = new SVG() - // 超链接节点 - let a = new A().to(hyperlink).target('_blank') - a.node.addEventListener('click', e => { - e.stopPropagation() - }) - if (hyperlinkTitle) { - a.attr('title', hyperlinkTitle) - } - // 添加一个透明的层,作为鼠标区域 - a.rect(iconSize, iconSize).fill({ color: 'transparent' }) - // 超链接图标 - let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize) - this.style.iconNode(iconNode) - a.add(iconNode) - node.add(a) - return { - node, - width: iconSize, - height: iconSize - } - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 19:49:15 - * @Desc: 创建标签节点 - */ - createTagNode() { - let tagData = this.nodeData.data.tag - if (!tagData || tagData.length <= 0) { - return [] - } - let nodes = [] - tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => { - let tag = new G() - // 标签文本 - let text = new Text().text(item).x(8).cy(10) - this.style.tagText(text, index) - let { width } = text.bbox() - // 标签矩形 - let rect = new Rect().size(width + 16, 20) - this.style.tagRect(rect, index) - tag.add(rect).add(text) - nodes.push({ - node: tag, - width: width + 16, - height: 20 - }) - }) - return nodes - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 21:19:36 - * @Desc: 创建备注节点 - */ - createNoteNode() { - if (!this.nodeData.data.note) { - return null - } - let iconSize = this.themeConfig.iconSize - let node = new SVG().attr('cursor', 'pointer') - // 透明的层,用来作为鼠标区域 - node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' })) - // 备注图标 - let iconNode = SVG(iconsSvg.note).size(iconSize, iconSize) - this.style.iconNode(iconNode) - node.add(iconNode) - // 备注tooltip - if (!this.mindMap.opt.customNoteContentShow) { - if (!this.noteEl) { - this.noteEl = document.createElement('div') - this.noteEl.style.cssText = ` - position: absolute; - padding: 10px; - border-radius: 5px; - box-shadow: 0 2px 5px rgb(0 0 0 / 10%); - display: none; - background-color: #fff; - ` - document.body.appendChild(this.noteEl) - } - this.noteEl.innerText = this.nodeData.data.note - } - node.on('mouseover', () => { - let { left, top } = node.node.getBoundingClientRect() - if (!this.mindMap.opt.customNoteContentShow) { - this.noteEl.style.left = left + 'px' - this.noteEl.style.top = top + iconSize + 'px' - this.noteEl.style.display = 'block' - } else { - this.mindMap.opt.customNoteContentShow.show( - this.nodeData.data.note, - left, - top + iconSize - ) - } - }) - node.on('mouseout', () => { - if (!this.mindMap.opt.customNoteContentShow) { - this.noteEl.style.display = 'none' - } else { - this.mindMap.opt.customNoteContentShow.hide() - } - }) - return { - node, - width: iconSize, - height: iconSize - } - } - - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 22:02:07 - * @Desc: 获取节点形状 - */ - getShape() { - // 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形 - return this.themeConfig.nodeUseLineStyle - ? 'rectangle' - : this.style.getStyle('shape', false, false) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 11:10:11 - * @Desc: 定位节点内容 - */ - layout() { - let { width, textContentItemMargin } = this - let { paddingY } = this.getPaddingVale() - paddingY += this.shapePadding.paddingY - // 创建组 - this.group = new G() - // 概要节点添加一个带所属节点id的类名 - if (this.isGeneralization && this.generalizationBelongNode) { - this.group.addClass('generalization_' + this.generalizationBelongNode.uid) - } - this.draw.add(this.group) - this.update(true) - // 节点形状 - const shape = this.getShape() - this.style[shape === 'rectangle' ? 'rect' : 'shape']( - this.shapeInstance.createShape() - ) - // 图片节点 - let imgHeight = 0 - if (this._imgData) { - imgHeight = this._imgData.height - this.group.add(this._imgData.node) - this._imgData.node.cx(width / 2).y(paddingY) - } - // 内容节点 - let textContentNested = new G() - let textContentOffsetX = 0 - // icon - let iconNested = new G() - if (this._iconData && this._iconData.length > 0) { - let iconLeft = 0 - this._iconData.forEach(item => { - item.node - .x(textContentOffsetX + iconLeft) - .y((this._rectInfo.textContentHeight - item.height) / 2) - iconNested.add(item.node) - iconLeft += item.width + textContentItemMargin - }) - textContentNested.add(iconNested) - textContentOffsetX += iconLeft - } - // 文字 - if (this._textData) { - this._textData.node.x(textContentOffsetX).y(0) - textContentNested.add(this._textData.node) - textContentOffsetX += this._textData.width + textContentItemMargin - } - // 超链接 - if (this._hyperlinkData) { - this._hyperlinkData.node - .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2) - textContentNested.add(this._hyperlinkData.node) - textContentOffsetX += this._hyperlinkData.width + textContentItemMargin - } - // 标签 - let tagNested = new G() - if (this._tagData && this._tagData.length > 0) { - let tagLeft = 0 - this._tagData.forEach(item => { - item.node - .x(textContentOffsetX + tagLeft) - .y((this._rectInfo.textContentHeight - item.height) / 2) - tagNested.add(item.node) - tagLeft += item.width + textContentItemMargin - }) - textContentNested.add(tagNested) - textContentOffsetX += tagLeft - } - // 备注 - if (this._noteData) { - this._noteData.node - .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._noteData.height) / 2) - textContentNested.add(this._noteData.node) - textContentOffsetX += this._noteData.width - } - // 文字内容整体 - textContentNested.translate( - width / 2 - textContentNested.bbox().width / 2, - imgHeight + - paddingY + - (imgHeight > 0 && this._rectInfo.textContentHeight > 0 - ? this.blockContentMargin - : 0) - ) - this.group.add(textContentNested) - // 单击事件,选中节点 - this.group.on('click', e => { - this.mindMap.emit('node_click', this, e) - this.active(e) - }) - this.group.on('mousedown', e => { - e.stopPropagation() - this.mindMap.emit('node_mousedown', this, e) - }) - this.group.on('mouseup', e => { - e.stopPropagation() - this.mindMap.emit('node_mouseup', this, e) - }) - // 双击事件 - this.group.on('dblclick', e => { - if (this.mindMap.opt.readonly) { - return - } - e.stopPropagation() - this.mindMap.emit('node_dblclick', this, e) - }) - // 右键菜单事件 - this.group.on('contextmenu', e => { - if (this.mindMap.opt.readonly || this.isGeneralization) { - return - } - e.stopPropagation() - e.preventDefault() - if (this.nodeData.data.isActive) { - this.renderer.clearActive() - } - this.active(e) - this.mindMap.emit('node_contextmenu', e, this) - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 16:44:22 - * @Desc: 激活节点 - */ - active(e) { - if (this.mindMap.opt.readonly) { - return - } - e && e.stopPropagation() - if (this.nodeData.data.isActive) { - return - } - this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) - this.renderer.clearActive() - this.mindMap.execCommand('SET_NODE_ACTIVE', this, true) - this.renderer.addActiveNode(this) - this.mindMap.emit('node_active', this, this.renderer.activeNodeList) - } - - /** - * @Author: 王林 - * @Date: 2021-07-04 20:20:09 - * @Desc: 渲染节点到画布,会移除旧的,创建新的 - */ - renderNode() { - // 连线 - this.renderLine() - this.removeAllEvent() - this.removeAllNode() - this.createNodeData() - this.layout() - } - - /** - * @Author: 王林 - * @Date: 2021-07-04 22:47:01 - * @Desc: 更新节点 - */ - update(layout = false) { - if (!this.group) { - return - } - // 需要移除展开收缩按钮 - if (this._expandBtn && this.nodeData.children.length <= 0) { - this.removeExpandBtn() - } else if (!this._expandBtn && this.nodeData.children.length > 0) { - // 需要添加展开收缩按钮 - this.renderExpandBtn() - } else { - this.updateExpandBtnPos() - } - this.renderGeneralization() - let t = this.group.transform() - // 节点使用横线风格,有两种结构需要调整节点的位置 - let nodeUseLineStyleOffset = 0 - if ( - ['logicalStructure', 'mindMap'].includes(this.mindMap.opt.layout) && - !this.isRoot && - !this.isGeneralization && - this.themeConfig.nodeUseLineStyle - ) { - nodeUseLineStyleOffset = this.height / 2 - } - if (!layout) { - this.group - .animate(300) - .translate( - this.left - t.translateX, - this.top - t.translateY - nodeUseLineStyleOffset - ) - } else { - this.group.translate( - this.left - t.translateX, - this.top - t.translateY - nodeUseLineStyleOffset - ) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 13:55:58 - * @Desc: 递归渲染 - */ - render(callback = () => {}) { - // 节点 - if (this.initRender) { - this.initRender = false - this.renderNode() - } else { - // 连线 - this.renderLine() - this.update() - } - // 子节点 - if ( - this.children && - this.children.length && - this.nodeData.data.expand !== false - ) { - let index = 0 - asyncRun( - this.children.map(item => { - return () => { - item.render(() => { - index++ - if (index >= this.children.length) { - callback() - } - }) - } - }) - ) - } else { - callback() - } - // 手动插入的节点立即获得焦点并且开启编辑模式 - if (this.nodeData.inserting) { - delete this.nodeData.inserting - this.active() - this.mindMap.emit('node_dblclick', this) - } - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 09:24:55 - * @Desc: 递归删除 - */ - remove() { - this.initRender = true - this.removeAllEvent() - this.removeAllNode() - this.removeLine() - // 子节点 - if (this.children && this.children.length) { - asyncRun( - this.children.map(item => { - return () => { - item.remove() - } - }) - ) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 18:39:14 - * @Desc: 隐藏节点 - */ - hide() { - this.group.hide() - this.hideGeneralization() - if (this.parent) { - let index = this.parent.children.indexOf(this) - this.parent._lines[index].hide() - } - // 子节点 - if (this.children && this.children.length) { - asyncRun( - this.children.map(item => { - return () => { - item.hide() - } - }) - ) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-23 18:39:14 - * @Desc: 显示节点 - */ - show() { - if (!this.group) { - return - } - this.group.show() - this.showGeneralization() - if (this.parent) { - let index = this.parent.children.indexOf(this) - this.parent._lines[index] && this.parent._lines[index].show() - } - // 子节点 - if (this.children && this.children.length) { - asyncRun( - this.children.map(item => { - return () => { - item.show() - } - }) - ) - } - } - - /** - * @Author: 王林 - * @Date: 2021-04-10 22:01:53 - * @Desc: 连线 - */ - renderLine(deep = false) { - if (this.nodeData.data.expand === false) { - return - } - let childrenLen = this.nodeData.children.length - if (childrenLen > this._lines.length) { - // 创建缺少的线 - new Array(childrenLen - this._lines.length).fill(0).forEach(() => { - this._lines.push(this.draw.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, - (line, node) => { - // 添加样式 - this.styleLine(line, node) - }, - this.style.getStyle('lineStyle', true) - ) - // 子级的连线也需要更新 - if (deep && this.children && this.children.length > 0) { - this.children.forEach(item => { - item.renderLine(deep) - }) - } - } - - /** - * javascript comment - * @Author: flydreame - * @Date: 2022-09-17 12:41:29 - * @Desc: 设置连线样式 - */ - styleLine(line, node) { - let width = - node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true) - let color = - node.getSelfInhertStyle('lineColor') || node.getStyle('lineColor', true) - let dasharray = - node.getSelfInhertStyle('lineDasharray') || - node.getStyle('lineDasharray', true) - this.style.line(line, { - width, - color, - dasharray - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 16:40:21 - * @Desc: 移除连线 - */ - removeLine() { - this._lines.forEach(line => { - line.remove() - }) - this._lines = [] - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-01 09:27:30 - * @Desc: 检查是否存在概要 - */ - checkHasGeneralization() { - return !!this.nodeData.data.generalization - } - - /** - * @Author: 王林 - * @Date: 2022-07-31 09:41:28 - * @Desc: 创建概要节点 - */ - createGeneralizationNode() { - if (this.isGeneralization || !this.checkHasGeneralization()) { - return - } - if (!this._generalizationLine) { - this._generalizationLine = this.draw.path() - } - if (!this._generalizationNode) { - this._generalizationNode = new Node({ - data: { - data: this.nodeData.data.generalization - }, - uid: this.mindMap.uid++, - renderer: this.renderer, - mindMap: this.mindMap, - draw: this.draw, - isGeneralization: true - }) - this._generalizationNodeWidth = this._generalizationNode.width - this._generalizationNodeHeight = this._generalizationNode.height - this._generalizationNode.generalizationBelongNode = this - if (this.nodeData.data.generalization.isActive) { - this.renderer.addActiveNode(this._generalizationNode) - } - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-01 15:38:52 - * @Desc: 更新概要节点 - */ - updateGeneralization() { - this.removeGeneralization() - this.createGeneralizationNode() - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 08:35:51 - * @Desc: 渲染概要节点 - */ - renderGeneralization() { - if (this.isGeneralization) { - return - } - if (!this.checkHasGeneralization()) { - this.removeGeneralization() - this._generalizationNodeWidth = 0 - this._generalizationNodeHeight = 0 - return - } - if (this.nodeData.data.expand === false) { - this.removeGeneralization() - return - } - this.createGeneralizationNode() - this.renderer.layout.renderGeneralization( - this, - this._generalizationLine, - this._generalizationNode - ) - this.style.generalizationLine(this._generalizationLine) - this._generalizationNode.render() - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 13:11:27 - * @Desc: 删除概要节点 - */ - removeGeneralization() { - if (this._generalizationLine) { - this._generalizationLine.remove() - this._generalizationLine = null - } - if (this._generalizationNode) { - // 删除概要节点时要同步从激活节点里删除 - this.renderer.removeActiveNode(this._generalizationNode) - this._generalizationNode.remove() - this._generalizationNode = null - } - // hack修复当激活一个节点时创建概要,然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题 - if (this.generalizationBelongNode) { - this.draw - .find('.generalization_' + this.generalizationBelongNode.uid) - .remove() - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-01 09:56:46 - * @Desc: 隐藏概要节点 - */ - hideGeneralization() { - if (this._generalizationLine) { - this._generalizationLine.hide() - } - if (this._generalizationNode) { - this._generalizationNode.hide() - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-01 09:57:42 - * @Desc: 显示概要节点 - */ - showGeneralization() { - if (this._generalizationLine) { - this._generalizationLine.show() - } - if (this._generalizationNode) { - this._generalizationNode.show() - } - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 17:59:14 - * @Desc: 创建或更新展开收缩按钮内容 - */ - updateExpandBtnNode() { - if (this._expandBtn) { - this._expandBtn.clear() - } - let iconSvg - if (this.nodeData.data.expand === false) { - iconSvg = btnsSvg.open - } else { - iconSvg = btnsSvg.close - } - let node = SVG(iconSvg).size(this.expandBtnSize, this.expandBtnSize) - let fillNode = new Circle().size(this.expandBtnSize) - node.x(0).y(-this.expandBtnSize / 2) - fillNode.x(0).y(-this.expandBtnSize / 2) - this.style.iconBtn(node, fillNode) - if (this._expandBtn) this._expandBtn.add(fillNode).add(node) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-12 18:18:13 - * @Desc: 更新展开收缩按钮位置 - */ - updateExpandBtnPos() { - if (!this._expandBtn) { - return - } - this.renderer.layout.renderExpandBtn(this, this._expandBtn) - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 19:47:01 - * @Desc: 展开收缩按钮 - */ - renderExpandBtn() { - if ( - !this.nodeData.children || - this.nodeData.children.length <= 0 || - this.isRoot - ) { - return - } - this._expandBtn = new G() - this.updateExpandBtnNode() - this._expandBtn.on('mouseover', e => { - e.stopPropagation() - this._expandBtn.css({ - cursor: 'pointer' - }) - }) - this._expandBtn.on('mouseout', e => { - e.stopPropagation() - this._expandBtn.css({ - cursor: 'auto' - }) - }) - this._expandBtn.on('click', e => { - e.stopPropagation() - // 展开收缩 - this.mindMap.execCommand( - 'SET_NODE_EXPAND', - this, - !this.nodeData.data.expand - ) - this.mindMap.emit('expand_btn_click', this) - }) - this.group.add(this._expandBtn) - this.updateExpandBtnPos() - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 13:26:00 - * @Desc: 移除展开收缩按钮 - */ - removeExpandBtn() { - if (this._expandBtn) { - this._expandBtn.off(['mouseover', 'mouseout', 'click']) - this._expandBtn.clear() - this._expandBtn.remove() - this._expandBtn = null - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-25 09:51:37 - * @Desc: 检测当前节点是否是某个节点的祖先节点 - */ - isParent(node) { - if (this === node) { - return false - } - let parent = node.parent - while (parent) { - if (this === parent) { - return true - } - parent = parent.parent - } - return false - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-25 10:32:34 - * @Desc: 检测当前节点是否是某个节点的兄弟节点 - */ - isBrother(node) { - if (!this.parent || this === node) { - return false - } - return this.parent.children.find(item => { - return item === node - }) - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 22:51:57 - * @Desc: 获取padding值 - */ - getPaddingVale() { - return { - paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive), - paddingY: this.getStyle('paddingY', true, this.nodeData.data.isActive) - } - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 21:48:49 - * @Desc: 获取某个样式 - */ - getStyle(prop, root, isActive) { - let v = this.style.merge(prop, root, isActive) - return v === undefined ? '' : v - } - - /** - * javascript comment - * @Author: flydreame - * @Date: 2022-09-17 11:21:15 - * @Desc: 获取自定义样式 - */ - getSelfStyle(prop) { - return this.style.getSelfStyle(prop) - } - - /** - * javascript comment - * @Author: flydreame - * @Date: 2022-09-17 11:21:26 - * @Desc: 获取最近一个存在自身自定义样式的祖先节点的自定义样式 - */ - getParentSelfStyle(prop) { - if (this.parent) { - return ( - this.parent.getSelfStyle(prop) || this.parent.getParentSelfStyle(prop) - ) - } - return null - } - - /** - * javascript comment - * @Author: flydreame - * @Date: 2022-09-17 12:15:30 - * @Desc: 获取自身可继承的自定义样式 - */ - getSelfInhertStyle(prop) { - return ( - this.getSelfStyle(prop) || // 自身 - this.getParentSelfStyle(prop) - ) // 父级 - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 22:18:07 - * @Desc: 修改某个样式 - */ - setStyle(prop, value, isActive) { - this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive) - } - - /** - * @Author: 王林 - * @Date: 2021-06-22 22:04:02 - * @Desc: 获取数据 - */ - getData(key) { - return key ? this.nodeData.data[key] || '' : this.nodeData.data - } - - /** - * @Author: 王林 - * @Date: 2021-06-22 22:12:01 - * @Desc: 设置数据 - */ - setData(data = {}) { - this.mindMap.execCommand('SET_NODE_DATA', this, data) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:41:28 - * @Desc: 设置文本 - */ - setText(text) { - this.mindMap.execCommand('SET_NODE_TEXT', this, text) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:42:19 - * @Desc: 设置图片 - */ - setImage(imgData) { - this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:47:29 - * @Desc: 设置图标 - */ - setIcon(icons) { - this.mindMap.execCommand('SET_NODE_ICON', this, icons) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:50:41 - * @Desc: 设置超链接 - */ - setHyperlink(link, title) { - this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:53:24 - * @Desc: 设置备注 - */ - setNote(note) { - this.mindMap.execCommand('SET_NODE_NOTE', this, note) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:55:08 - * @Desc: 设置标签 - */ - setTag(tag) { - this.mindMap.execCommand('SET_NODE_TAG', this, tag) - } - - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 21:47:45 - * @Desc: 设置形状 - */ - setShape(shape) { - this.mindMap.execCommand('SET_NODE_SHAPE', this, shape) - } -} - -export default Node +import Style from './Style' +import Shape from './Shape' +import { resizeImgSize, asyncRun } from './utils' +import { Image, SVG, Circle, A, G, Rect, Text } from '@svgdotjs/svg.js' +import btnsSvg from './svg/btns' +import iconsSvg from './svg/icons' + +// 节点类 +class Node { + // 构造函数 + constructor(opt = {}) { + // 节点数据 + this.nodeData = this.handleData(opt.data || {}) + // id + this.uid = opt.uid + // 控制实例 + this.mindMap = opt.mindMap + // 渲染实例 + this.renderer = opt.renderer + // 渲染器 + this.draw = opt.draw || null + // 主题配置 + this.themeConfig = this.mindMap.themeConfig + // 样式实例 + this.style = new Style(this, this.themeConfig) + // 形状实例 + 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 + // 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.group = null + // 节点内容对象 + this._imgData = null + this._iconData = null + this._textData = null + this._hyperlinkData = null + this._tagData = null + this._noteData = null + this.noteEl = null + this._expandBtn = null + this._lines = [] + this._generalizationLine = null + this._generalizationNode = null + // 尺寸信息 + this._rectInfo = { + imgContentWidth: 0, + imgContentHeight: 0, + textContentWidth: 0, + textContentHeight: 0 + } + // 概要节点的宽高 + this._generalizationNodeWidth = 0 + this._generalizationNodeHeight = 0 + // 各种文字信息的间距 + this.textContentItemMargin = this.mindMap.opt.textContentMargin + // 图片和文字节点的间距 + this.blockContentMargin = this.mindMap.opt.imgTextMargin + // 展开收缩按钮尺寸 + this.expandBtnSize = this.mindMap.opt.expandBtnSize + // 初始渲染 + this.initRender = true + // 初始化 + // this.createNodeData() + this.getSize() + } + + // 支持自定义位置 + 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 + } + + // 更新主题配置 + updateThemeConfig() { + // 主题配置 + this.themeConfig = this.mindMap.themeConfig + // 样式实例 + this.style.updateThemeConfig(this.themeConfig) + } + + // 复位部分布局时会重新设置的数据 + reset() { + this.children = [] + this.parent = null + this.isRoot = false + this.layerIndex = 0 + this.left = 0 + this.top = 0 + } + + // 处理数据 + 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 + } + + // 检查节点是否存在自定义数据 + hasCustomPosition() { + return this.customLeft !== undefined && this.customTop !== undefined + } + + // 检查节点是否存在自定义位置的祖先节点 + ancestorHasCustomPosition() { + let node = this + while (node) { + if (node.hasCustomPosition()) { + return true + } + node = node.parent + } + return false + } + + // 添加子节点 + addChildren(node) { + this.children.push(node) + } + + // 创建节点的各个内容对象数据 + createNodeData() { + this._imgData = this.createImgNode() + this._iconData = this.createIconNode() + this._textData = this.createTextNode() + this._hyperlinkData = this.createHyperlinkNode() + this._tagData = this.createTagNode() + this._noteData = this.createNoteNode() + this.createGeneralizationNode() + } + + // 解绑所有事件 + removeAllEvent() { + if (this._noteData) { + this._noteData.node.off(['mouseover', 'mouseout']) + } + if (this._expandBtn) { + this._expandBtn.off(['mouseover', 'mouseout', 'click']) + } + if (this.group) { + this.group.off([ + 'click', + 'dblclick', + 'contextmenu', + 'mousedown', + 'mouseup' + ]) + } + } + + // 移除节点内容 + removeAllNode() { + // 节点内的内容 + ;[ + this._imgData, + this._iconData, + this._textData, + this._hyperlinkData, + this._tagData, + this._noteData + ].forEach(item => { + if (item && item.node) item.node.remove() + }) + this._imgData = null + this._iconData = null + this._textData = null + this._hyperlinkData = null + this._tagData = null + this._noteData = null + // 展开收缩按钮 + if (this._expandBtn) { + this._expandBtn.remove() + this._expandBtn = null + } + // 组 + if (this.group) { + this.group.clear() + this.group.remove() + this.group = null + } + // 概要 + this.removeGeneralization() + } + + // 计算节点的宽高 + getSize() { + this.removeAllNode() + this.createNodeData() + let { width, height } = this.getNodeRect() + // 判断节点尺寸是否有变化 + let changed = this.width !== width || this.height !== height + this.width = width + this.height = height + return changed + } + + // 计算节点尺寸信息 + getNodeRect() { + // 宽高 + let imgContentWidth = 0 + let imgContentHeight = 0 + let textContentWidth = 0 + let textContentHeight = 0 + // 存在图片 + if (this._imgData) { + this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width + this._rectInfo.imgContentHeight = imgContentHeight = this._imgData.height + } + // 图标 + if (this._iconData.length > 0) { + textContentWidth += this._iconData.reduce((sum, cur) => { + textContentHeight = Math.max(textContentHeight, cur.height) + return (sum += cur.width + this.textContentItemMargin) + }, 0) + } + // 文字 + if (this._textData) { + textContentWidth += this._textData.width + textContentHeight = Math.max(textContentHeight, this._textData.height) + } + // 超链接 + if (this._hyperlinkData) { + textContentWidth += this._hyperlinkData.width + textContentHeight = Math.max( + textContentHeight, + this._hyperlinkData.height + ) + } + // 标签 + if (this._tagData.length > 0) { + textContentWidth += this._tagData.reduce((sum, cur) => { + textContentHeight = Math.max(textContentHeight, cur.height) + return (sum += cur.width + this.textContentItemMargin) + }, 0) + } + // 备注 + if (this._noteData) { + textContentWidth += this._noteData.width + textContentHeight = Math.max(textContentHeight, this._noteData.height) + } + // 文字内容部分的尺寸 + this._rectInfo.textContentWidth = textContentWidth + this._rectInfo.textContentHeight = textContentHeight + // 间距 + let margin = + imgContentHeight > 0 && textContentHeight > 0 + ? this.blockContentMargin + : 0 + let { paddingX, paddingY } = this.getPaddingVale() + // 纯内容宽高 + let _width = Math.max(imgContentWidth, textContentWidth) + let _height = imgContentHeight + textContentHeight + // 计算节点形状需要的附加内边距 + let { paddingX: shapePaddingX, paddingY: shapePaddingY } = + this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY) + this.shapePadding.paddingX = shapePaddingX + this.shapePadding.paddingY = shapePaddingY + return { + width: _width + paddingX * 2 + shapePaddingX * 2, + height: _height + paddingY * 2 + margin + shapePaddingY * 2 + } + } + + // 创建图片节点 + createImgNode() { + let img = this.nodeData.data.image + if (!img) { + return + } + let imgSize = this.getImgShowSize() + let node = new Image().load(img).size(...imgSize) + if (this.nodeData.data.imageTitle) { + node.attr('title', this.nodeData.data.imageTitle) + } + node.on('dblclick', e => { + this.mindMap.emit('node_img_dblclick', this, e) + }) + return { + node, + width: imgSize[0], + height: imgSize[1] + } + } + + // 获取图片显示宽高 + getImgShowSize() { + return resizeImgSize( + this.nodeData.data.imageSize.width, + this.nodeData.data.imageSize.height, + this.themeConfig.imgMaxWidth, + this.themeConfig.imgMaxHeight + ) + } + + // 创建icon节点 + createIconNode() { + let _data = this.nodeData.data + if (!_data.icon || _data.icon.length <= 0) { + return [] + } + let iconSize = this.themeConfig.iconSize + return _data.icon.map(item => { + return { + node: SVG(iconsSvg.getNodeIconListIcon(item)).size(iconSize, iconSize), + width: iconSize, + height: iconSize + } + }) + } + + // 创建文本节点 + createTextNode() { + let g = new G() + let fontSize = this.getStyle( + 'fontSize', + this.isRoot, + this.nodeData.data.isActive + ) + let lineHeight = this.getStyle( + 'lineHeight', + this.isRoot, + this.nodeData.data.isActive + ) + this.nodeData.data.text.split(/\n/gim).forEach((item, index) => { + let node = new Text().text(item) + this.style.text(node) + node.y(fontSize * lineHeight * index) + g.add(node) + }) + let { width, height } = g.bbox() + return { + node: g, + width, + height + } + } + + // 创建超链接节点 + createHyperlinkNode() { + let { hyperlink, hyperlinkTitle } = this.nodeData.data + if (!hyperlink) { + return + } + let iconSize = this.themeConfig.iconSize + let node = new SVG() + // 超链接节点 + let a = new A().to(hyperlink).target('_blank') + a.node.addEventListener('click', e => { + e.stopPropagation() + }) + if (hyperlinkTitle) { + a.attr('title', hyperlinkTitle) + } + // 添加一个透明的层,作为鼠标区域 + a.rect(iconSize, iconSize).fill({ color: 'transparent' }) + // 超链接图标 + let iconNode = SVG(iconsSvg.hyperlink).size(iconSize, iconSize) + this.style.iconNode(iconNode) + a.add(iconNode) + node.add(a) + return { + node, + width: iconSize, + height: iconSize + } + } + + // 创建标签节点 + createTagNode() { + let tagData = this.nodeData.data.tag + if (!tagData || tagData.length <= 0) { + return [] + } + let nodes = [] + tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => { + let tag = new G() + // 标签文本 + let text = new Text().text(item).x(8).cy(10) + this.style.tagText(text, index) + let { width } = text.bbox() + // 标签矩形 + let rect = new Rect().size(width + 16, 20) + this.style.tagRect(rect, index) + tag.add(rect).add(text) + nodes.push({ + node: tag, + width: width + 16, + height: 20 + }) + }) + return nodes + } + + // 创建备注节点 + createNoteNode() { + if (!this.nodeData.data.note) { + return null + } + let iconSize = this.themeConfig.iconSize + let node = new SVG().attr('cursor', 'pointer') + // 透明的层,用来作为鼠标区域 + node.add(new Rect().size(iconSize, iconSize).fill({ color: 'transparent' })) + // 备注图标 + let iconNode = SVG(iconsSvg.note).size(iconSize, iconSize) + this.style.iconNode(iconNode) + node.add(iconNode) + // 备注tooltip + if (!this.mindMap.opt.customNoteContentShow) { + if (!this.noteEl) { + this.noteEl = document.createElement('div') + this.noteEl.style.cssText = ` + position: absolute; + padding: 10px; + border-radius: 5px; + box-shadow: 0 2px 5px rgb(0 0 0 / 10%); + display: none; + background-color: #fff; + ` + document.body.appendChild(this.noteEl) + } + this.noteEl.innerText = this.nodeData.data.note + } + node.on('mouseover', () => { + let { left, top } = node.node.getBoundingClientRect() + if (!this.mindMap.opt.customNoteContentShow) { + this.noteEl.style.left = left + 'px' + this.noteEl.style.top = top + iconSize + 'px' + this.noteEl.style.display = 'block' + } else { + this.mindMap.opt.customNoteContentShow.show( + this.nodeData.data.note, + left, + top + iconSize + ) + } + }) + node.on('mouseout', () => { + if (!this.mindMap.opt.customNoteContentShow) { + this.noteEl.style.display = 'none' + } else { + this.mindMap.opt.customNoteContentShow.hide() + } + }) + return { + node, + width: iconSize, + height: iconSize + } + } + + // 获取节点形状 + getShape() { + // 节点使用功能横线风格的话不支持设置形状,直接使用默认的矩形 + return this.themeConfig.nodeUseLineStyle + ? 'rectangle' + : this.style.getStyle('shape', false, false) + } + + // 定位节点内容 + layout() { + let { width, textContentItemMargin } = this + let { paddingY } = this.getPaddingVale() + paddingY += this.shapePadding.paddingY + // 创建组 + this.group = new G() + // 概要节点添加一个带所属节点id的类名 + if (this.isGeneralization && this.generalizationBelongNode) { + this.group.addClass('generalization_' + this.generalizationBelongNode.uid) + } + this.draw.add(this.group) + this.update(true) + // 节点形状 + const shape = this.getShape() + this.style[shape === 'rectangle' ? 'rect' : 'shape']( + this.shapeInstance.createShape() + ) + // 图片节点 + let imgHeight = 0 + if (this._imgData) { + imgHeight = this._imgData.height + this.group.add(this._imgData.node) + this._imgData.node.cx(width / 2).y(paddingY) + } + // 内容节点 + let textContentNested = new G() + let textContentOffsetX = 0 + // icon + let iconNested = new G() + if (this._iconData && this._iconData.length > 0) { + let iconLeft = 0 + this._iconData.forEach(item => { + item.node + .x(textContentOffsetX + iconLeft) + .y((this._rectInfo.textContentHeight - item.height) / 2) + iconNested.add(item.node) + iconLeft += item.width + textContentItemMargin + }) + textContentNested.add(iconNested) + textContentOffsetX += iconLeft + } + // 文字 + if (this._textData) { + this._textData.node.x(textContentOffsetX).y(0) + textContentNested.add(this._textData.node) + textContentOffsetX += this._textData.width + textContentItemMargin + } + // 超链接 + if (this._hyperlinkData) { + this._hyperlinkData.node + .x(textContentOffsetX) + .y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2) + textContentNested.add(this._hyperlinkData.node) + textContentOffsetX += this._hyperlinkData.width + textContentItemMargin + } + // 标签 + let tagNested = new G() + if (this._tagData && this._tagData.length > 0) { + let tagLeft = 0 + this._tagData.forEach(item => { + item.node + .x(textContentOffsetX + tagLeft) + .y((this._rectInfo.textContentHeight - item.height) / 2) + tagNested.add(item.node) + tagLeft += item.width + textContentItemMargin + }) + textContentNested.add(tagNested) + textContentOffsetX += tagLeft + } + // 备注 + if (this._noteData) { + this._noteData.node + .x(textContentOffsetX) + .y((this._rectInfo.textContentHeight - this._noteData.height) / 2) + textContentNested.add(this._noteData.node) + textContentOffsetX += this._noteData.width + } + // 文字内容整体 + textContentNested.translate( + width / 2 - textContentNested.bbox().width / 2, + imgHeight + + paddingY + + (imgHeight > 0 && this._rectInfo.textContentHeight > 0 + ? this.blockContentMargin + : 0) + ) + this.group.add(textContentNested) + // 单击事件,选中节点 + this.group.on('click', e => { + this.mindMap.emit('node_click', this, e) + this.active(e) + }) + this.group.on('mousedown', e => { + e.stopPropagation() + this.mindMap.emit('node_mousedown', this, e) + }) + this.group.on('mouseup', e => { + e.stopPropagation() + this.mindMap.emit('node_mouseup', this, e) + }) + // 双击事件 + this.group.on('dblclick', e => { + if (this.mindMap.opt.readonly) { + return + } + e.stopPropagation() + this.mindMap.emit('node_dblclick', this, e) + }) + // 右键菜单事件 + this.group.on('contextmenu', e => { + if (this.mindMap.opt.readonly || this.isGeneralization) { + return + } + e.stopPropagation() + e.preventDefault() + if (this.nodeData.data.isActive) { + this.renderer.clearActive() + } + this.active(e) + this.mindMap.emit('node_contextmenu', e, this) + }) + } + + // 激活节点 + active(e) { + if (this.mindMap.opt.readonly) { + return + } + e && e.stopPropagation() + if (this.nodeData.data.isActive) { + return + } + this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) + this.renderer.clearActive() + this.mindMap.execCommand('SET_NODE_ACTIVE', this, true) + this.renderer.addActiveNode(this) + this.mindMap.emit('node_active', this, this.renderer.activeNodeList) + } + + // 渲染节点到画布,会移除旧的,创建新的 + renderNode() { + // 连线 + this.renderLine() + this.removeAllEvent() + this.removeAllNode() + this.createNodeData() + this.layout() + } + + // 更新节点 + update(layout = false) { + if (!this.group) { + return + } + // 需要移除展开收缩按钮 + if (this._expandBtn && this.nodeData.children.length <= 0) { + this.removeExpandBtn() + } else if (!this._expandBtn && this.nodeData.children.length > 0) { + // 需要添加展开收缩按钮 + this.renderExpandBtn() + } else { + this.updateExpandBtnPos() + } + this.renderGeneralization() + let t = this.group.transform() + // 节点使用横线风格,有两种结构需要调整节点的位置 + let nodeUseLineStyleOffset = 0 + if ( + ['logicalStructure', 'mindMap'].includes(this.mindMap.opt.layout) && + !this.isRoot && + !this.isGeneralization && + this.themeConfig.nodeUseLineStyle + ) { + nodeUseLineStyleOffset = this.height / 2 + } + if (!layout) { + this.group + .animate(300) + .translate( + this.left - t.translateX, + this.top - t.translateY - nodeUseLineStyleOffset + ) + } else { + this.group.translate( + this.left - t.translateX, + this.top - t.translateY - nodeUseLineStyleOffset + ) + } + } + + // 递归渲染 + render(callback = () => {}) { + // 节点 + if (this.initRender) { + this.initRender = false + this.renderNode() + } else { + // 连线 + this.renderLine() + this.update() + } + // 子节点 + if ( + this.children && + this.children.length && + this.nodeData.data.expand !== false + ) { + let index = 0 + asyncRun( + this.children.map(item => { + return () => { + item.render(() => { + index++ + if (index >= this.children.length) { + callback() + } + }) + } + }) + ) + } else { + callback() + } + // 手动插入的节点立即获得焦点并且开启编辑模式 + if (this.nodeData.inserting) { + delete this.nodeData.inserting + this.active() + this.mindMap.emit('node_dblclick', this) + } + } + + // 递归删除 + remove() { + this.initRender = true + this.removeAllEvent() + this.removeAllNode() + this.removeLine() + // 子节点 + if (this.children && this.children.length) { + asyncRun( + this.children.map(item => { + return () => { + item.remove() + } + }) + ) + } + } + + // 隐藏节点 + hide() { + this.group.hide() + this.hideGeneralization() + if (this.parent) { + let index = this.parent.children.indexOf(this) + this.parent._lines[index].hide() + } + // 子节点 + if (this.children && this.children.length) { + asyncRun( + this.children.map(item => { + return () => { + item.hide() + } + }) + ) + } + } + + // 显示节点 + show() { + if (!this.group) { + return + } + this.group.show() + this.showGeneralization() + if (this.parent) { + let index = this.parent.children.indexOf(this) + this.parent._lines[index] && this.parent._lines[index].show() + } + // 子节点 + if (this.children && this.children.length) { + asyncRun( + this.children.map(item => { + return () => { + item.show() + } + }) + ) + } + } + + // 连线 + renderLine(deep = false) { + if (this.nodeData.data.expand === false) { + return + } + let childrenLen = this.nodeData.children.length + if (childrenLen > this._lines.length) { + // 创建缺少的线 + new Array(childrenLen - this._lines.length).fill(0).forEach(() => { + this._lines.push(this.draw.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, + (line, node) => { + // 添加样式 + this.styleLine(line, node) + }, + this.style.getStyle('lineStyle', true) + ) + // 子级的连线也需要更新 + if (deep && this.children && this.children.length > 0) { + this.children.forEach(item => { + item.renderLine(deep) + }) + } + } + + // 设置连线样式 + styleLine(line, node) { + let width = + node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true) + let color = + node.getSelfInhertStyle('lineColor') || node.getStyle('lineColor', true) + let dasharray = + node.getSelfInhertStyle('lineDasharray') || + node.getStyle('lineDasharray', true) + this.style.line(line, { + width, + color, + dasharray + }) + } + + // 移除连线 + removeLine() { + this._lines.forEach(line => { + line.remove() + }) + this._lines = [] + } + + // 检查是否存在概要 + checkHasGeneralization() { + return !!this.nodeData.data.generalization + } + + // 创建概要节点 + createGeneralizationNode() { + if (this.isGeneralization || !this.checkHasGeneralization()) { + return + } + if (!this._generalizationLine) { + this._generalizationLine = this.draw.path() + } + if (!this._generalizationNode) { + this._generalizationNode = new Node({ + data: { + data: this.nodeData.data.generalization + }, + uid: this.mindMap.uid++, + renderer: this.renderer, + mindMap: this.mindMap, + draw: this.draw, + isGeneralization: true + }) + this._generalizationNodeWidth = this._generalizationNode.width + this._generalizationNodeHeight = this._generalizationNode.height + this._generalizationNode.generalizationBelongNode = this + if (this.nodeData.data.generalization.isActive) { + this.renderer.addActiveNode(this._generalizationNode) + } + } + } + + // 更新概要节点 + updateGeneralization() { + this.removeGeneralization() + this.createGeneralizationNode() + } + + // 渲染概要节点 + renderGeneralization() { + if (this.isGeneralization) { + return + } + if (!this.checkHasGeneralization()) { + this.removeGeneralization() + this._generalizationNodeWidth = 0 + this._generalizationNodeHeight = 0 + return + } + if (this.nodeData.data.expand === false) { + this.removeGeneralization() + return + } + this.createGeneralizationNode() + this.renderer.layout.renderGeneralization( + this, + this._generalizationLine, + this._generalizationNode + ) + this.style.generalizationLine(this._generalizationLine) + this._generalizationNode.render() + } + + // 删除概要节点 + removeGeneralization() { + if (this._generalizationLine) { + this._generalizationLine.remove() + this._generalizationLine = null + } + if (this._generalizationNode) { + // 删除概要节点时要同步从激活节点里删除 + this.renderer.removeActiveNode(this._generalizationNode) + this._generalizationNode.remove() + this._generalizationNode = null + } + // hack修复当激活一个节点时创建概要,然后立即激活创建的概要节点后会重复创建概要节点并且无法删除的问题 + if (this.generalizationBelongNode) { + this.draw + .find('.generalization_' + this.generalizationBelongNode.uid) + .remove() + } + } + + // 隐藏概要节点 + hideGeneralization() { + if (this._generalizationLine) { + this._generalizationLine.hide() + } + if (this._generalizationNode) { + this._generalizationNode.hide() + } + } + + // 显示概要节点 + showGeneralization() { + if (this._generalizationLine) { + this._generalizationLine.show() + } + if (this._generalizationNode) { + this._generalizationNode.show() + } + } + + // 创建或更新展开收缩按钮内容 + updateExpandBtnNode() { + if (this._expandBtn) { + this._expandBtn.clear() + } + let iconSvg + if (this.nodeData.data.expand === false) { + iconSvg = btnsSvg.open + } else { + iconSvg = btnsSvg.close + } + let node = SVG(iconSvg).size(this.expandBtnSize, this.expandBtnSize) + let fillNode = new Circle().size(this.expandBtnSize) + node.x(0).y(-this.expandBtnSize / 2) + fillNode.x(0).y(-this.expandBtnSize / 2) + this.style.iconBtn(node, fillNode) + if (this._expandBtn) this._expandBtn.add(fillNode).add(node) + } + + // 更新展开收缩按钮位置 + updateExpandBtnPos() { + if (!this._expandBtn) { + return + } + this.renderer.layout.renderExpandBtn(this, this._expandBtn) + } + + // 展开收缩按钮 + renderExpandBtn() { + if ( + !this.nodeData.children || + this.nodeData.children.length <= 0 || + this.isRoot + ) { + return + } + this._expandBtn = new G() + this.updateExpandBtnNode() + this._expandBtn.on('mouseover', e => { + e.stopPropagation() + this._expandBtn.css({ + cursor: 'pointer' + }) + }) + this._expandBtn.on('mouseout', e => { + e.stopPropagation() + this._expandBtn.css({ + cursor: 'auto' + }) + }) + this._expandBtn.on('click', e => { + e.stopPropagation() + // 展开收缩 + this.mindMap.execCommand( + 'SET_NODE_EXPAND', + this, + !this.nodeData.data.expand + ) + this.mindMap.emit('expand_btn_click', this) + }) + this.group.add(this._expandBtn) + this.updateExpandBtnPos() + } + + // 移除展开收缩按钮 + removeExpandBtn() { + if (this._expandBtn) { + this._expandBtn.off(['mouseover', 'mouseout', 'click']) + this._expandBtn.clear() + this._expandBtn.remove() + this._expandBtn = null + } + } + + // 检测当前节点是否是某个节点的祖先节点 + isParent(node) { + if (this === node) { + return false + } + let parent = node.parent + while (parent) { + if (this === parent) { + return true + } + parent = parent.parent + } + return false + } + + // 检测当前节点是否是某个节点的兄弟节点 + isBrother(node) { + if (!this.parent || this === node) { + return false + } + return this.parent.children.find(item => { + return item === node + }) + } + + // 获取padding值 + getPaddingVale() { + return { + paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive), + paddingY: this.getStyle('paddingY', true, this.nodeData.data.isActive) + } + } + + // 获取某个样式 + getStyle(prop, root, isActive) { + let v = this.style.merge(prop, root, isActive) + 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) + ) // 父级 + } + + // 修改某个样式 + setStyle(prop, value, isActive) { + this.mindMap.execCommand('SET_NODE_STYLE', this, prop, value, isActive) + } + + // 获取数据 + getData(key) { + return key ? this.nodeData.data[key] || '' : this.nodeData.data + } + + // 设置数据 + setData(data = {}) { + this.mindMap.execCommand('SET_NODE_DATA', this, data) + } + + // 设置文本 + setText(text) { + this.mindMap.execCommand('SET_NODE_TEXT', this, text) + } + + // 设置图片 + setImage(imgData) { + this.mindMap.execCommand('SET_NODE_IMAGE', this, imgData) + } + + // 设置图标 + setIcon(icons) { + this.mindMap.execCommand('SET_NODE_ICON', this, icons) + } + + // 设置超链接 + setHyperlink(link, title) { + this.mindMap.execCommand('SET_NODE_HYPERLINK', this, link, title) + } + + // 设置备注 + setNote(note) { + this.mindMap.execCommand('SET_NODE_NOTE', this, note) + } + + // 设置标签 + setTag(tag) { + this.mindMap.execCommand('SET_NODE_TAG', this, tag) + } + + // 设置形状 + setShape(shape) { + this.mindMap.execCommand('SET_NODE_SHAPE', this, shape) + } +} + +export default Node diff --git a/simple-mind-map/src/Render.js b/simple-mind-map/src/Render.js index db99336a..821173db 100644 --- a/simple-mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -1,1117 +1,893 @@ -import merge from 'deepmerge' -import LogicalStructure from './layouts/LogicalStructure' -import MindMap from './layouts/MindMap' -import CatalogOrganization from './layouts/CatalogOrganization' -import OrganizationStructure from './layouts/OrganizationStructure' -import TextEdit from './TextEdit' -import { copyNodeTree, simpleDeepClone, walk } from './utils' -import { shapeList } from './Shape' -import { lineStyleProps } from './themes/default' - -// 布局列表 -const layouts = { - // 逻辑结构图 - logicalStructure: LogicalStructure, - // 思维导图 - mindMap: MindMap, - // 目录组织图 - catalogOrganization: CatalogOrganization, - // 组织结构图 - organizationStructure: OrganizationStructure -} - -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 16:25:07 - * @Desc: 渲染 - */ -class Render { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 16:25:32 - * @Desc: 构造函数 - */ - constructor(opt = {}) { - this.opt = opt - this.mindMap = opt.mindMap - this.themeConfig = this.mindMap.themeConfig - this.draw = this.mindMap.draw - // 渲染树,操作过程中修改的都是这里的数据 - this.renderTree = merge({}, this.mindMap.opt.data || {}) - // 是否重新渲染 - this.reRender = false - // 当前激活的节点列表 - this.activeNodeList = [] - // 根节点 - this.root = null - // 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改 - this.textEdit = new TextEdit(this) - // 布局 - this.setLayout() - // 绑定事件 - this.bindEvent() - // 注册命令 - this.registerCommands() - // 注册快捷键 - this.registerShortcutKeys() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 16:20:07 - * @Desc: 设置布局结构 - */ - setLayout() { - this.layout = new ( - layouts[this.mindMap.opt.layout] - ? layouts[this.mindMap.opt.layout] - : layouts.logicalStructure - )(this) - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 10:34:06 - * @Desc: 绑定事件 - */ - bindEvent() { - // 点击事件 - this.mindMap.on('draw_click', () => { - // 清除激活状态 - if (this.activeNodeList.length > 0) { - this.mindMap.execCommand('CLEAR_ACTIVE_NODE') - } - }) - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:19:06 - * @Desc: 注册命令 - */ - registerCommands() { - // 全选 - this.selectAll = this.selectAll.bind(this) - this.mindMap.command.add('SELECT_ALL', this.selectAll) - // 回退 - this.back = this.back.bind(this) - this.mindMap.command.add('BACK', this.back) - // 前进 - this.forward = this.forward.bind(this) - this.mindMap.command.add('FORWARD', this.forward) - // 插入同级节点 - this.insertNode = this.insertNode.bind(this) - this.mindMap.command.add('INSERT_NODE', this.insertNode) - // 插入子节点 - this.insertChildNode = this.insertChildNode.bind(this) - this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode) - // 上移节点 - this.upNode = this.upNode.bind(this) - this.mindMap.command.add('UP_NODE', this.upNode) - // 下移节点 - this.downNode = this.downNode.bind(this) - this.mindMap.command.add('DOWN_NODE', this.downNode) - // 移动节点 - this.insertAfter = this.insertAfter.bind(this) - this.mindMap.command.add('INSERT_AFTER', this.insertAfter) - this.insertBefore = this.insertBefore.bind(this) - this.mindMap.command.add('INSERT_BEFORE', this.insertBefore) - this.moveNodeTo = this.moveNodeTo.bind(this) - this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo) - // 删除节点 - this.removeNode = this.removeNode.bind(this) - this.mindMap.command.add('REMOVE_NODE', this.removeNode) - // 粘贴节点 - this.pasteNode = this.pasteNode.bind(this) - this.mindMap.command.add('PASTE_NODE', this.pasteNode) - // 剪切节点 - this.cutNode = this.cutNode.bind(this) - this.mindMap.command.add('CUT_NODE', this.cutNode) - // 修改节点样式 - this.setNodeStyle = this.setNodeStyle.bind(this) - this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle) - // 切换节点是否激活 - this.setNodeActive = this.setNodeActive.bind(this) - this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive) - // 清除所有激活节点 - this.clearAllActive = this.clearAllActive.bind(this) - this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearAllActive) - // 切换节点是否展开 - this.setNodeExpand = this.setNodeExpand.bind(this) - this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand) - // 展开所有节点 - this.expandAllNode = this.expandAllNode.bind(this) - this.mindMap.command.add('EXPAND_ALL', this.expandAllNode) - // 收起所有节点 - this.unexpandAllNode = this.unexpandAllNode.bind(this) - this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode) - // 展开到指定层级 - this.expandToLevel = this.expandToLevel.bind(this) - this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel) - // 设置节点数据 - this.setNodeData = this.setNodeData.bind(this) - this.mindMap.command.add('SET_NODE_DATA', this.setNodeData) - // 设置节点文本 - this.setNodeText = this.setNodeText.bind(this) - this.mindMap.command.add('SET_NODE_TEXT', this.setNodeText) - // 设置节点图片 - this.setNodeImage = this.setNodeImage.bind(this) - this.mindMap.command.add('SET_NODE_IMAGE', this.setNodeImage) - // 设置节点图标 - this.setNodeIcon = this.setNodeIcon.bind(this) - this.mindMap.command.add('SET_NODE_ICON', this.setNodeIcon) - // 设置节点超链接 - this.setNodeHyperlink = this.setNodeHyperlink.bind(this) - this.mindMap.command.add('SET_NODE_HYPERLINK', this.setNodeHyperlink) - // 设置节点备注 - this.setNodeNote = this.setNodeNote.bind(this) - this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote) - // 设置节点标签 - this.setNodeTag = this.setNodeTag.bind(this) - this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag) - // 添加节点概要 - this.addGeneralization = this.addGeneralization.bind(this) - this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization) - // 删除节点概要 - this.removeGeneralization = this.removeGeneralization.bind(this) - this.mindMap.command.add('REMOVE_GENERALIZATION', this.removeGeneralization) - // 设置节点自定义位置 - this.setNodeCustomPosition = this.setNodeCustomPosition.bind(this) - this.mindMap.command.add( - 'SET_NODE_CUSTOM_POSITION', - this.setNodeCustomPosition - ) - // 一键整理布局 - this.resetLayout = this.resetLayout.bind(this) - this.mindMap.command.add('RESET_LAYOUT', this.resetLayout) - // 设置节点形状 - this.setNodeShape = this.setNodeShape.bind(this) - this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape) - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 16:55:44 - * @Desc: 注册快捷键 - */ - registerShortcutKeys() { - // 插入下级节点 - this.mindMap.keyCommand.addShortcut('Tab', () => { - this.mindMap.execCommand('INSERT_CHILD_NODE') - }) - // 插入同级节点 - this.insertNodeWrap = () => { - if (this.textEdit.showTextEdit) { - return - } - this.mindMap.execCommand('INSERT_NODE') - } - this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) - // 插入概要 - this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization) - // 展开/收起节点 - this.toggleActiveExpand = this.toggleActiveExpand.bind(this) - this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) - // 删除节点 - this.removeNodeWrap = () => { - this.mindMap.execCommand('REMOVE_NODE') - } - this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) - // 节点编辑时某些快捷键会存在冲突,需要暂时去除 - this.mindMap.on('before_show_text_edit', () => { - this.startTextEdit() - }) - this.mindMap.on('hide_text_edit', () => { - this.endTextEdit() - }) - // 全选 - this.mindMap.keyCommand.addShortcut('Control+a', () => { - this.mindMap.execCommand('SELECT_ALL') - }) - // 一键整理布局 - this.mindMap.keyCommand.addShortcut('Control+l', this.resetLayout) - // 上移节点 - this.mindMap.keyCommand.addShortcut('Control+Up', this.upNode) - // 下移节点 - this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode) - // 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-05-09 10:43:52 - * @Desc: 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突 - */ - startTextEdit() { - this.mindMap.keyCommand.save() - // this.mindMap.keyCommand.removeShortcut('Del|Backspace') - // this.mindMap.keyCommand.removeShortcut('/') - // this.mindMap.keyCommand.removeShortcut('Enter', this.insertNodeWrap) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-05-09 10:45:11 - * @Desc: 结束文字编辑,会恢复回车键和删除键相关快捷键 - */ - endTextEdit() { - this.mindMap.keyCommand.restore() - // this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) - // this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) - // this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 16:27:55 - * @Desc: 渲染 - */ - render() { - if (this.reRender) { - this.clearActive() - } - this.layout.doLayout(root => { - this.root = root - this.root.render(() => { - this.mindMap.emit('node_tree_render_end') - }) - }) - this.mindMap.emit('node_active', null, this.activeNodeList) - } - - /** - * @Author: 王林 - * @Date: 2021-04-12 22:45:01 - * @Desc: 清除当前激活的节点 - */ - clearActive() { - this.activeNodeList.forEach(item => { - this.setNodeActive(item, false) - }) - this.activeNodeList = [] - } - - /** - * @Author: 王林 - * @Date: 2021-08-03 23:14:34 - * @Desc: 清除当前所有激活节点,并会触发事件 - */ - clearAllActive() { - if (this.activeNodeList.length <= 0) { - return - } - this.clearActive() - this.mindMap.emit('node_active', null, []) - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 10:54:00 - * @Desc: 添加节点到激活列表里 - */ - addActiveNode(node) { - let index = this.findActiveNodeIndex(node) - if (index === -1) { - this.activeNodeList.push(node) - } - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 10:04:04 - * @Desc: 在激活列表里移除某个节点 - */ - removeActiveNode(node) { - let index = this.findActiveNodeIndex(node) - if (index === -1) { - return - } - this.activeNodeList.splice(index, 1) - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 10:55:23 - * @Desc: 检索某个节点在激活列表里的索引 - */ - findActiveNodeIndex(node) { - return this.activeNodeList.findIndex(item => { - return item === node - }) - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:46:08 - * @Desc: 获取节点在同级里的索引位置 - */ - getNodeIndex(node) { - return node.parent - ? node.parent.children.findIndex(item => { - return item === node - }) - : 0 - } - - /** - * @Author: 王林 - * @Date: 2021-08-04 23:54:52 - * @Desc: 全选 - */ - selectAll() { - walk( - this.root, - null, - node => { - if (!node.nodeData.data.isActive) { - node.nodeData.data.isActive = true - this.addActiveNode(node) - setTimeout(() => { - node.renderNode() - }, 0) - } - }, - null, - true, - 0, - 0 - ) - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 22:34:12 - * @Desc: 回退 - */ - back(step) { - this.clearAllActive() - let data = this.mindMap.command.back(step) - if (data) { - this.renderTree = data - this.mindMap.reRender() - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-12 10:44:51 - * @Desc: 前进 - */ - forward(step) { - this.clearAllActive() - let data = this.mindMap.command.forward(step) - if (data) { - this.renderTree = data - this.mindMap.reRender() - } - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:19:54 - * @Desc: 插入同级节点,多个节点只会操作第一个节点 - */ - insertNode() { - if (this.activeNodeList.length <= 0) { - return - } - let first = this.activeNodeList[0] - if (first.isRoot) { - this.insertChildNode() - } else { - let text = first.layerIndex === 1 ? '二级节点' : '分支主题' - if (first.layerIndex === 1) { - first.parent.initRender = true - } - let index = this.getNodeIndex(first) - first.parent.nodeData.children.splice(index + 1, 0, { - inserting: true, - data: { - text: text, - expand: true - }, - children: [] - }) - this.mindMap.render() - } - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:31:02 - * @Desc: 插入子节点 - */ - insertChildNode() { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(node => { - if (!node.nodeData.children) { - node.nodeData.children = [] - } - let text = node.isRoot ? '二级节点' : '分支主题' - node.nodeData.children.push({ - inserting: true, - data: { - text: text, - expand: true - }, - children: [] - }) - // 插入子节点时自动展开子节点 - node.nodeData.data.expand = true - if (node.isRoot) { - node.initRender = true - // this.mindMap.batchExecution.push('renderNode' + index, () => { - // node.renderNode() - // }) - } - }) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-07-14 23:34:14 - * @Desc: 上移节点,多个节点只会操作第一个节点 - */ - upNode() { - if (this.activeNodeList.length <= 0) { - return - } - let node = this.activeNodeList[0] - if (node.isRoot) { - return - } - let parent = node.parent - let childList = parent.children - let index = childList.findIndex(item => { - return item === node - }) - if (index === -1 || index === 0) { - return - } - let insertIndex = index - 1 - // 节点实例 - childList.splice(index, 1) - childList.splice(insertIndex, 0, node) - // 节点数据 - parent.nodeData.children.splice(index, 1) - parent.nodeData.children.splice(insertIndex, 0, node.nodeData) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-07-14 23:34:18 - * @Desc: 下移节点,多个节点只会操作第一个节点 - */ - downNode() { - if (this.activeNodeList.length <= 0) { - return - } - let node = this.activeNodeList[0] - if (node.isRoot) { - return - } - let parent = node.parent - let childList = parent.children - let index = childList.findIndex(item => { - return item === node - }) - if (index === -1 || index === childList.length - 1) { - return - } - let insertIndex = index + 1 - // 节点实例 - childList.splice(index, 1) - childList.splice(insertIndex, 0, node) - // 节点数据 - parent.nodeData.children.splice(index, 1) - parent.nodeData.children.splice(insertIndex, 0, node.nodeData) - this.mindMap.render() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-25 10:51:34 - * @Desc: 将节点移动到另一个节点的前面 - */ - insertBefore(node, exist) { - if (node.isRoot) { - return - } - // 移动节点 - let nodeParent = node.parent - let nodeBorthers = nodeParent.children - let nodeIndex = nodeBorthers.findIndex(item => { - return item === node - }) - if (nodeIndex === -1) { - return - } - nodeBorthers.splice(nodeIndex, 1) - nodeParent.nodeData.children.splice(nodeIndex, 1) - - // 目标节点 - let existParent = exist.parent - let existBorthers = existParent.children - let existIndex = existBorthers.findIndex(item => { - return item === exist - }) - if (existIndex === -1) { - return - } - existBorthers.splice(existIndex, 0, node) - existParent.nodeData.children.splice(existIndex, 0, node.nodeData) - this.mindMap.render() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-25 10:51:34 - * @Desc: 将节点移动到另一个节点的后面 - */ - insertAfter(node, exist) { - if (node.isRoot) { - return - } - // 移动节点 - let nodeParent = node.parent - let nodeBorthers = nodeParent.children - let nodeIndex = nodeBorthers.findIndex(item => { - return item === node - }) - if (nodeIndex === -1) { - return - } - nodeBorthers.splice(nodeIndex, 1) - nodeParent.nodeData.children.splice(nodeIndex, 1) - - // 目标节点 - let existParent = exist.parent - let existBorthers = existParent.children - let existIndex = existBorthers.findIndex(item => { - return item === exist - }) - if (existIndex === -1) { - return - } - existIndex++ - existBorthers.splice(existIndex, 0, node) - existParent.nodeData.children.splice(existIndex, 0, node.nodeData) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:40:39 - * @Desc: 移除节点 - */ - removeNode() { - if (this.activeNodeList.length <= 0) { - return - } - for (let i = 0; i < this.activeNodeList.length; i++) { - let node = this.activeNodeList[i] - if (node.isGeneralization) { - // 删除概要节点 - this.setNodeData(node.generalizationBelongNode, { - generalization: null - }) - node.generalizationBelongNode.update() - this.removeActiveNode(node) - i-- - } else if (node.isRoot) { - node.children.forEach(child => { - child.remove() - }) - node.children = [] - node.nodeData.children = [] - break - } else { - this.removeActiveNode(node) - this.removeOneNode(node) - i-- - } - } - this.mindMap.emit('node_active', null, []) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-07-15 22:46:27 - * @Desc: 移除某个指定节点 - */ - removeOneNode(node) { - let index = this.getNodeIndex(node) - node.remove() - node.parent.children.splice(index, 1) - node.parent.nodeData.children.splice(index, 1) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-15 09:53:23 - * @Desc: 复制节点,多个节点只会操作第一个节点 - */ - copyNode() { - if (this.activeNodeList.length <= 0) { - return - } - return copyNodeTree({}, this.activeNodeList[0], true) - } - - /** - * @Author: 王林 - * @Date: 2021-07-15 22:36:45 - * @Desc: 剪切节点,多个节点只会操作第一个节点 - */ - cutNode(callback) { - if (this.activeNodeList.length <= 0) { - return - } - let node = this.activeNodeList[0] - if (node.isRoot) { - return null - } - let copyData = copyNodeTree({}, node, true) - this.removeActiveNode(node) - this.removeOneNode(node) - this.mindMap.emit('node_active', null, this.activeNodeList) - this.mindMap.render() - if (callback && typeof callback === 'function') { - callback(copyData) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-24 16:54:01 - * @Desc: 移动一个节点作为另一个节点的子节点 - */ - moveNodeTo(node, toNode) { - if (node.isRoot) { - return - } - let copyData = copyNodeTree({}, node) - this.removeActiveNode(node) - this.removeOneNode(node) - this.mindMap.emit('node_active', null, this.activeNodeList) - toNode.nodeData.children.push(copyData) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-07-15 20:09:39 - * @Desc: 粘贴节点到节点 - */ - pasteNode(data) { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(item => { - item.nodeData.children.push(simpleDeepClone(data)) - }) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-07-08 21:54:30 - * @Desc: 设置节点样式 - */ - setNodeStyle(node, prop, value, isActive) { - let data = {} - if (isActive) { - data = { - activeStyle: { - ...(node.nodeData.data.activeStyle || {}), - [prop]: value - } - } - } else { - data = { - [prop]: value - } - } - this.setNodeDataRender(node, data) - // 更新了连线的样式 - if (lineStyleProps.includes(prop)) { - ;(node.parent || node).renderLine(true) - } - } - - /** - * @Author: 王林 - * @Date: 2021-07-08 22:13:03 - * @Desc: 设置节点是否激活 - */ - setNodeActive(node, active) { - this.setNodeData(node, { - isActive: active - }) - node.renderNode() - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 16:52:41 - * @Desc: 设置节点是否展开 - */ - setNodeExpand(node, expand) { - this.setNodeData(node, { - expand - }) - if (expand) { - // 展开 - node.children.forEach(item => { - item.render() - }) - node.renderLine() - node.updateExpandBtnNode() - } else { - // 收缩 - node.children.forEach(item => { - item.remove() - }) - node.removeLine() - node.updateExpandBtnNode() - } - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-07-15 23:23:37 - * @Desc: 展开所有 - */ - expandAllNode() { - walk( - this.renderTree, - null, - node => { - if (!node.data.expand) { - node.data.expand = true - } - }, - null, - true, - 0, - 0 - ) - this.mindMap.reRender() - } - - /** - * @Author: 王林 - * @Date: 2021-07-15 23:27:14 - * @Desc: 收起所有 - */ - unexpandAllNode() { - walk( - this.renderTree, - null, - (node, parent, isRoot) => { - node._node = null - if (!isRoot) { - node.data.expand = false - } - }, - null, - true, - 0, - 0 - ) - this.mindMap.reRender() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-23 16:31:27 - * @Desc: 展开到指定层级 - */ - expandToLevel(level) { - walk( - this.renderTree, - null, - (node, parent, isRoot, layerIndex) => { - node._node = null - node.data.expand = layerIndex < level - }, - null, - true, - 0, - 0 - ) - this.mindMap.reRender() - } - - /** - * @Author: 王林 - * @Date: 2022-08-14 09:18:40 - * @Desc: 切换激活节点的展开状态 - */ - toggleActiveExpand() { - this.activeNodeList.forEach(node => { - if (node.nodeData.children.length <= 0) { - return - } - this.toggleNodeExpand(node) - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-11 17:15:33 - * @Desc: 切换节点展开状态 - */ - toggleNodeExpand(node) { - this.mindMap.execCommand( - 'SET_NODE_EXPAND', - node, - !node.nodeData.data.expand - ) - } - - /** - * @Author: 王林 - * @Date: 2021-07-09 22:04:19 - * @Desc: 设置节点文本 - */ - setNodeText(node, text) { - this.setNodeDataRender(node, { - text - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:37:40 - * @Desc: 设置节点图片 - */ - setNodeImage(node, { url, title, width, height }) { - this.setNodeDataRender(node, { - image: url, - imageTitle: title || '', - imageSize: { - width, - height - } - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:44:06 - * @Desc: 设置节点图标 - */ - setNodeIcon(node, icons) { - this.setNodeDataRender(node, { - icon: icons - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:49:33 - * @Desc: 设置节点超链接 - */ - setNodeHyperlink(node, link, title = '') { - this.setNodeDataRender(node, { - hyperlink: link, - hyperlinkTitle: title - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:52:59 - * @Desc: 设置节点备注 - */ - setNodeNote(node, note) { - this.setNodeDataRender(node, { - note - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:54:53 - * @Desc: 设置节点标签 - */ - setNodeTag(node, tag) { - this.setNodeDataRender(node, { - tag - }) - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 20:52:42 - * @Desc: 添加节点概要 - */ - addGeneralization(data) { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(node => { - if (node.nodeData.data.generalization || node.isRoot) { - return - } - this.setNodeData(node, { - generalization: data || { - text: '概要' - } - }) - node.update() - }) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 21:16:33 - * @Desc: 删除节点概要 - */ - removeGeneralization() { - if (this.activeNodeList.length <= 0) { - return - } - this.activeNodeList.forEach(node => { - if (!node.nodeData.data.generalization) { - return - } - this.setNodeData(node, { - generalization: null - }) - node.update() - }) - this.mindMap.render() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-02 19:04:24 - * @Desc: 设置节点自定义位置 - */ - setNodeCustomPosition(node, left = undefined, top = undefined) { - let nodeList = [node] || this.activeNodeList - nodeList.forEach(item => { - this.setNodeData(item, { - customLeft: left, - customTop: top - }) - }) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-02 20:02:50 - * @Desc: 一键整理布局,即去除自定义位置 - */ - resetLayout() { - walk( - this.root, - null, - node => { - node.customLeft = undefined - node.customTop = undefined - this.setNodeData(node, { - customLeft: undefined, - customTop: undefined - }) - this.mindMap.render() - }, - null, - true, - 0, - 0 - ) - } - - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 21:44:01 - * @Desc: 设置节点形状 - */ - setNodeShape(node, shape) { - if (!shape || !shapeList.includes(shape)) { - return - } - let nodeList = [node] || this.activeNodeList - nodeList.forEach(item => { - this.setNodeStyle(item, 'shape', shape) - }) - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 14:19:48 - * @Desc: 更新节点数据 - */ - setNodeData(node, data) { - Object.keys(data).forEach(key => { - node.nodeData.data[key] = data[key] - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-10 08:45:48 - * @Desc: 设置节点数据,并判断是否渲染 - */ - setNodeDataRender(node, data) { - this.setNodeData(node, data) - let changed = node.getSize() - node.renderNode() - if (changed) { - if (node.isGeneralization) { - // 概要节点 - node.generalizationBelongNode.updateGeneralization() - } - this.mindMap.render() - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 11:46:57 - * @Desc: 移动节点到画布中心 - */ - moveNodeToCenter(node) { - let halfWidth = this.mindMap.width / 2 - let halfHeight = this.mindMap.height / 2 - let { left, top, width, height } = node - let nodeCenterX = left + width / 2 - let nodeCenterY = top + height / 2 - let { state } = this.mindMap.view.getTransformData() - let targetX = halfWidth - state.x - let targetY = halfHeight - state.y - let offsetX = targetX - nodeCenterX - let offsetY = targetY - nodeCenterY - this.mindMap.view.translateX(offsetX) - this.mindMap.view.translateY(offsetY) - this.mindMap.view.setScale(1) - } -} - -export default Render +import merge from 'deepmerge' +import LogicalStructure from './layouts/LogicalStructure' +import MindMap from './layouts/MindMap' +import CatalogOrganization from './layouts/CatalogOrganization' +import OrganizationStructure from './layouts/OrganizationStructure' +import TextEdit from './TextEdit' +import { copyNodeTree, simpleDeepClone, walk } from './utils' +import { shapeList } from './Shape' +import { lineStyleProps } from './themes/default' + +// 布局列表 +const layouts = { + // 逻辑结构图 + logicalStructure: LogicalStructure, + // 思维导图 + mindMap: MindMap, + // 目录组织图 + catalogOrganization: CatalogOrganization, + // 组织结构图 + organizationStructure: OrganizationStructure +} + +// 渲染 +class Render { + // 构造函数 + constructor(opt = {}) { + this.opt = opt + this.mindMap = opt.mindMap + this.themeConfig = this.mindMap.themeConfig + this.draw = this.mindMap.draw + // 渲染树,操作过程中修改的都是这里的数据 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 是否重新渲染 + this.reRender = false + // 当前激活的节点列表 + this.activeNodeList = [] + // 根节点 + this.root = null + // 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改 + this.textEdit = new TextEdit(this) + // 布局 + this.setLayout() + // 绑定事件 + this.bindEvent() + // 注册命令 + this.registerCommands() + // 注册快捷键 + this.registerShortcutKeys() + } + + // 设置布局结构 + setLayout() { + this.layout = new ( + layouts[this.mindMap.opt.layout] + ? layouts[this.mindMap.opt.layout] + : layouts.logicalStructure + )(this) + } + + // 绑定事件 + bindEvent() { + // 点击事件 + this.mindMap.on('draw_click', () => { + // 清除激活状态 + if (this.activeNodeList.length > 0) { + this.mindMap.execCommand('CLEAR_ACTIVE_NODE') + } + }) + } + + // 注册命令 + registerCommands() { + // 全选 + this.selectAll = this.selectAll.bind(this) + this.mindMap.command.add('SELECT_ALL', this.selectAll) + // 回退 + this.back = this.back.bind(this) + this.mindMap.command.add('BACK', this.back) + // 前进 + this.forward = this.forward.bind(this) + this.mindMap.command.add('FORWARD', this.forward) + // 插入同级节点 + this.insertNode = this.insertNode.bind(this) + this.mindMap.command.add('INSERT_NODE', this.insertNode) + // 插入子节点 + this.insertChildNode = this.insertChildNode.bind(this) + this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode) + // 上移节点 + this.upNode = this.upNode.bind(this) + this.mindMap.command.add('UP_NODE', this.upNode) + // 下移节点 + this.downNode = this.downNode.bind(this) + this.mindMap.command.add('DOWN_NODE', this.downNode) + // 移动节点 + this.insertAfter = this.insertAfter.bind(this) + this.mindMap.command.add('INSERT_AFTER', this.insertAfter) + this.insertBefore = this.insertBefore.bind(this) + this.mindMap.command.add('INSERT_BEFORE', this.insertBefore) + this.moveNodeTo = this.moveNodeTo.bind(this) + this.mindMap.command.add('MOVE_NODE_TO', this.moveNodeTo) + // 删除节点 + this.removeNode = this.removeNode.bind(this) + this.mindMap.command.add('REMOVE_NODE', this.removeNode) + // 粘贴节点 + this.pasteNode = this.pasteNode.bind(this) + this.mindMap.command.add('PASTE_NODE', this.pasteNode) + // 剪切节点 + this.cutNode = this.cutNode.bind(this) + this.mindMap.command.add('CUT_NODE', this.cutNode) + // 修改节点样式 + this.setNodeStyle = this.setNodeStyle.bind(this) + this.mindMap.command.add('SET_NODE_STYLE', this.setNodeStyle) + // 切换节点是否激活 + this.setNodeActive = this.setNodeActive.bind(this) + this.mindMap.command.add('SET_NODE_ACTIVE', this.setNodeActive) + // 清除所有激活节点 + this.clearAllActive = this.clearAllActive.bind(this) + this.mindMap.command.add('CLEAR_ACTIVE_NODE', this.clearAllActive) + // 切换节点是否展开 + this.setNodeExpand = this.setNodeExpand.bind(this) + this.mindMap.command.add('SET_NODE_EXPAND', this.setNodeExpand) + // 展开所有节点 + this.expandAllNode = this.expandAllNode.bind(this) + this.mindMap.command.add('EXPAND_ALL', this.expandAllNode) + // 收起所有节点 + this.unexpandAllNode = this.unexpandAllNode.bind(this) + this.mindMap.command.add('UNEXPAND_ALL', this.unexpandAllNode) + // 展开到指定层级 + this.expandToLevel = this.expandToLevel.bind(this) + this.mindMap.command.add('UNEXPAND_TO_LEVEL', this.expandToLevel) + // 设置节点数据 + this.setNodeData = this.setNodeData.bind(this) + this.mindMap.command.add('SET_NODE_DATA', this.setNodeData) + // 设置节点文本 + this.setNodeText = this.setNodeText.bind(this) + this.mindMap.command.add('SET_NODE_TEXT', this.setNodeText) + // 设置节点图片 + this.setNodeImage = this.setNodeImage.bind(this) + this.mindMap.command.add('SET_NODE_IMAGE', this.setNodeImage) + // 设置节点图标 + this.setNodeIcon = this.setNodeIcon.bind(this) + this.mindMap.command.add('SET_NODE_ICON', this.setNodeIcon) + // 设置节点超链接 + this.setNodeHyperlink = this.setNodeHyperlink.bind(this) + this.mindMap.command.add('SET_NODE_HYPERLINK', this.setNodeHyperlink) + // 设置节点备注 + this.setNodeNote = this.setNodeNote.bind(this) + this.mindMap.command.add('SET_NODE_NOTE', this.setNodeNote) + // 设置节点标签 + this.setNodeTag = this.setNodeTag.bind(this) + this.mindMap.command.add('SET_NODE_TAG', this.setNodeTag) + // 添加节点概要 + this.addGeneralization = this.addGeneralization.bind(this) + this.mindMap.command.add('ADD_GENERALIZATION', this.addGeneralization) + // 删除节点概要 + this.removeGeneralization = this.removeGeneralization.bind(this) + this.mindMap.command.add('REMOVE_GENERALIZATION', this.removeGeneralization) + // 设置节点自定义位置 + this.setNodeCustomPosition = this.setNodeCustomPosition.bind(this) + this.mindMap.command.add( + 'SET_NODE_CUSTOM_POSITION', + this.setNodeCustomPosition + ) + // 一键整理布局 + this.resetLayout = this.resetLayout.bind(this) + this.mindMap.command.add('RESET_LAYOUT', this.resetLayout) + // 设置节点形状 + this.setNodeShape = this.setNodeShape.bind(this) + this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape) + } + + // 注册快捷键 + registerShortcutKeys() { + // 插入下级节点 + this.mindMap.keyCommand.addShortcut('Tab', () => { + this.mindMap.execCommand('INSERT_CHILD_NODE') + }) + // 插入同级节点 + this.insertNodeWrap = () => { + if (this.textEdit.showTextEdit) { + return + } + this.mindMap.execCommand('INSERT_NODE') + } + this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) + // 插入概要 + this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization) + // 展开/收起节点 + this.toggleActiveExpand = this.toggleActiveExpand.bind(this) + this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) + // 删除节点 + this.removeNodeWrap = () => { + this.mindMap.execCommand('REMOVE_NODE') + } + this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) + // 节点编辑时某些快捷键会存在冲突,需要暂时去除 + this.mindMap.on('before_show_text_edit', () => { + this.startTextEdit() + }) + this.mindMap.on('hide_text_edit', () => { + this.endTextEdit() + }) + // 全选 + this.mindMap.keyCommand.addShortcut('Control+a', () => { + this.mindMap.execCommand('SELECT_ALL') + }) + // 一键整理布局 + this.mindMap.keyCommand.addShortcut('Control+l', this.resetLayout) + // 上移节点 + this.mindMap.keyCommand.addShortcut('Control+Up', this.upNode) + // 下移节点 + this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode) + // 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo + } + + // 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突 + startTextEdit() { + this.mindMap.keyCommand.save() + // this.mindMap.keyCommand.removeShortcut('Del|Backspace') + // this.mindMap.keyCommand.removeShortcut('/') + // this.mindMap.keyCommand.removeShortcut('Enter', this.insertNodeWrap) + } + + // 结束文字编辑,会恢复回车键和删除键相关快捷键 + endTextEdit() { + this.mindMap.keyCommand.restore() + // this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeNodeWrap) + // this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand) + // this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap) + } + + // 渲染 + render() { + if (this.reRender) { + this.clearActive() + } + this.layout.doLayout(root => { + this.root = root + this.root.render(() => { + this.mindMap.emit('node_tree_render_end') + }) + }) + this.mindMap.emit('node_active', null, this.activeNodeList) + } + + // 清除当前激活的节点 + clearActive() { + this.activeNodeList.forEach(item => { + this.setNodeActive(item, false) + }) + this.activeNodeList = [] + } + + // 清除当前所有激活节点,并会触发事件 + clearAllActive() { + if (this.activeNodeList.length <= 0) { + return + } + this.clearActive() + this.mindMap.emit('node_active', null, []) + } + + // 添加节点到激活列表里 + addActiveNode(node) { + let index = this.findActiveNodeIndex(node) + if (index === -1) { + this.activeNodeList.push(node) + } + } + + // 在激活列表里移除某个节点 + removeActiveNode(node) { + let index = this.findActiveNodeIndex(node) + if (index === -1) { + return + } + this.activeNodeList.splice(index, 1) + } + + // 检索某个节点在激活列表里的索引 + findActiveNodeIndex(node) { + return this.activeNodeList.findIndex(item => { + return item === node + }) + } + + // 获取节点在同级里的索引位置 + getNodeIndex(node) { + return node.parent + ? node.parent.children.findIndex(item => { + return item === node + }) + : 0 + } + + // 全选 + selectAll() { + walk( + this.root, + null, + node => { + if (!node.nodeData.data.isActive) { + node.nodeData.data.isActive = true + this.addActiveNode(node) + setTimeout(() => { + node.renderNode() + }, 0) + } + }, + null, + true, + 0, + 0 + ) + } + + // 回退 + back(step) { + this.clearAllActive() + let data = this.mindMap.command.back(step) + if (data) { + this.renderTree = data + this.mindMap.reRender() + } + } + + // 前进 + forward(step) { + this.clearAllActive() + let data = this.mindMap.command.forward(step) + if (data) { + this.renderTree = data + this.mindMap.reRender() + } + } + + // 插入同级节点,多个节点只会操作第一个节点 + insertNode() { + if (this.activeNodeList.length <= 0) { + return + } + let first = this.activeNodeList[0] + if (first.isRoot) { + this.insertChildNode() + } else { + let text = first.layerIndex === 1 ? '二级节点' : '分支主题' + if (first.layerIndex === 1) { + first.parent.initRender = true + } + let index = this.getNodeIndex(first) + first.parent.nodeData.children.splice(index + 1, 0, { + inserting: true, + data: { + text: text, + expand: true + }, + children: [] + }) + this.mindMap.render() + } + } + + // 插入子节点 + insertChildNode() { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(node => { + if (!node.nodeData.children) { + node.nodeData.children = [] + } + let text = node.isRoot ? '二级节点' : '分支主题' + node.nodeData.children.push({ + inserting: true, + data: { + text: text, + expand: true + }, + children: [] + }) + // 插入子节点时自动展开子节点 + node.nodeData.data.expand = true + if (node.isRoot) { + node.initRender = true + // this.mindMap.batchExecution.push('renderNode' + index, () => { + // node.renderNode() + // }) + } + }) + this.mindMap.render() + } + + // 上移节点,多个节点只会操作第一个节点 + upNode() { + if (this.activeNodeList.length <= 0) { + return + } + let node = this.activeNodeList[0] + if (node.isRoot) { + return + } + let parent = node.parent + let childList = parent.children + let index = childList.findIndex(item => { + return item === node + }) + if (index === -1 || index === 0) { + return + } + let insertIndex = index - 1 + // 节点实例 + childList.splice(index, 1) + childList.splice(insertIndex, 0, node) + // 节点数据 + parent.nodeData.children.splice(index, 1) + parent.nodeData.children.splice(insertIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 下移节点,多个节点只会操作第一个节点 + downNode() { + if (this.activeNodeList.length <= 0) { + return + } + let node = this.activeNodeList[0] + if (node.isRoot) { + return + } + let parent = node.parent + let childList = parent.children + let index = childList.findIndex(item => { + return item === node + }) + if (index === -1 || index === childList.length - 1) { + return + } + let insertIndex = index + 1 + // 节点实例 + childList.splice(index, 1) + childList.splice(insertIndex, 0, node) + // 节点数据 + parent.nodeData.children.splice(index, 1) + parent.nodeData.children.splice(insertIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 将节点移动到另一个节点的前面 + insertBefore(node, exist) { + if (node.isRoot) { + return + } + // 移动节点 + let nodeParent = node.parent + let nodeBorthers = nodeParent.children + let nodeIndex = nodeBorthers.findIndex(item => { + return item === node + }) + if (nodeIndex === -1) { + return + } + nodeBorthers.splice(nodeIndex, 1) + nodeParent.nodeData.children.splice(nodeIndex, 1) + + // 目标节点 + let existParent = exist.parent + let existBorthers = existParent.children + let existIndex = existBorthers.findIndex(item => { + return item === exist + }) + if (existIndex === -1) { + return + } + existBorthers.splice(existIndex, 0, node) + existParent.nodeData.children.splice(existIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 将节点移动到另一个节点的后面 + insertAfter(node, exist) { + if (node.isRoot) { + return + } + // 移动节点 + let nodeParent = node.parent + let nodeBorthers = nodeParent.children + let nodeIndex = nodeBorthers.findIndex(item => { + return item === node + }) + if (nodeIndex === -1) { + return + } + nodeBorthers.splice(nodeIndex, 1) + nodeParent.nodeData.children.splice(nodeIndex, 1) + + // 目标节点 + let existParent = exist.parent + let existBorthers = existParent.children + let existIndex = existBorthers.findIndex(item => { + return item === exist + }) + if (existIndex === -1) { + return + } + existIndex++ + existBorthers.splice(existIndex, 0, node) + existParent.nodeData.children.splice(existIndex, 0, node.nodeData) + this.mindMap.render() + } + + // 移除节点 + removeNode() { + if (this.activeNodeList.length <= 0) { + return + } + for (let i = 0; i < this.activeNodeList.length; i++) { + let node = this.activeNodeList[i] + if (node.isGeneralization) { + // 删除概要节点 + this.setNodeData(node.generalizationBelongNode, { + generalization: null + }) + node.generalizationBelongNode.update() + this.removeActiveNode(node) + i-- + } else if (node.isRoot) { + node.children.forEach(child => { + child.remove() + }) + node.children = [] + node.nodeData.children = [] + break + } else { + this.removeActiveNode(node) + this.removeOneNode(node) + i-- + } + } + this.mindMap.emit('node_active', null, []) + this.mindMap.render() + } + + // 移除某个指定节点 + removeOneNode(node) { + let index = this.getNodeIndex(node) + node.remove() + node.parent.children.splice(index, 1) + node.parent.nodeData.children.splice(index, 1) + } + + // 复制节点,多个节点只会操作第一个节点 + copyNode() { + if (this.activeNodeList.length <= 0) { + return + } + return copyNodeTree({}, this.activeNodeList[0], true) + } + + // 剪切节点,多个节点只会操作第一个节点 + cutNode(callback) { + if (this.activeNodeList.length <= 0) { + return + } + let node = this.activeNodeList[0] + if (node.isRoot) { + return null + } + let copyData = copyNodeTree({}, node, true) + this.removeActiveNode(node) + this.removeOneNode(node) + this.mindMap.emit('node_active', null, this.activeNodeList) + this.mindMap.render() + if (callback && typeof callback === 'function') { + callback(copyData) + } + } + + // 移动一个节点作为另一个节点的子节点 + moveNodeTo(node, toNode) { + if (node.isRoot) { + return + } + let copyData = copyNodeTree({}, node) + this.removeActiveNode(node) + this.removeOneNode(node) + this.mindMap.emit('node_active', null, this.activeNodeList) + toNode.nodeData.children.push(copyData) + this.mindMap.render() + } + + // 粘贴节点到节点 + pasteNode(data) { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(item => { + item.nodeData.children.push(simpleDeepClone(data)) + }) + this.mindMap.render() + } + + // 设置节点样式 + setNodeStyle(node, prop, value, isActive) { + let data = {} + if (isActive) { + data = { + activeStyle: { + ...(node.nodeData.data.activeStyle || {}), + [prop]: value + } + } + } else { + data = { + [prop]: value + } + } + this.setNodeDataRender(node, data) + // 更新了连线的样式 + if (lineStyleProps.includes(prop)) { + ;(node.parent || node).renderLine(true) + } + } + + // 设置节点是否激活 + setNodeActive(node, active) { + this.setNodeData(node, { + isActive: active + }) + node.renderNode() + } + + // 设置节点是否展开 + setNodeExpand(node, expand) { + this.setNodeData(node, { + expand + }) + if (expand) { + // 展开 + node.children.forEach(item => { + item.render() + }) + node.renderLine() + node.updateExpandBtnNode() + } else { + // 收缩 + node.children.forEach(item => { + item.remove() + }) + node.removeLine() + node.updateExpandBtnNode() + } + this.mindMap.render() + } + + // 展开所有 + expandAllNode() { + walk( + this.renderTree, + null, + node => { + if (!node.data.expand) { + node.data.expand = true + } + }, + null, + true, + 0, + 0 + ) + this.mindMap.reRender() + } + + // 收起所有 + unexpandAllNode() { + walk( + this.renderTree, + null, + (node, parent, isRoot) => { + node._node = null + if (!isRoot) { + node.data.expand = false + } + }, + null, + true, + 0, + 0 + ) + this.mindMap.reRender() + } + + // 展开到指定层级 + expandToLevel(level) { + walk( + this.renderTree, + null, + (node, parent, isRoot, layerIndex) => { + node._node = null + node.data.expand = layerIndex < level + }, + null, + true, + 0, + 0 + ) + this.mindMap.reRender() + } + + // 切换激活节点的展开状态 + toggleActiveExpand() { + this.activeNodeList.forEach(node => { + if (node.nodeData.children.length <= 0) { + return + } + this.toggleNodeExpand(node) + }) + } + + // 切换节点展开状态 + toggleNodeExpand(node) { + this.mindMap.execCommand( + 'SET_NODE_EXPAND', + node, + !node.nodeData.data.expand + ) + } + + // 设置节点文本 + setNodeText(node, text) { + this.setNodeDataRender(node, { + text + }) + } + + // 设置节点图片 + setNodeImage(node, { url, title, width, height }) { + this.setNodeDataRender(node, { + image: url, + imageTitle: title || '', + imageSize: { + width, + height + } + }) + } + + // 设置节点图标 + setNodeIcon(node, icons) { + this.setNodeDataRender(node, { + icon: icons + }) + } + + // 设置节点超链接 + setNodeHyperlink(node, link, title = '') { + this.setNodeDataRender(node, { + hyperlink: link, + hyperlinkTitle: title + }) + } + + // 设置节点备注 + setNodeNote(node, note) { + this.setNodeDataRender(node, { + note + }) + } + + // 设置节点标签 + setNodeTag(node, tag) { + this.setNodeDataRender(node, { + tag + }) + } + + // 添加节点概要 + addGeneralization(data) { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(node => { + if (node.nodeData.data.generalization || node.isRoot) { + return + } + this.setNodeData(node, { + generalization: data || { + text: '概要' + } + }) + node.update() + }) + this.mindMap.render() + } + + // 删除节点概要 + removeGeneralization() { + if (this.activeNodeList.length <= 0) { + return + } + this.activeNodeList.forEach(node => { + if (!node.nodeData.data.generalization) { + return + } + this.setNodeData(node, { + generalization: null + }) + node.update() + }) + this.mindMap.render() + } + + // 设置节点自定义位置 + setNodeCustomPosition(node, left = undefined, top = undefined) { + let nodeList = [node] || this.activeNodeList + nodeList.forEach(item => { + this.setNodeData(item, { + customLeft: left, + customTop: top + }) + }) + } + + // 一键整理布局,即去除自定义位置 + resetLayout() { + walk( + this.root, + null, + node => { + node.customLeft = undefined + node.customTop = undefined + this.setNodeData(node, { + customLeft: undefined, + customTop: undefined + }) + this.mindMap.render() + }, + null, + true, + 0, + 0 + ) + } + + // 设置节点形状 + setNodeShape(node, shape) { + if (!shape || !shapeList.includes(shape)) { + return + } + let nodeList = [node] || this.activeNodeList + nodeList.forEach(item => { + this.setNodeStyle(item, 'shape', shape) + }) + } + + // 更新节点数据 + setNodeData(node, data) { + Object.keys(data).forEach(key => { + node.nodeData.data[key] = data[key] + }) + } + + // 设置节点数据,并判断是否渲染 + setNodeDataRender(node, data) { + this.setNodeData(node, data) + let changed = node.getSize() + node.renderNode() + if (changed) { + if (node.isGeneralization) { + // 概要节点 + node.generalizationBelongNode.updateGeneralization() + } + this.mindMap.render() + } + } + + // 移动节点到画布中心 + moveNodeToCenter(node) { + let halfWidth = this.mindMap.width / 2 + let halfHeight = this.mindMap.height / 2 + let { left, top, width, height } = node + let nodeCenterX = left + width / 2 + let nodeCenterY = top + height / 2 + let { state } = this.mindMap.view.getTransformData() + let targetX = halfWidth - state.x + let targetY = halfHeight - state.y + let offsetX = targetX - nodeCenterX + let offsetY = targetY - nodeCenterY + this.mindMap.view.translateX(offsetX) + this.mindMap.view.translateY(offsetY) + this.mindMap.view.setScale(1) + } +} + +export default Render diff --git a/simple-mind-map/src/Select.js b/simple-mind-map/src/Select.js index 0e4b0c96..95949c20 100644 --- a/simple-mind-map/src/Select.js +++ b/simple-mind-map/src/Select.js @@ -1,17 +1,9 @@ import { bfsWalk, throttle } from './utils' -/** - * @Author: 王林 - * @Date: 2021-07-10 22:34:51 - * @Desc: 选择节点类 - */ +// 选择节点类 class Select { - /** - * @Author: 王林 - * @Date: 2021-07-10 22:35:16 - * @Desc: 构造函数 - */ + // 构造函数 constructor({ mindMap }) { this.mindMap = mindMap this.rect = null @@ -23,11 +15,7 @@ class Select { this.bindEvent() } - /** - * @Author: 王林 - * @Date: 2021-07-10 22:36:36 - * @Desc: 绑定事件 - */ + // 绑定事件 bindEvent() { this.checkInNodes = throttle(this.checkInNodes, 500, this) this.mindMap.on('mousedown', e => { @@ -81,11 +69,7 @@ class Select { }) } - /** - * @Author: 王林 - * @Date: 2021-07-13 07:55:49 - * @Desc: 鼠标移动事件 - */ + // 鼠标移动事件 onMove(x, y) { // 绘制矩形 this.rect.plot([ @@ -128,22 +112,14 @@ class Select { } } - /** - * @Author: 王林 - * @Date: 2021-07-22 08:02:23 - * @Desc: 开启自动移动 - */ + // 开启自动移动 startAutoMove(x, y) { this.autoMoveTimer = setTimeout(() => { this.onMove(x, y) }, 20) } - /** - * @Author: 王林 - * @Date: 2021-07-11 10:19:37 - * @Desc: 创建矩形 - */ + // 创建矩形 createRect(x, y) { this.rect = this.mindMap.svg .polygon() @@ -156,11 +132,7 @@ class Select { .plot([[x, y]]) } - /** - * @Author: 王林 - * @Date: 2021-07-11 10:20:43 - * @Desc: 检测在选区里的节点 - */ + // 检测在选区里的节点 checkInNodes() { let { scaleX, scaleY, translateX, translateY } = this.mindMap.draw.transform() diff --git a/simple-mind-map/src/Shape.js b/simple-mind-map/src/Shape.js index df846132..ea446471 100644 --- a/simple-mind-map/src/Shape.js +++ b/simple-mind-map/src/Shape.js @@ -1,18 +1,10 @@ -/** - * @Author: 王林 - * @Date: 2022-08-22 21:32:50 - * @Desc: 节点形状类 - */ +// 节点形状类 export default class Shape { constructor(node) { this.node = node } - /** - * @Author: 王林 - * @Date: 2022-08-17 22:32:32 - * @Desc: 形状需要的padding - */ + // 形状需要的padding getShapePadding(width, height, paddingX, paddingY) { const shape = this.node.getShape() const defaultPaddingX = 15 @@ -64,11 +56,7 @@ export default class Shape { } } - /** - * @Author: 王林 - * @Date: 2022-08-17 22:22:53 - * @Desc: 创建形状节点 - */ + // 创建形状节点 createShape() { const shape = this.node.getShape() let { width, height } = this.node @@ -104,11 +92,7 @@ export default class Shape { return node } - /** - * @Author: 王林 - * @Date: 2022-09-04 09:08:54 - * @Desc: 创建菱形 - */ + // 创建菱形 createDiamond() { let { width, height } = this.node let halfWidth = width / 2 @@ -129,11 +113,7 @@ export default class Shape { `) } - /** - * @Author: 王林 - * @Date: 2022-09-03 16:14:12 - * @Desc: 创建平行四边形 - */ + // 创建平行四边形 createParallelogram() { let { paddingX } = this.node.getPaddingVale() paddingX = paddingX || this.node.shapePadding.paddingX @@ -146,11 +126,7 @@ export default class Shape { `) } - /** - * @Author: 王林 - * @Date: 2022-09-03 16:50:23 - * @Desc: 创建圆角矩形 - */ + // 创建圆角矩形 createRoundedRectangle() { let { width, height } = this.node let halfHeight = height / 2 @@ -163,12 +139,7 @@ export default class Shape { `) } - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 16:14:08 - * @Desc: 创建八角矩形 - */ + // 创建八角矩形 createOctagonalRectangle() { let w = 5 let { width, height } = this.node @@ -184,12 +155,7 @@ export default class Shape { `) } - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 20:55:50 - * @Desc: 创建外三角矩形 - */ + // 创建外三角矩形 createOuterTriangularRectangle() { let { paddingX } = this.node.getPaddingVale() paddingX = paddingX || this.node.shapePadding.paddingX @@ -204,12 +170,7 @@ export default class Shape { `) } - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 20:59:37 - * @Desc: 创建内三角矩形 - */ + // 创建内三角矩形 createInnerTriangularRectangle() { let { paddingX } = this.node.getPaddingVale() paddingX = paddingX || this.node.shapePadding.paddingX @@ -224,12 +185,7 @@ export default class Shape { `) } - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 21:06:31 - * @Desc: 创建椭圆 - */ + // 创建椭圆 createEllipse() { let { width, height } = this.node let halfWidth = width / 2 @@ -242,12 +198,7 @@ export default class Shape { `) } - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 21:14:04 - * @Desc: 创建圆 - */ + // 创建圆 createCircle() { let { width, height } = this.node let halfWidth = width / 2 diff --git a/simple-mind-map/src/Style.js b/simple-mind-map/src/Style.js index cf6d283e..6917db6d 100644 --- a/simple-mind-map/src/Style.js +++ b/simple-mind-map/src/Style.js @@ -1,238 +1,167 @@ -import { tagColorList } from './utils/constant' -const rootProp = ['paddingX', 'paddingY'] - -/** - * @Author: 王林 - * @Date: 2021-04-11 10:09:08 - * @Desc: 样式类 - */ -class Style { - /** - * @Author: 王林 - * @Date: 2021-04-11 16:01:53 - * @Desc: 设置背景样式 - */ - static setBackgroundStyle(el, themeConfig) { - let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig - el.style.backgroundColor = backgroundColor - if (backgroundImage) { - el.style.backgroundImage = `url(${backgroundImage})` - el.style.backgroundRepeat = backgroundRepeat - } - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 10:10:11 - * @Desc: 构造函数 - */ - constructor(ctx, themeConfig) { - this.ctx = ctx - this.themeConfig = themeConfig - } - - /** - * @Author: 王林 - * @Date: 2021-07-12 07:40:14 - * @Desc: 更新主题配置 - */ - updateThemeConfig(themeConfig) { - this.themeConfig = themeConfig - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 12:02:55 - * @Desc: 合并样式 - */ - merge(prop, root, isActive) { - // 三级及以下节点 - let defaultConfig = this.themeConfig.node - if (root || rootProp.includes(prop)) { - // 直接使用最外层样式 - defaultConfig = this.themeConfig - } else if (this.ctx.isGeneralization) { - // 概要节点 - defaultConfig = this.themeConfig.generalization - } else if (this.ctx.layerIndex === 0) { - // 根节点 - defaultConfig = this.themeConfig.root - } else if (this.ctx.layerIndex === 1) { - // 二级节点 - defaultConfig = this.themeConfig.second - } - // 激活状态 - if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) { - if ( - this.ctx.nodeData.data.activeStyle && - this.ctx.nodeData.data.activeStyle[prop] !== undefined - ) { - return this.ctx.nodeData.data.activeStyle[prop] - } else if (defaultConfig.active && defaultConfig.active[prop]) { - return defaultConfig.active[prop] - } - } - // 优先使用节点本身的样式 - return this.getSelfStyle(prop) !== undefined - ? this.getSelfStyle(prop) - : defaultConfig[prop] - } - - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 21:55:57 - * @Desc: 获取某个样式值 - */ - getStyle(prop, root, isActive) { - return this.merge(prop, root, isActive) - } - - /** - * javascript comment - * @Author: flydreame - * @Date: 2022-09-17 12:09:39 - * @Desc: 获取自身自定义样式 - */ - getSelfStyle(prop) { - return this.ctx.nodeData.data[prop] - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 10:12:56 - * @Desc: 矩形 - */ - rect(node) { - this.shape(node) - node.radius(this.merge('borderRadius')) - } - - /** - * javascript comment - * @Author: 王林 - * @Date: 2022-09-12 15:04:28 - * @Desc: 矩形外的其他形状 - */ - shape(node) { - node.fill({ - color: this.merge('fillColor') - }) - // 节点使用横线样式,不需要渲染非激活状态的边框样式 - if ( - !this.ctx.isRoot && - !this.ctx.isGeneralization && - this.themeConfig.nodeUseLineStyle && - !this.ctx.nodeData.data.isActive - ) { - return - } - node.stroke({ - color: this.merge('borderColor'), - width: this.merge('borderWidth'), - dasharray: this.merge('borderDasharray') - }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 12:07:59 - * @Desc: 文字 - */ - text(node) { - node - .fill({ - color: this.merge('color') - }) - .css({ - 'font-family': this.merge('fontFamily'), - 'font-size': this.merge('fontSize'), - 'font-weight': this.merge('fontWeight'), - 'font-style': this.merge('fontStyle'), - 'text-decoration': this.merge('textDecoration') - }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-13 08:14:34 - * @Desc: html文字节点 - */ - domText(node, fontSizeScale = 1) { - node.style.fontFamily = this.merge('fontFamily') - node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px' - node.style.fontWeight = this.merge('fontWeight') || 'normal' - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 20:02:18 - * @Desc: 标签文字 - */ - tagText(node, index) { - node - .fill({ - color: tagColorList[index].color - }) - .css({ - 'font-size': '12px' - }) - } - - /** - * @Author: 王林 - * @Date: 2021-06-20 21:04:11 - * @Desc: 标签矩形 - */ - tagRect(node, index) { - node.fill({ - color: tagColorList[index].background - }) - } - - /** - * @Author: 王林 - * @Date: 2021-07-03 22:37:19 - * @Desc: 内置图标 - */ - iconNode(node) { - node.attr({ - fill: this.merge('color') - }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 14:50:49 - * @Desc: 连线 - */ - line(node, { width, color, dasharray } = {}) { - node.stroke({ width, color, dasharray }).fill({ color: 'none' }) - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 16:19:03 - * @Desc: 概要连线 - */ - generalizationLine(node) { - node - .stroke({ - width: this.merge('generalizationLineWidth', true), - color: this.merge('generalizationLineColor', true) - }) - .fill({ color: 'none' }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 20:03:59 - * @Desc: 按钮 - */ - iconBtn(node, fillNode) { - node.fill({ color: '#808080' }) - fillNode.fill({ color: '#fff' }) - } -} - -export default Style +import { tagColorList } from './utils/constant' +const rootProp = ['paddingX', 'paddingY'] + +// 样式类 +class Style { + // 设置背景样式 + static setBackgroundStyle(el, themeConfig) { + let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig + el.style.backgroundColor = backgroundColor + if (backgroundImage) { + el.style.backgroundImage = `url(${backgroundImage})` + el.style.backgroundRepeat = backgroundRepeat + } + } + + // 构造函数 + constructor(ctx, themeConfig) { + this.ctx = ctx + this.themeConfig = themeConfig + } + + // 更新主题配置 + updateThemeConfig(themeConfig) { + this.themeConfig = themeConfig + } + + // 合并样式 + merge(prop, root, isActive) { + // 三级及以下节点 + let defaultConfig = this.themeConfig.node + if (root || rootProp.includes(prop)) { + // 直接使用最外层样式 + defaultConfig = this.themeConfig + } else if (this.ctx.isGeneralization) { + // 概要节点 + defaultConfig = this.themeConfig.generalization + } else if (this.ctx.layerIndex === 0) { + // 根节点 + defaultConfig = this.themeConfig.root + } else if (this.ctx.layerIndex === 1) { + // 二级节点 + defaultConfig = this.themeConfig.second + } + // 激活状态 + if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) { + if ( + this.ctx.nodeData.data.activeStyle && + this.ctx.nodeData.data.activeStyle[prop] !== undefined + ) { + return this.ctx.nodeData.data.activeStyle[prop] + } else if (defaultConfig.active && defaultConfig.active[prop]) { + return defaultConfig.active[prop] + } + } + // 优先使用节点本身的样式 + return this.getSelfStyle(prop) !== undefined + ? this.getSelfStyle(prop) + : defaultConfig[prop] + } + + // 获取某个样式值 + getStyle(prop, root, isActive) { + return this.merge(prop, root, isActive) + } + + // 获取自身自定义样式 + getSelfStyle(prop) { + return this.ctx.nodeData.data[prop] + } + + // 矩形 + rect(node) { + this.shape(node) + node.radius(this.merge('borderRadius')) + } + + // 矩形外的其他形状 + shape(node) { + node.fill({ + color: this.merge('fillColor') + }) + // 节点使用横线样式,不需要渲染非激活状态的边框样式 + if ( + !this.ctx.isRoot && + !this.ctx.isGeneralization && + this.themeConfig.nodeUseLineStyle && + !this.ctx.nodeData.data.isActive + ) { + return + } + node.stroke({ + color: this.merge('borderColor'), + width: this.merge('borderWidth'), + dasharray: this.merge('borderDasharray') + }) + } + + // 文字 + text(node) { + node + .fill({ + color: this.merge('color') + }) + .css({ + 'font-family': this.merge('fontFamily'), + 'font-size': this.merge('fontSize'), + 'font-weight': this.merge('fontWeight'), + 'font-style': this.merge('fontStyle'), + 'text-decoration': this.merge('textDecoration') + }) + } + + // html文字节点 + domText(node, fontSizeScale = 1) { + node.style.fontFamily = this.merge('fontFamily') + node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px' + node.style.fontWeight = this.merge('fontWeight') || 'normal' + } + + // 标签文字 + tagText(node, index) { + node + .fill({ + color: tagColorList[index].color + }) + .css({ + 'font-size': '12px' + }) + } + + // 标签矩形 + tagRect(node, index) { + node.fill({ + color: tagColorList[index].background + }) + } + + // 内置图标 + iconNode(node) { + node.attr({ + fill: this.merge('color') + }) + } + + // 连线 + line(node, { width, color, dasharray } = {}) { + node.stroke({ width, color, dasharray }).fill({ color: 'none' }) + } + + // 概要连线 + generalizationLine(node) { + node + .stroke({ + width: this.merge('generalizationLineWidth', true), + color: this.merge('generalizationLineColor', true) + }) + .fill({ color: 'none' }) + } + + // 按钮 + iconBtn(node, fillNode) { + node.fill({ color: '#808080' }) + fillNode.fill({ color: '#fff' }) + } +} + +export default Style diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js index d1b5f4d1..6916bdcc 100644 --- a/simple-mind-map/src/TextEdit.js +++ b/simple-mind-map/src/TextEdit.js @@ -1,18 +1,8 @@ import { getStrWithBrFromHtml } from './utils' -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-06-19 11:11:28 - * @Desc: 节点文字编辑类 - */ +// 节点文字编辑类 export default class TextEdit { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-06-19 11:22:57 - * @Desc: 构造函数 - */ + // 构造函数 constructor(renderer) { this.renderer = renderer this.mindMap = renderer.mindMap @@ -23,11 +13,7 @@ export default class TextEdit { this.bindEvent() } - /** - * @Author: 王林 - * @Date: 2021-04-24 13:27:04 - * @Desc: 事件 - */ + // 事件 bindEvent() { this.show = this.show.bind(this) // 节点双击事件 @@ -54,12 +40,7 @@ export default class TextEdit { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-08-16 16:27:02 - * @Desc: 注册临时快捷键 - */ + // 注册临时快捷键 registerTmpShortcut() { // 注册回车快捷键 this.mindMap.keyCommand.addShortcut('Enter', () => { @@ -67,20 +48,12 @@ export default class TextEdit { }) } - /** - * @Author: 王林 - * @Date: 2021-04-13 22:15:56 - * @Desc: 显示文本编辑框 - */ + // 显示文本编辑框 show(node) { this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect()) } - /** - * @Author: 王林 - * @Date: 2021-04-13 22:13:02 - * @Desc: 显示文本编辑框 - */ + // 显示文本编辑框 showEditTextBox(node, rect) { this.mindMap.emit('before_show_text_edit') this.registerTmpShortcut() @@ -107,11 +80,7 @@ export default class TextEdit { this.selectNodeText() } - /** - * @Author: 王林 - * @Date: 2021-08-02 23:13:50 - * @Desc: 选中文本 - */ + // 选中文本 selectNodeText() { let selection = window.getSelection() let range = document.createRange() @@ -120,11 +89,7 @@ export default class TextEdit { selection.addRange(range) } - /** - * @Author: 王林 - * @Date: 2021-04-24 13:48:16 - * @Desc: 隐藏文本编辑框 - */ + // 隐藏文本编辑框 hideEditTextBox() { if (!this.showTextEdit) { return diff --git a/simple-mind-map/src/View.js b/simple-mind-map/src/View.js index bca83896..244293a4 100644 --- a/simple-mind-map/src/View.js +++ b/simple-mind-map/src/View.js @@ -1,16 +1,6 @@ -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:45:24 - * @Desc: 视图操作类 - */ +// 视图操作类 class View { - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:45:40 - * @Desc: 构造函数 - */ + // 构造函数 constructor(opt = {}) { this.opt = opt this.mindMap = this.opt.mindMap @@ -24,12 +14,7 @@ class View { this.bind() } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:38:51 - * @Desc: 绑定 - */ + // 绑定 bind() { // 快捷键 this.mindMap.keyCommand.addShortcut('Control+=', () => { @@ -80,12 +65,7 @@ class View { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-22 18:30:24 - * @Desc: 获取当前变换状态数据 - */ + // 获取当前变换状态数据 getTransformData() { return { transform: this.mindMap.draw.transform(), @@ -99,12 +79,7 @@ class View { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-11-22 19:54:17 - * @Desc: 动态设置变换状态数据 - */ + // 动态设置变换状态数据 setTransformData(viewData) { if (viewData) { Object.keys(viewData.state).forEach(prop => { @@ -118,55 +93,31 @@ class View { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 15:49:06 - * @Desc: 平移x方向 - */ + // 平移x方向 translateX(step) { this.x += step this.transform() } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:03:53 - * @Desc: 平移x方式到 - */ + // 平移x方式到 translateXTo(x) { this.x = x this.transform() } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 15:48:52 - * @Desc: 平移y方向 - */ + // 平移y方向 translateY(step) { this.y += step this.transform() } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-10-10 14:04:10 - * @Desc: 平移y方向到 - */ + // 平移y方向到 translateYTo(y) { this.y = y this.transform() } - /** - * @Author: 王林 - * @Date: 2021-07-04 17:13:14 - * @Desc: 应用变换 - */ + // 应用变换 transform() { this.mindMap.draw.transform({ scale: this.scale, @@ -176,11 +127,7 @@ class View { this.mindMap.emit('view_data_change', this.getTransformData()) } - /** - * @Author: 王林 - * @Date: 2021-07-11 17:41:35 - * @Desc: 恢复 - */ + // 恢复 reset() { this.scale = 1 this.x = 0 @@ -188,11 +135,7 @@ class View { this.transform() } - /** - * @Author: 王林 - * @Date: 2021-07-04 17:10:34 - * @Desc: 缩小 - */ + // 缩小 narrow() { if (this.scale - this.mindMap.opt.scaleRatio > 0.1) { this.scale -= this.mindMap.opt.scaleRatio @@ -203,23 +146,14 @@ class View { this.mindMap.emit('scale', this.scale) } - /** - * @Author: 王林 - * @Date: 2021-07-04 17:10:41 - * @Desc: 放大 - */ + // 放大 enlarge() { this.scale += this.mindMap.opt.scaleRatio this.transform() this.mindMap.emit('scale', this.scale) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-12-09 16:31:59 - * @Desc: 设置缩放 - */ + // 设置缩放 setScale(scale) { this.scale = scale this.transform() diff --git a/simple-mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js index 6ee9345a..aecdfdd3 100644 --- a/simple-mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -1,16 +1,8 @@ import Node from '../Node' -/** - * @Author: 王林 - * @Date: 2021-04-12 22:24:30 - * @Desc: 布局基类 - */ +// 布局基类 class Base { - /** - * @Author: 王林 - * @Date: 2021-04-12 22:25:16 - * @Desc: 构造函数 - */ + // 构造函数 constructor(renderer) { // 渲染实例 this.renderer = renderer @@ -22,45 +14,25 @@ class Base { this.root = null } - /** - * @Author: 王林 - * @Date: 2021-04-12 22:39:50 - * @Desc: 计算节点位置 - */ + // 计算节点位置 doLayout() { throw new Error('【computed】方法为必要方法,需要子类进行重写!') } - /** - * @Author: 王林 - * @Date: 2021-04-12 22:41:04 - * @Desc: 连线 - */ + // 连线 renderLine() { throw new Error('【renderLine】方法为必要方法,需要子类进行重写!') } - /** - * @Author: 王林 - * @Date: 2021-04-12 22:42:08 - * @Desc: 定位展开收缩按钮 - */ + // 定位展开收缩按钮 renderExpandBtn() { throw new Error('【renderExpandBtn】方法为必要方法,需要子类进行重写!') } - /** - * @Author: 王林 - * @Date: 2022-07-30 22:49:28 - * @Desc: 概要节点 - */ + // 概要节点 renderGeneralization() {} - /** - * @Author: 王林 - * @Date: 2021-07-10 21:30:54 - * @Desc: 创建节点实例 - */ + // 创建节点实例 createNode(data, parent, isRoot, layerIndex) { // 创建节点 let newNode = null @@ -98,22 +70,13 @@ class Base { return newNode } - /** - * @Author: 王林 - * @Date: 2021-07-16 13:48:43 - * @Desc: 定位节点到画布中间 - */ + // 定位节点到画布中间 setNodeCenter(node) { node.left = (this.mindMap.width - node.width) / 2 node.top = (this.mindMap.height - node.height) / 2 } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 11:25:52 - * @Desc: 更新子节点属性 - */ + // 更新子节点属性 updateChildren(children, prop, offset) { children.forEach(item => { item[prop] += offset @@ -124,22 +87,14 @@ class Base { }) } - /** - * @Author: 王林 - * @Date: 2021-04-11 15:05:01 - * @Desc: 二次贝塞尔曲线 - */ + // 二次贝塞尔曲线 quadraticCurvePath(x1, y1, x2, y2) { let cx = x1 + (x2 - x1) * 0.2 let cy = y1 + (y2 - y1) * 0.8 return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` } - /** - * @Author: 王林 - * @Date: 2021-04-11 15:05:18 - * @Desc: 三次贝塞尔曲线 - */ + // 三次贝塞尔曲线 cubicBezierPath(x1, y1, x2, y2) { let cx1 = x1 + (x2 - x1) / 2 let cy1 = y1 @@ -148,33 +103,21 @@ class Base { return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}` } - /** - * @Author: 王林 - * @Date: 2021-06-27 19:00:07 - * @Desc: 获取节点的marginX - */ + // 获取节点的marginX getMarginX(layerIndex) { return layerIndex === 1 ? this.mindMap.themeConfig.second.marginX : this.mindMap.themeConfig.node.marginX } - /** - * @Author: 王林 - * @Date: 2021-04-11 15:34:20 - * @Desc: 获取节点的marginY - */ + // 获取节点的marginY getMarginY(layerIndex) { return layerIndex === 1 ? this.mindMap.themeConfig.second.marginY : this.mindMap.themeConfig.node.marginY } - /** - * @Author: 王林 - * @Date: 2022-07-31 20:53:12 - * @Desc: 获取节点包括概要在内的宽度 - */ + // 获取节点包括概要在内的宽度 getNodeWidthWithGeneralization(node) { return Math.max( node.width, @@ -182,11 +125,7 @@ class Base { ) } - /** - * @Author: 王林 - * @Date: 2022-07-31 20:53:12 - * @Desc: 获取节点包括概要在内的高度 - */ + // 获取节点包括概要在内的高度 getNodeHeightWithGeneralization(node) { return Math.max( node.height, @@ -194,10 +133,8 @@ class Base { ) } + // 获取节点的边界值 /** - * @Author: 王林 - * @Date: 2022-07-31 09:14:03 - * @Desc: 获取节点的边界值 * dir:生长方向,h(水平)、v(垂直) * isLeft:是否向左生长 */ diff --git a/simple-mind-map/src/layouts/CatalogOrganization.js b/simple-mind-map/src/layouts/CatalogOrganization.js index dc20cfb0..0b102655 100644 --- a/simple-mind-map/src/layouts/CatalogOrganization.js +++ b/simple-mind-map/src/layouts/CatalogOrganization.js @@ -1,441 +1,386 @@ -import Base from './Base' -import { walk, asyncRun } from '../utils' - -/** - * @Author: 王林 - * @Date: 2021-04-12 22:25:58 - * @Desc: 目录组织图 - */ -class CatalogOrganization extends Base { - /** - * @Author: 王林 - * @Date: 2021-04-12 22:26:31 - * @Desc: 构造函数 - */ - constructor(opt = {}) { - super(opt) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:04:20 - * @Desc: 布局 - */ - doLayout(callback) { - let task = [ - () => { - this.computedBaseValue() - }, - () => { - this.computedLeftTopValue() - }, - () => { - this.adjustLeftTopValue() - }, - () => { - callback(this.root) - } - ] - asyncRun(task) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:49:32 - * @Desc: 遍历数据计算节点的left、width、height - */ - computedBaseValue() { - walk( - this.renderer.renderTree, - null, - (cur, parent, isRoot, layerIndex) => { - let newNode = this.createNode(cur, parent, isRoot, layerIndex) - // 根节点定位在画布中心位置 - if (isRoot) { - this.setNodeCenter(newNode) - } else { - // 非根节点 - if (parent._node.isRoot) { - newNode.top = - parent._node.top + - parent._node.height + - this.getMarginX(layerIndex) - } - } - if (!cur.data.expand) { - return true - } - }, - (cur, parent, isRoot, layerIndex) => { - if (isRoot) { - 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.getMarginX(layerIndex + 1) - : 0 - } - }, - true, - 0 - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:59:25 - * @Desc: 遍历节点树计算节点的left、top - */ - computedLeftTopValue() { - walk( - this.root, - null, - (node, parent, isRoot, layerIndex) => { - if ( - node.nodeData.data.expand && - node.children && - node.children.length - ) { - let marginX = this.getMarginX(layerIndex + 1) - let marginY = this.getMarginY(layerIndex + 1) - if (isRoot) { - 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 - }) - } else { - let totalTop = node.top + node.height + marginY + node.expandBtnSize - node.children.forEach(cur => { - cur.left = node.left + node.width * 0.5 - cur.top = totalTop - totalTop += cur.height + marginY + node.expandBtnSize - }) - } - } - }, - null, - true - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 10:04:05 - * @Desc: 调整节点left、top - */ - adjustLeftTopValue() { - walk( - this.root, - null, - (node, parent, isRoot, layerIndex) => { - if (!node.nodeData.data.expand) { - return - } - // 调整left - if (parent && parent.isRoot) { - let areaWidth = this.getNodeAreaWidth(node) - let difference = areaWidth - node.width - if (difference > 0) { - this.updateBrothersLeft(node, difference / 2) - } - } - // 调整top - let len = node.children.length - if (parent && !parent.isRoot && len > 0) { - let marginY = this.getMarginY(layerIndex + 1) - let totalHeight = - node.children.reduce((h, item) => { - return h + item.height - }, 0) + - (len + 1) * marginY + - len * node.expandBtnSize - this.updateBrothersTop(node, totalHeight) - } - }, - null, - true - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-12 18:55:03 - * @Desc: 递归计算节点的宽度 - */ - getNodeAreaWidth(node) { - let widthArr = [] - let loop = (node, width) => { - if (node.children.length) { - width += node.width / 2 - node.children.forEach(item => { - loop(item, width) - }) - } else { - width += node.width - widthArr.push(width) - } - } - loop(node, 0) - return Math.max(...widthArr) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 11:12:51 - * @Desc: 调整兄弟节点的left - */ - updateBrothersLeft(node, addWidth) { - if (node.parent) { - let childrenList = node.parent.children - let index = childrenList.findIndex(item => { - return item === node - }) - // 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称 - if ( - (index === 0 || index === childrenList.length - 1) && - childrenList.length > 1 - ) { - let _offset = index === 0 ? -addWidth : addWidth - node.left += _offset - if ( - node.children && - node.children.length && - !node.hasCustomPosition() - ) { - this.updateChildren(node.children, 'left', _offset) - } - } - 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.updateBrothersLeft(node.parent, addWidth) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:26:03 - * @Desc: 调整兄弟节点的top - */ - updateBrothersTop(node, addHeight) { - if (node.parent && !node.parent.isRoot) { - let childrenList = node.parent.children - let index = childrenList.findIndex(item => { - return item === node - }) - childrenList.forEach((item, _index) => { - if (item.hasCustomPosition()) { - // 适配自定义位置 - return - } - let _offset = 0 - // 下面的节点往下移 - if (_index > index) { - _offset = addHeight - } - item.top += _offset - // 同步更新子节点的位置 - if (item.children && item.children.length) { - this.updateChildren(item.children, 'top', _offset) - } - }) - // 更新父节点的位置 - this.updateBrothersTop(node.parent, addHeight) - } - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 14:42:48 - * @Desc: 绘制连线,连接该节点到其子节点 - */ - renderLine(node, lines, style) { - if (node.children.length <= 0) { - return [] - } - let { left, top, width, height, expandBtnSize } = node - let len = node.children.length - let marginX = this.getMarginX(node.layerIndex + 1) - if (node.isRoot) { - // 根节点 - let x1 = left + width / 2 - let y1 = top + height - let s1 = marginX * 0.7 - let minx = Infinity - let maxx = -Infinity - node.children.forEach((item, index) => { - let x2 = item.left + item.width / 2 - let y2 = 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},${y1 + s1 > y2 ? y2 + item.height : y2}` + - nodeUseLineStylePath - // 竖线 - lines[index].plot(path) - style && style(lines[index], item) - }) - minx = Math.min(minx, x1) - maxx = Math.max(maxx, x1) - // 父节点的竖线 - let line1 = this.draw.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() - node.style.line(lin2) - lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`) - node._lines.push(lin2) - style && style(lin2, node) - } - } else { - // 非根节点 - let y1 = top + height - let maxy = -Infinity - let x2 = node.left + node.width * 0.3 - node.children.forEach((item, index) => { - // 为了适配自定义位置,下面做了各种位置的兼容 - let y2 = item.top + item.height / 2 - if (y2 > maxy) { - maxy = y2 - } - // 水平线 - let path = '' - let _left = item.left - let _isLeft = item.left + item.width < x2 - let _isXCenter = false - if (_isLeft) { - // 水平位置在父节点左边 - _left = item.left + item.width - } else if (item.left < x2 && item.left + item.width > x2) { - // 水平位置在父节点之间 - _isXCenter = true - y2 = item.top - maxy = y2 - } - if (y2 > top && y2 < y1) { - // 自定义位置的情况:垂直位置节点在父节点之间 - path = `M ${ - _isLeft ? node.left : node.left + node.width - },${y2} L ${_left},${y2}` - } else if (y2 < y1) { - // 自定义位置的情况:垂直位置节点在父节点上面 - if (_isXCenter) { - y2 = item.top + item.height - _left = x2 - } - path = `M ${x2},${top} L ${x2},${y2} L ${_left},${y2}` - } else { - if (_isXCenter) { - _left = x2 - } - path = `M ${x2},${y2} L ${_left},${y2}` - } - // 节点使用横线风格,需要额外渲染横线 - let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle - ? ` L ${_left},${y2 - item.height / 2} L ${_left},${ - y2 + item.height / 2 - }` - : '' - path += nodeUseLineStylePath - lines[index].plot(path) - style && style(lines[index], item) - }) - // 竖线 - if (len > 0) { - let lin2 = this.draw.path() - expandBtnSize = len > 0 ? expandBtnSize : 0 - node.style.line(lin2) - if (maxy < y1 + expandBtnSize) { - lin2.hide() - } else { - lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`) - lin2.show() - } - node._lines.push(lin2) - style && style(lin2, node) - } - } - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 19:54:26 - * @Desc: 渲染按钮 - */ - renderExpandBtn(node, btn) { - let { width, height, expandBtnSize, isRoot } = node - if (!isRoot) { - let { translateX, translateY } = btn.transform() - btn.translate( - width * 0.3 - expandBtnSize / 2 - translateX, - height + expandBtnSize / 2 - translateY - ) - } - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 08:30:35 - * @Desc: 创建概要节点 - */ - renderGeneralization(node, gLine, gNode) { - let { - top, - bottom, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h') - let x1 = right + generalizationLineMargin - let y1 = top - let x2 = right + generalizationLineMargin - let y2 = bottom - let cx = x1 + 20 - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = right + generalizationNodeMargin - gNode.top = top + (bottom - top - gNode.height) / 2 - } -} - -export default CatalogOrganization +import Base from './Base' +import { walk, asyncRun } from '../utils' + +// 目录组织图 +class CatalogOrganization extends Base { + // 构造函数 + constructor(opt = {}) { + super(opt) + } + + // 布局 + doLayout(callback) { + let task = [ + () => { + this.computedBaseValue() + }, + () => { + this.computedLeftTopValue() + }, + () => { + this.adjustLeftTopValue() + }, + () => { + callback(this.root) + } + ] + asyncRun(task) + } + + // 遍历数据计算节点的left、width、height + computedBaseValue() { + walk( + this.renderer.renderTree, + null, + (cur, parent, isRoot, layerIndex) => { + let newNode = this.createNode(cur, parent, isRoot, layerIndex) + // 根节点定位在画布中心位置 + if (isRoot) { + this.setNodeCenter(newNode) + } else { + // 非根节点 + if (parent._node.isRoot) { + newNode.top = + parent._node.top + + parent._node.height + + this.getMarginX(layerIndex) + } + } + if (!cur.data.expand) { + return true + } + }, + (cur, parent, isRoot, layerIndex) => { + if (isRoot) { + 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.getMarginX(layerIndex + 1) + : 0 + } + }, + true, + 0 + ) + } + + // 遍历节点树计算节点的left、top + computedLeftTopValue() { + walk( + this.root, + null, + (node, parent, isRoot, layerIndex) => { + if ( + node.nodeData.data.expand && + node.children && + node.children.length + ) { + let marginX = this.getMarginX(layerIndex + 1) + let marginY = this.getMarginY(layerIndex + 1) + if (isRoot) { + 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 + }) + } else { + let totalTop = node.top + node.height + marginY + node.expandBtnSize + node.children.forEach(cur => { + cur.left = node.left + node.width * 0.5 + cur.top = totalTop + totalTop += cur.height + marginY + node.expandBtnSize + }) + } + } + }, + null, + true + ) + } + + // 调整节点left、top + adjustLeftTopValue() { + walk( + this.root, + null, + (node, parent, isRoot, layerIndex) => { + if (!node.nodeData.data.expand) { + return + } + // 调整left + if (parent && parent.isRoot) { + let areaWidth = this.getNodeAreaWidth(node) + let difference = areaWidth - node.width + if (difference > 0) { + this.updateBrothersLeft(node, difference / 2) + } + } + // 调整top + let len = node.children.length + if (parent && !parent.isRoot && len > 0) { + let marginY = this.getMarginY(layerIndex + 1) + let totalHeight = + node.children.reduce((h, item) => { + return h + item.height + }, 0) + + (len + 1) * marginY + + len * node.expandBtnSize + this.updateBrothersTop(node, totalHeight) + } + }, + null, + true + ) + } + + // 递归计算节点的宽度 + getNodeAreaWidth(node) { + let widthArr = [] + let loop = (node, width) => { + if (node.children.length) { + width += node.width / 2 + node.children.forEach(item => { + loop(item, width) + }) + } else { + width += node.width + widthArr.push(width) + } + } + loop(node, 0) + return Math.max(...widthArr) + } + + // 调整兄弟节点的left + updateBrothersLeft(node, addWidth) { + if (node.parent) { + let childrenList = node.parent.children + let index = childrenList.findIndex(item => { + return item === node + }) + // 存在大于一个节点时,第一个或最后一个节点自身也需要移动,否则两边不对称 + if ( + (index === 0 || index === childrenList.length - 1) && + childrenList.length > 1 + ) { + let _offset = index === 0 ? -addWidth : addWidth + node.left += _offset + if ( + node.children && + node.children.length && + !node.hasCustomPosition() + ) { + this.updateChildren(node.children, 'left', _offset) + } + } + 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.updateBrothersLeft(node.parent, addWidth) + } + } + + // 调整兄弟节点的top + updateBrothersTop(node, addHeight) { + if (node.parent && !node.parent.isRoot) { + let childrenList = node.parent.children + let index = childrenList.findIndex(item => { + return item === node + }) + childrenList.forEach((item, _index) => { + if (item.hasCustomPosition()) { + // 适配自定义位置 + return + } + let _offset = 0 + // 下面的节点往下移 + if (_index > index) { + _offset = addHeight + } + item.top += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'top', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothersTop(node.parent, addHeight) + } + } + + // 绘制连线,连接该节点到其子节点 + renderLine(node, lines, style) { + if (node.children.length <= 0) { + return [] + } + let { left, top, width, height, expandBtnSize } = node + let len = node.children.length + let marginX = this.getMarginX(node.layerIndex + 1) + if (node.isRoot) { + // 根节点 + let x1 = left + width / 2 + let y1 = top + height + let s1 = marginX * 0.7 + let minx = Infinity + let maxx = -Infinity + node.children.forEach((item, index) => { + let x2 = item.left + item.width / 2 + let y2 = 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},${y1 + s1 > y2 ? y2 + item.height : y2}` + + nodeUseLineStylePath + // 竖线 + lines[index].plot(path) + style && style(lines[index], item) + }) + minx = Math.min(minx, x1) + maxx = Math.max(maxx, x1) + // 父节点的竖线 + let line1 = this.draw.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() + node.style.line(lin2) + lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`) + node._lines.push(lin2) + style && style(lin2, node) + } + } else { + // 非根节点 + let y1 = top + height + let maxy = -Infinity + let x2 = node.left + node.width * 0.3 + node.children.forEach((item, index) => { + // 为了适配自定义位置,下面做了各种位置的兼容 + let y2 = item.top + item.height / 2 + if (y2 > maxy) { + maxy = y2 + } + // 水平线 + let path = '' + let _left = item.left + let _isLeft = item.left + item.width < x2 + let _isXCenter = false + if (_isLeft) { + // 水平位置在父节点左边 + _left = item.left + item.width + } else if (item.left < x2 && item.left + item.width > x2) { + // 水平位置在父节点之间 + _isXCenter = true + y2 = item.top + maxy = y2 + } + if (y2 > top && y2 < y1) { + // 自定义位置的情况:垂直位置节点在父节点之间 + path = `M ${ + _isLeft ? node.left : node.left + node.width + },${y2} L ${_left},${y2}` + } else if (y2 < y1) { + // 自定义位置的情况:垂直位置节点在父节点上面 + if (_isXCenter) { + y2 = item.top + item.height + _left = x2 + } + path = `M ${x2},${top} L ${x2},${y2} L ${_left},${y2}` + } else { + if (_isXCenter) { + _left = x2 + } + path = `M ${x2},${y2} L ${_left},${y2}` + } + // 节点使用横线风格,需要额外渲染横线 + let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle + ? ` L ${_left},${y2 - item.height / 2} L ${_left},${ + y2 + item.height / 2 + }` + : '' + path += nodeUseLineStylePath + lines[index].plot(path) + style && style(lines[index], item) + }) + // 竖线 + if (len > 0) { + let lin2 = this.draw.path() + expandBtnSize = len > 0 ? expandBtnSize : 0 + node.style.line(lin2) + if (maxy < y1 + expandBtnSize) { + lin2.hide() + } else { + lin2.plot(`M ${x2},${y1 + expandBtnSize} L ${x2},${maxy}`) + lin2.show() + } + node._lines.push(lin2) + style && style(lin2, node) + } + } + } + + // 渲染按钮 + renderExpandBtn(node, btn) { + let { width, height, expandBtnSize, isRoot } = node + if (!isRoot) { + let { translateX, translateY } = btn.transform() + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + height + expandBtnSize / 2 - translateY + ) + } + } + + // 创建概要节点 + renderGeneralization(node, gLine, gNode) { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeBoundaries(node, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + gLine.plot(path) + gNode.left = right + generalizationNodeMargin + gNode.top = top + (bottom - top - gNode.height) / 2 + } +} + +export default CatalogOrganization diff --git a/simple-mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js index 1940b977..20636e93 100644 --- a/simple-mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -1,327 +1,267 @@ -import Base from './Base' -import { walk, asyncRun } from '../utils' - -/** - * @Author: 王林 - * @Date: 2021-04-12 22:25:58 - * @Desc: 逻辑结构图 - */ -class LogicalStructure extends Base { - /** - * @Author: 王林 - * @Date: 2021-04-12 22:26:31 - * @Desc: 构造函数 - */ - constructor(opt = {}) { - super(opt) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:04:20 - * @Desc: 布局 - */ - doLayout(callback) { - let task = [ - () => { - this.computedBaseValue() - }, - () => { - this.computedTopValue() - }, - () => { - this.adjustTopValue() - }, - () => { - callback(this.root) - } - ] - asyncRun(task) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:49:32 - * @Desc: 遍历数据计算节点的left、width、height - */ - computedBaseValue() { - walk( - this.renderer.renderTree, - null, - (cur, parent, isRoot, layerIndex) => { - let newNode = this.createNode(cur, parent, isRoot, layerIndex) - // 根节点定位在画布中心位置 - if (isRoot) { - this.setNodeCenter(newNode) - } else { - // 非根节点 - // 定位到父节点右侧 - newNode.left = - parent._node.left + parent._node.width + this.getMarginX(layerIndex) - } - if (!cur.data.expand) { - return true - } - }, - (cur, parent, isRoot, layerIndex) => { - // 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距 - let len = cur.data.expand === false ? 0 : cur._node.children.length - cur._node.childrenAreaHeight = len - ? cur._node.children.reduce((h, item) => { - return h + item.height - }, 0) + - (len + 1) * this.getMarginY(layerIndex + 1) - : 0 - }, - true, - 0 - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:59:25 - * @Desc: 遍历节点树计算节点的top - */ - computedTopValue() { - walk( - this.root, - null, - (node, parent, isRoot, layerIndex) => { - if ( - node.nodeData.data.expand && - node.children && - node.children.length - ) { - let marginY = this.getMarginY(layerIndex + 1) - // 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半 - let top = node.top + node.height / 2 - node.childrenAreaHeight / 2 - let totalTop = top + marginY - node.children.forEach(cur => { - cur.top = totalTop - totalTop += cur.height + marginY - }) - } - }, - null, - true - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 10:04:05 - * @Desc: 调整节点top - */ - adjustTopValue() { - walk( - this.root, - null, - (node, parent, isRoot, layerIndex) => { - if (!node.nodeData.data.expand) { - return - } - // 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置 - let difference = - node.childrenAreaHeight - - this.getMarginY(layerIndex + 1) * 2 - - node.height - if (difference > 0) { - this.updateBrothers(node, difference / 2) - } - }, - null, - true - ) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:26:03 - * @Desc: 更新兄弟节点的top - */ - updateBrothers(node, addHeight) { - if (node.parent) { - let childrenList = node.parent.children - let index = childrenList.findIndex(item => { - return item === node - }) - childrenList.forEach((item, _index) => { - if (item === node || item.hasCustomPosition()) { - // 适配自定义位置 - return - } - let _offset = 0 - // 上面的节点往上移 - if (_index < index) { - _offset = -addHeight - } else if (_index > index) { - // 下面的节点往下移 - _offset = addHeight - } - item.top += _offset - // 同步更新子节点的位置 - if (item.children && item.children.length) { - this.updateChildren(item.children, 'top', _offset) - } - }) - // 更新父节点的位置 - this.updateBrothers(node.parent, addHeight) - } - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 14:42:48 - * @Desc: 绘制连线,连接该节点到其子节点 - */ - 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) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:17:30 - * @Desc: 直线风格连线 - */ - renderLineStraight(node, lines, style) { - if (node.children.length <= 0) { - return [] - } - let { left, top, width, height, expandBtnSize } = node - let marginX = this.getMarginX(node.layerIndex + 1) - let s1 = (marginX - expandBtnSize) * 0.6 - node.children.forEach((item, index) => { - let x1 = - node.layerIndex === 0 ? left + width : left + width + expandBtnSize - let y1 = top + height / 2 - let x2 = item.left - let y2 = item.top + item.height / 2 - // 节点使用横线风格,需要额外渲染横线 - let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle - ? item.width - : 0 - let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${ - x2 + nodeUseLineStyleOffset - },${y2}` - lines[index].plot(path) - style && style(lines[index], item) - }) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:34:41 - * @Desc: 直连风格 - */ - renderLineDirect(node, lines, style) { - if (node.children.length <= 0) { - return [] - } - let { left, top, width, height, expandBtnSize } = node - node.children.forEach((item, index) => { - let x1 = - node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize - let y1 = top + height / 2 - let x2 = item.left - let y2 = item.top + item.height / 2 - // 节点使用横线风格,需要额外渲染横线 - let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle - ? ` L ${item.left + item.width},${y2}` - : '' - let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath - lines[index].plot(path) - style && style(lines[index], item) - }) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:17:43 - * @Desc: 曲线风格连线 - */ - renderLineCurve(node, lines, style) { - if (node.children.length <= 0) { - return [] - } - let { left, top, width, height, expandBtnSize } = node - node.children.forEach((item, index) => { - let x1 = - node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize - let y1 = top + height / 2 - let x2 = item.left - let y2 = item.top + item.height / 2 - let path = '' - // 节点使用横线风格,需要额外渲染横线 - let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle - ? ` L ${item.left + item.width},${y2}` - : '' - if (node.isRoot) { - path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath - } else { - path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath - } - lines[index].plot(path) - style && style(lines[index], item) - }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 19:54:26 - * @Desc: 渲染按钮 - */ - renderExpandBtn(node, btn) { - let { width, height } = node - let { translateX, translateY } = btn.transform() - // 节点使用横线风格,需要调整展开收起按钮位置 - let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle - ? height / 2 - : 0 - btn.translate( - width - translateX, - height / 2 - translateY + nodeUseLineStyleOffset - ) - } - - /** - * @Author: 王林 - * @Date: 2022-07-30 08:30:35 - * @Desc: 创建概要节点 - */ - renderGeneralization(node, gLine, gNode) { - let { - top, - bottom, - right, - generalizationLineMargin, - generalizationNodeMargin - } = this.getNodeBoundaries(node, 'h') - let x1 = right + generalizationLineMargin - let y1 = top - let x2 = right + generalizationLineMargin - let y2 = bottom - let cx = x1 + 20 - let cy = y1 + (y2 - y1) / 2 - let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` - gLine.plot(path) - gNode.left = right + generalizationNodeMargin - gNode.top = top + (bottom - top - gNode.height) / 2 - } -} - -export default LogicalStructure +import Base from './Base' +import { walk, asyncRun } from '../utils' + +// 逻辑结构图 +class LogicalStructure extends Base { + // 构造函数 + constructor(opt = {}) { + super(opt) + } + + // 布局 + doLayout(callback) { + let task = [ + () => { + this.computedBaseValue() + }, + () => { + this.computedTopValue() + }, + () => { + this.adjustTopValue() + }, + () => { + callback(this.root) + } + ] + asyncRun(task) + } + + // 遍历数据计算节点的left、width、height + computedBaseValue() { + walk( + this.renderer.renderTree, + null, + (cur, parent, isRoot, layerIndex) => { + let newNode = this.createNode(cur, parent, isRoot, layerIndex) + // 根节点定位在画布中心位置 + if (isRoot) { + this.setNodeCenter(newNode) + } else { + // 非根节点 + // 定位到父节点右侧 + newNode.left = + parent._node.left + parent._node.width + this.getMarginX(layerIndex) + } + if (!cur.data.expand) { + return true + } + }, + (cur, parent, isRoot, layerIndex) => { + // 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距 + let len = cur.data.expand === false ? 0 : cur._node.children.length + cur._node.childrenAreaHeight = len + ? cur._node.children.reduce((h, item) => { + return h + item.height + }, 0) + + (len + 1) * this.getMarginY(layerIndex + 1) + : 0 + }, + true, + 0 + ) + } + + // 遍历节点树计算节点的top + computedTopValue() { + walk( + this.root, + null, + (node, parent, isRoot, layerIndex) => { + if ( + node.nodeData.data.expand && + node.children && + node.children.length + ) { + let marginY = this.getMarginY(layerIndex + 1) + // 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半 + let top = node.top + node.height / 2 - node.childrenAreaHeight / 2 + let totalTop = top + marginY + node.children.forEach(cur => { + cur.top = totalTop + totalTop += cur.height + marginY + }) + } + }, + null, + true + ) + } + + // 调整节点top + adjustTopValue() { + walk( + this.root, + null, + (node, parent, isRoot, layerIndex) => { + if (!node.nodeData.data.expand) { + return + } + // 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置 + let difference = + node.childrenAreaHeight - + this.getMarginY(layerIndex + 1) * 2 - + node.height + if (difference > 0) { + this.updateBrothers(node, difference / 2) + } + }, + null, + true + ) + } + + // 更新兄弟节点的top + updateBrothers(node, addHeight) { + if (node.parent) { + let childrenList = node.parent.children + let index = childrenList.findIndex(item => { + return item === node + }) + childrenList.forEach((item, _index) => { + if (item === node || item.hasCustomPosition()) { + // 适配自定义位置 + return + } + let _offset = 0 + // 上面的节点往上移 + if (_index < index) { + _offset = -addHeight + } else if (_index > index) { + // 下面的节点往下移 + _offset = addHeight + } + item.top += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'top', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothers(node.parent, addHeight) + } + } + + // 绘制连线,连接该节点到其子节点 + 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) + } + } + + // 直线风格连线 + renderLineStraight(node, lines, style) { + if (node.children.length <= 0) { + return [] + } + let { left, top, width, height, expandBtnSize } = node + let marginX = this.getMarginX(node.layerIndex + 1) + let s1 = (marginX - expandBtnSize) * 0.6 + node.children.forEach((item, index) => { + let x1 = + node.layerIndex === 0 ? left + width : left + width + expandBtnSize + let y1 = top + height / 2 + let x2 = item.left + let y2 = item.top + item.height / 2 + // 节点使用横线风格,需要额外渲染横线 + let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle + ? item.width + : 0 + let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${ + x2 + nodeUseLineStyleOffset + },${y2}` + lines[index].plot(path) + style && style(lines[index], item) + }) + } + + // 直连风格 + renderLineDirect(node, lines, style) { + if (node.children.length <= 0) { + return [] + } + let { left, top, width, height, expandBtnSize } = node + node.children.forEach((item, index) => { + let x1 = + node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize + let y1 = top + height / 2 + let x2 = item.left + let y2 = item.top + item.height / 2 + // 节点使用横线风格,需要额外渲染横线 + let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle + ? ` L ${item.left + item.width},${y2}` + : '' + let path = `M ${x1},${y1} L ${x2},${y2}` + nodeUseLineStylePath + lines[index].plot(path) + style && style(lines[index], item) + }) + } + + // 曲线风格连线 + renderLineCurve(node, lines, style) { + if (node.children.length <= 0) { + return [] + } + let { left, top, width, height, expandBtnSize } = node + node.children.forEach((item, index) => { + let x1 = + node.layerIndex === 0 ? left + width / 2 : left + width + expandBtnSize + let y1 = top + height / 2 + let x2 = item.left + let y2 = item.top + item.height / 2 + let path = '' + // 节点使用横线风格,需要额外渲染横线 + let nodeUseLineStylePath = this.mindMap.themeConfig.nodeUseLineStyle + ? ` L ${item.left + item.width},${y2}` + : '' + if (node.isRoot) { + path = this.quadraticCurvePath(x1, y1, x2, y2) + nodeUseLineStylePath + } else { + path = this.cubicBezierPath(x1, y1, x2, y2) + nodeUseLineStylePath + } + lines[index].plot(path) + style && style(lines[index], item) + }) + } + + // 渲染按钮 + renderExpandBtn(node, btn) { + let { width, height } = node + let { translateX, translateY } = btn.transform() + // 节点使用横线风格,需要调整展开收起按钮位置 + let nodeUseLineStyleOffset = this.mindMap.themeConfig.nodeUseLineStyle + ? height / 2 + : 0 + btn.translate( + width - translateX, + height / 2 - translateY + nodeUseLineStyleOffset + ) + } + + // 创建概要节点 + renderGeneralization(node, gLine, gNode) { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeBoundaries(node, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + gLine.plot(path) + gNode.left = right + generalizationNodeMargin + gNode.top = top + (bottom - top - gNode.height) / 2 + } +} + +export default LogicalStructure diff --git a/simple-mind-map/src/layouts/MindMap.js b/simple-mind-map/src/layouts/MindMap.js index e188da27..99ebfa03 100644 --- a/simple-mind-map/src/layouts/MindMap.js +++ b/simple-mind-map/src/layouts/MindMap.js @@ -1,28 +1,17 @@ import Base from './Base' import { walk, asyncRun } from '../utils' -/** - * @Author: 王林 - * @Date: 2021-04-12 22:25:58 - * @Desc: 思维导图 - * 在逻辑结构图的基础上增加一个变量来记录生长方向,向左还是向右,同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可 - */ +// 思维导图 + class MindMap extends Base { - /** - * @Author: 王林 - * @Date: 2021-04-12 22:26:31 - * @Desc: 构造函数 - */ + // 构造函数 + // 在逻辑结构图的基础上增加一个变量来记录生长方向,向左还是向右,同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可 constructor(opt = {}) { super(opt) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:04:20 - * @Desc: 布局 - */ + // 布局 + doLayout(callback) { let task = [ () => { @@ -41,12 +30,8 @@ class MindMap extends Base { asyncRun(task) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:49:32 - * @Desc: 遍历数据计算节点的left、width、height - */ + // 遍历数据计算节点的left、width、height + computedBaseValue() { walk( this.renderer.renderTree, @@ -110,12 +95,8 @@ class MindMap extends Base { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:59:25 - * @Desc: 遍历节点树计算节点的top - */ + // 遍历节点树计算节点的top + computedTopValue() { walk( this.root, @@ -147,12 +128,8 @@ class MindMap extends Base { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 10:04:05 - * @Desc: 调整节点top - */ + // 调整节点top + adjustTopValue() { walk( this.root, @@ -174,12 +151,8 @@ class MindMap extends Base { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:26:03 - * @Desc: 更新兄弟节点的top - */ + // 更新兄弟节点的top + updateBrothers(node, leftAddHeight, rightAddHeight) { if (node.parent) { // 过滤出和自己同方向的节点 @@ -214,11 +187,8 @@ class MindMap extends Base { } } - /** - * @Author: 王林 - * @Date: 2021-04-11 14:42:48 - * @Desc: 绘制连线,连接该节点到其子节点 - */ + // 绘制连线,连接该节点到其子节点 + renderLine(node, lines, style, lineStyle) { if (lineStyle === 'curve') { this.renderLineCurve(node, lines, style) @@ -229,12 +199,8 @@ class MindMap extends Base { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:10:47 - * @Desc: 直线风格连线 - */ + // 直线风格连线 + renderLineStraight(node, lines, style) { if (node.children.length <= 0) { return [] @@ -268,12 +234,8 @@ class MindMap extends Base { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:34:41 - * @Desc: 直连风格 - */ + // 直连风格 + renderLineDirect(node, lines, style) { if (node.children.length <= 0) { return [] @@ -304,12 +266,8 @@ class MindMap extends Base { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:10:56 - * @Desc: 曲线风格连线 - */ + // 曲线风格连线 + renderLineCurve(node, lines, style) { if (node.children.length <= 0) { return [] @@ -345,11 +303,8 @@ class MindMap extends Base { }) } - /** - * @Author: 王林 - * @Date: 2021-04-11 19:54:26 - * @Desc: 渲染按钮 - */ + // 渲染按钮 + renderExpandBtn(node, btn) { let { width, height, expandBtnSize } = node let { translateX, translateY } = btn.transform() @@ -362,11 +317,8 @@ class MindMap extends Base { btn.translate(x, y) } - /** - * @Author: 王林 - * @Date: 2022-07-30 08:30:35 - * @Desc: 创建概要节点 - */ + // 创建概要节点 + renderGeneralization(node, gLine, gNode) { let isLeft = node.dir === 'left' let { diff --git a/simple-mind-map/src/layouts/OrganizationStructure.js b/simple-mind-map/src/layouts/OrganizationStructure.js index 51bffe16..4c2dc460 100644 --- a/simple-mind-map/src/layouts/OrganizationStructure.js +++ b/simple-mind-map/src/layouts/OrganizationStructure.js @@ -1,28 +1,17 @@ import Base from './Base' import { walk, asyncRun } from '../utils' -/** - * @Author: 王林 - * @Date: 2021-04-12 22:25:58 - * @Desc: 组织结构图 - * 和逻辑结构图基本一样,只是方向变成向下生长,所以先计算节点的top,后计算节点的left、最后调整节点的left即可 - */ +// 组织结构图 +// 和逻辑结构图基本一样,只是方向变成向下生长,所以先计算节点的top,后计算节点的left、最后调整节点的left即可 class OrganizationStructure extends Base { - /** - * @Author: 王林 - * @Date: 2021-04-12 22:26:31 - * @Desc: 构造函数 - */ + // 构造函数 + constructor(opt = {}) { super(opt) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:04:20 - * @Desc: 布局 - */ + // 布局 + doLayout(callback) { let task = [ () => { @@ -41,12 +30,8 @@ class OrganizationStructure extends Base { asyncRun(task) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:49:32 - * @Desc: 遍历数据计算节点的left、width、height - */ + // 遍历数据计算节点的left、width、height + computedBaseValue() { walk( this.renderer.renderTree, @@ -81,12 +66,8 @@ class OrganizationStructure extends Base { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 09:59:25 - * @Desc: 遍历节点树计算节点的left - */ + // 遍历节点树计算节点的left + computedLeftValue() { walk( this.root, @@ -112,12 +93,8 @@ class OrganizationStructure extends Base { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 10:04:05 - * @Desc: 调整节点left - */ + // 调整节点left + adjustLeftValue() { walk( this.root, @@ -140,12 +117,8 @@ class OrganizationStructure extends Base { ) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:26:03 - * @Desc: 更新兄弟节点的left - */ + // 更新兄弟节点的left + updateBrothers(node, addWidth) { if (node.parent) { let childrenList = node.parent.children @@ -176,11 +149,8 @@ class OrganizationStructure extends Base { } } - /** - * @Author: 王林 - * @Date: 2021-04-11 14:42:48 - * @Desc: 绘制连线,连接该节点到其子节点 - */ + // 绘制连线,连接该节点到其子节点 + renderLine(node, lines, style, lineStyle) { if (lineStyle === 'direct') { this.renderLineDirect(node, lines, style) @@ -189,12 +159,8 @@ class OrganizationStructure extends Base { } } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:34:41 - * @Desc: 直连风格 - */ + // 直连风格 + renderLineDirect(node, lines, style) { if (node.children.length <= 0) { return [] @@ -215,12 +181,8 @@ class OrganizationStructure extends Base { }) } - /** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-30 14:39:07 - * @Desc: 直线风格连线 - */ + // 直线风格连线 + renderLineStraight(node, lines, style) { if (node.children.length <= 0) { return [] @@ -269,11 +231,8 @@ class OrganizationStructure extends Base { } } - /** - * @Author: 王林 - * @Date: 2021-04-11 19:54:26 - * @Desc: 渲染按钮 - */ + // 渲染按钮 + renderExpandBtn(node, btn) { let { width, height, expandBtnSize } = node let { translateX, translateY } = btn.transform() @@ -283,11 +242,8 @@ class OrganizationStructure extends Base { ) } - /** - * @Author: 王林 - * @Date: 2022-07-30 08:30:35 - * @Desc: 创建概要节点 - */ + // 创建概要节点 + renderGeneralization(node, gLine, gNode) { let { bottom, diff --git a/simple-mind-map/src/parse/xmind.js b/simple-mind-map/src/parse/xmind.js index 1199fdf9..4f4a3faf 100644 --- a/simple-mind-map/src/parse/xmind.js +++ b/simple-mind-map/src/parse/xmind.js @@ -1,12 +1,7 @@ import JSZip from 'jszip' import xmlConvert from 'xml-js' -/** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-21 14:07:47 - * @Desc: 解析.xmind文件 - */ +// 解析.xmind文件 const parseXmindFile = file => { return new Promise((resolve, reject) => { JSZip.loadAsync(file).then( @@ -37,12 +32,7 @@ const parseXmindFile = file => { }) } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-21 18:57:25 - * @Desc: 转换xmind数据 - */ +// 转换xmind数据 const transformXmind = content => { let data = JSON.parse(content)[0] let nodeTree = data.rootTopic @@ -82,12 +72,7 @@ const transformXmind = content => { return newTree } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2022-09-23 15:51:51 - * @Desc: 转换旧版xmind数据,xmind8 - */ +// 转换旧版xmind数据,xmind8 const transformOldXmind = content => { let data = JSON.parse(content) let elements = data.elements diff --git a/simple-mind-map/src/svg/btns.js b/simple-mind-map/src/svg/btns.js index c099a4d9..e456fe22 100644 --- a/simple-mind-map/src/svg/btns.js +++ b/simple-mind-map/src/svg/btns.js @@ -1,15 +1,7 @@ -/** - * @Author: 王林 - * @Date: 2021-04-11 19:46:10 - * @Desc: 展开按钮 - */ +// 展开按钮 const open = `` -/** - * @Author: 王林 - * @Date: 2021-04-11 19:46:23 - * @Desc: 收缩按钮 - */ +// 收缩按钮 const close = `` export default { diff --git a/simple-mind-map/src/svg/icons.js b/simple-mind-map/src/svg/icons.js index ddb7ff8f..735926e3 100644 --- a/simple-mind-map/src/svg/icons.js +++ b/simple-mind-map/src/svg/icons.js @@ -278,11 +278,7 @@ export const nodeIconList = [ } ] -/** - * @Author: 王林 - * @Date: 2021-06-23 22:36:56 - * @Desc: 获取nodeIconList icon内容 - */ +// 获取nodeIconList icon内容 const getNodeIconListIcon = name => { let arr = name.split('_') let typeData = nodeIconList.find(item => { diff --git a/simple-mind-map/src/themes/blueSky.js b/simple-mind-map/src/themes/blueSky.js index aa085c28..1f464a72 100644 --- a/simple-mind-map/src/themes/blueSky.js +++ b/simple-mind-map/src/themes/blueSky.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 天空蓝 - */ +// 天空蓝 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(115, 161, 191)', diff --git a/simple-mind-map/src/themes/brainImpairedPink.js b/simple-mind-map/src/themes/brainImpairedPink.js index a31881e2..e301bdfc 100644 --- a/simple-mind-map/src/themes/brainImpairedPink.js +++ b/simple-mind-map/src/themes/brainImpairedPink.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 脑残粉 - */ +// 脑残粉 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(191, 115, 148)', diff --git a/simple-mind-map/src/themes/classic.js b/simple-mind-map/src/themes/classic.js index d501a71c..eeeff67e 100644 --- a/simple-mind-map/src/themes/classic.js +++ b/simple-mind-map/src/themes/classic.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 脑图经典 - */ +// 脑图经典 export default merge(defaultTheme, { // 连线的颜色 lineColor: '#fff', diff --git a/simple-mind-map/src/themes/classic2.js b/simple-mind-map/src/themes/classic2.js index 2e00a182..2c40fc70 100644 --- a/simple-mind-map/src/themes/classic2.js +++ b/simple-mind-map/src/themes/classic2.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 经典2 - */ +// 经典2 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(51, 51, 51)', diff --git a/simple-mind-map/src/themes/classic3.js b/simple-mind-map/src/themes/classic3.js index f8e10b08..27ce5c6b 100644 --- a/simple-mind-map/src/themes/classic3.js +++ b/simple-mind-map/src/themes/classic3.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 经典3 - */ +// 经典3 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(94, 202, 110)', diff --git a/simple-mind-map/src/themes/classic4.js b/simple-mind-map/src/themes/classic4.js index 449ca317..a4046e9e 100644 --- a/simple-mind-map/src/themes/classic4.js +++ b/simple-mind-map/src/themes/classic4.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 经典4 - */ +// 经典4 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(30, 53, 86)', diff --git a/simple-mind-map/src/themes/classicBlue.js b/simple-mind-map/src/themes/classicBlue.js index a085aa60..59539342 100644 --- a/simple-mind-map/src/themes/classicBlue.js +++ b/simple-mind-map/src/themes/classicBlue.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 经典蓝 - */ +// 经典蓝 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(51, 51, 51)', diff --git a/simple-mind-map/src/themes/classicGreen.js b/simple-mind-map/src/themes/classicGreen.js index c6e7648a..87725dbf 100644 --- a/simple-mind-map/src/themes/classicGreen.js +++ b/simple-mind-map/src/themes/classicGreen.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 经典绿 - */ +// 经典绿 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(123, 199, 120)', diff --git a/simple-mind-map/src/themes/dark.js b/simple-mind-map/src/themes/dark.js index 475a904e..08350045 100644 --- a/simple-mind-map/src/themes/dark.js +++ b/simple-mind-map/src/themes/dark.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 暗色 - */ +// 暗色 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(17, 68, 23)', diff --git a/simple-mind-map/src/themes/dark2.js b/simple-mind-map/src/themes/dark2.js index d55ab41b..a81de694 100644 --- a/simple-mind-map/src/themes/dark2.js +++ b/simple-mind-map/src/themes/dark2.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 暗色2 - */ +// 暗色2 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(75, 81, 78)', diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js index 7d6c94d5..b101a7ed 100644 --- a/simple-mind-map/src/themes/default.js +++ b/simple-mind-map/src/themes/default.js @@ -1,146 +1,142 @@ -/** - * @Author: 王林 - * @Date: 2021-04-11 10:19:55 - * @Desc: 默认主题 - */ -export default { - // 节点内边距 - paddingX: 15, - paddingY: 5, - // 图片显示的最大宽度 - imgMaxWidth: 100, - // 图片显示的最大高度 - imgMaxHeight: 100, - // icon的大小 - iconSize: 20, - // 连线的粗细 - lineWidth: 1, - // 连线的颜色 - lineColor: '#549688', - // 连线样式 - lineDasharray: 'none', - // 连线风格 - lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct) - // 概要连线的粗细 - generalizationLineWidth: 1, - // 概要连线的颜色 - generalizationLineColor: '#549688', - // 概要曲线距节点的距离 - generalizationLineMargin: 0, - // 概要节点距节点的距离 - generalizationNodeMargin: 20, - // 背景颜色 - backgroundColor: '#fafafa', - // 背景图片 - backgroundImage: 'none', - // 背景重复 - backgroundRepeat: 'no-repeat', - // 节点使用横线样式 - nodeUseLineStyle: false, - // 根节点样式 - root: { - shape: 'rectangle', - fillColor: '#549688', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#fff', - fontSize: 16, - fontWeight: 'bold', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: 'transparent', - borderWidth: 0, - borderDasharray: 'none', - borderRadius: 5, - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - }, - // 二级节点样式 - second: { - shape: 'rectangle', - marginX: 100, - marginY: 40, - fillColor: '#fff', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#565656', - fontSize: 16, - fontWeight: 'noraml', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: '#549688', - borderWidth: 1, - borderDasharray: 'none', - borderRadius: 5, - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - }, - // 三级及以下节点样式 - node: { - shape: 'rectangle', - marginX: 50, - marginY: 0, - fillColor: 'transparent', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#6a6d6c', - fontSize: 14, - fontWeight: 'noraml', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: 'transparent', - borderWidth: 0, - borderRadius: 5, - borderDasharray: 'none', - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - }, - // 概要节点样式 - generalization: { - shape: 'rectangle', - marginX: 100, - marginY: 40, - fillColor: '#fff', - fontFamily: '微软雅黑, Microsoft YaHei', - color: '#565656', - fontSize: 16, - fontWeight: 'noraml', - fontStyle: 'normal', - lineHeight: 1.5, - borderColor: '#549688', - borderWidth: 1, - borderDasharray: 'none', - borderRadius: 5, - textDecoration: 'none', - active: { - borderColor: 'rgb(57, 80, 96)', - borderWidth: 3, - borderDasharray: 'none' - } - } -} - -// 支持激活样式的属性 -// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小 -export const supportActiveStyle = [ - 'fillColor', - 'color', - 'fontWeight', - 'fontStyle', - 'borderColor', - 'borderWidth', - 'borderDasharray', - 'borderRadius', - 'textDecoration' -] - -export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth'] +// 默认主题 +export default { + // 节点内边距 + paddingX: 15, + paddingY: 5, + // 图片显示的最大宽度 + imgMaxWidth: 100, + // 图片显示的最大高度 + imgMaxHeight: 100, + // icon的大小 + iconSize: 20, + // 连线的粗细 + lineWidth: 1, + // 连线的颜色 + lineColor: '#549688', + // 连线样式 + lineDasharray: 'none', + // 连线风格 + lineStyle: 'straight', // 针对logicalStructure、mindMap两种结构。曲线(curve)、直线(straight)、直连(direct) + // 概要连线的粗细 + generalizationLineWidth: 1, + // 概要连线的颜色 + generalizationLineColor: '#549688', + // 概要曲线距节点的距离 + generalizationLineMargin: 0, + // 概要节点距节点的距离 + generalizationNodeMargin: 20, + // 背景颜色 + backgroundColor: '#fafafa', + // 背景图片 + backgroundImage: 'none', + // 背景重复 + backgroundRepeat: 'no-repeat', + // 节点使用横线样式 + nodeUseLineStyle: false, + // 根节点样式 + root: { + shape: 'rectangle', + fillColor: '#549688', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#fff', + fontSize: 16, + fontWeight: 'bold', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: 'transparent', + borderWidth: 0, + borderDasharray: 'none', + borderRadius: 5, + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + }, + // 二级节点样式 + second: { + shape: 'rectangle', + marginX: 100, + marginY: 40, + fillColor: '#fff', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#565656', + fontSize: 16, + fontWeight: 'noraml', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: '#549688', + borderWidth: 1, + borderDasharray: 'none', + borderRadius: 5, + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + }, + // 三级及以下节点样式 + node: { + shape: 'rectangle', + marginX: 50, + marginY: 0, + fillColor: 'transparent', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#6a6d6c', + fontSize: 14, + fontWeight: 'noraml', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: 'transparent', + borderWidth: 0, + borderRadius: 5, + borderDasharray: 'none', + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + }, + // 概要节点样式 + generalization: { + shape: 'rectangle', + marginX: 100, + marginY: 40, + fillColor: '#fff', + fontFamily: '微软雅黑, Microsoft YaHei', + color: '#565656', + fontSize: 16, + fontWeight: 'noraml', + fontStyle: 'normal', + lineHeight: 1.5, + borderColor: '#549688', + borderWidth: 1, + borderDasharray: 'none', + borderRadius: 5, + textDecoration: 'none', + active: { + borderColor: 'rgb(57, 80, 96)', + borderWidth: 3, + borderDasharray: 'none' + } + } +} + +// 支持激活样式的属性 +// 简单来说,会改变节点大小的都不支持在激活时设置,为了性能考虑,节点切换激活态时不会重新计算节点大小 +export const supportActiveStyle = [ + 'fillColor', + 'color', + 'fontWeight', + 'fontStyle', + 'borderColor', + 'borderWidth', + 'borderDasharray', + 'borderRadius', + 'textDecoration' +] + +export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth'] diff --git a/simple-mind-map/src/themes/earthYellow.js b/simple-mind-map/src/themes/earthYellow.js index e22998f3..9d375b4a 100644 --- a/simple-mind-map/src/themes/earthYellow.js +++ b/simple-mind-map/src/themes/earthYellow.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 泥土黄 - */ +// 泥土黄 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(191, 147, 115)', diff --git a/simple-mind-map/src/themes/freshGreen.js b/simple-mind-map/src/themes/freshGreen.js index 61634caa..ab11006e 100644 --- a/simple-mind-map/src/themes/freshGreen.js +++ b/simple-mind-map/src/themes/freshGreen.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 清新绿 - */ +// 清新绿 export default merge(defaultTheme, { // 连线的颜色 lineColor: '#333', diff --git a/simple-mind-map/src/themes/freshRed.js b/simple-mind-map/src/themes/freshRed.js index 7fb10406..3032f4d0 100644 --- a/simple-mind-map/src/themes/freshRed.js +++ b/simple-mind-map/src/themes/freshRed.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 清新红 - */ +// 清新红 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(191, 115, 115)', diff --git a/simple-mind-map/src/themes/gold.js b/simple-mind-map/src/themes/gold.js index 16df0763..538c3c67 100644 --- a/simple-mind-map/src/themes/gold.js +++ b/simple-mind-map/src/themes/gold.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 金色vip - */ +// 金色vip export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(51, 56, 62)', diff --git a/simple-mind-map/src/themes/greenLeaf.js b/simple-mind-map/src/themes/greenLeaf.js index 842f1424..d0c4f642 100644 --- a/simple-mind-map/src/themes/greenLeaf.js +++ b/simple-mind-map/src/themes/greenLeaf.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 绿叶 - */ +// 绿叶 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(40, 193, 84)', diff --git a/simple-mind-map/src/themes/minions.js b/simple-mind-map/src/themes/minions.js index 0c862f6f..09ba44fd 100644 --- a/simple-mind-map/src/themes/minions.js +++ b/simple-mind-map/src/themes/minions.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 小黄人 - */ +// 小黄人 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(51, 51, 51)', diff --git a/simple-mind-map/src/themes/mint.js b/simple-mind-map/src/themes/mint.js index 4be697e2..19e22104 100644 --- a/simple-mind-map/src/themes/mint.js +++ b/simple-mind-map/src/themes/mint.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 薄荷 - */ +// 薄荷 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(104, 204, 202)', diff --git a/simple-mind-map/src/themes/pinkGrape.js b/simple-mind-map/src/themes/pinkGrape.js index 46a1744a..ca9b1c11 100644 --- a/simple-mind-map/src/themes/pinkGrape.js +++ b/simple-mind-map/src/themes/pinkGrape.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 粉红葡萄 - */ +// 粉红葡萄 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(166, 101, 106)', diff --git a/simple-mind-map/src/themes/romanticPurple.js b/simple-mind-map/src/themes/romanticPurple.js index 981ff9d0..fc27a18e 100644 --- a/simple-mind-map/src/themes/romanticPurple.js +++ b/simple-mind-map/src/themes/romanticPurple.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 浪漫紫 - */ +// 浪漫紫 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(123, 115, 191)', diff --git a/simple-mind-map/src/themes/skyGreen.js b/simple-mind-map/src/themes/skyGreen.js index 57dcfcdb..d0a92f29 100644 --- a/simple-mind-map/src/themes/skyGreen.js +++ b/simple-mind-map/src/themes/skyGreen.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 天清绿 - */ +// 天清绿 export default merge(defaultTheme, { // 连线的颜色 lineColor: '#fff', diff --git a/simple-mind-map/src/themes/vitalityOrange.js b/simple-mind-map/src/themes/vitalityOrange.js index 30ca2487..f3f95c0c 100644 --- a/simple-mind-map/src/themes/vitalityOrange.js +++ b/simple-mind-map/src/themes/vitalityOrange.js @@ -1,11 +1,7 @@ import defaultTheme from './default' import merge from 'deepmerge' -/** - * @Author: 王林 - * @Date: 2021-04-11 15:22:18 - * @Desc: 活力橙 - */ +// 活力橙 export default merge(defaultTheme, { // 连线的颜色 lineColor: 'rgb(254, 146, 0)', diff --git a/simple-mind-map/src/utils/constant.js b/simple-mind-map/src/utils/constant.js index b3c84e07..66b71ec2 100644 --- a/simple-mind-map/src/utils/constant.js +++ b/simple-mind-map/src/utils/constant.js @@ -1,8 +1,4 @@ -/** - * @Author: 王林 - * @Date: 2021-06-24 21:42:07 - * @Desc: 标签颜色列表 - */ +// 标签颜色列表 export const tagColorList = [ { color: 'rgb(77, 65, 0)', @@ -26,12 +22,7 @@ export const tagColorList = [ } ] -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-13 15:56:28 - * @Desc: 布局结构列表 - */ +// 布局结构列表 export const layoutList = [ { name: '逻辑结构图', @@ -61,11 +52,7 @@ export const layoutValueList = [ 'organizationStructure' ] -/** - * @Author: 王林 - * @Date: 2021-06-24 22:58:42 - * @Desc: 主题列表 - */ +// 主题列表 export const themeList = [ { name: '默认', diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index 586f37d4..819b09a3 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -1,9 +1,4 @@ -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:13:17 - * @Desc: 深度优先遍历树 - */ +// 深度优先遍历树 export const walk = ( root, parent, @@ -34,12 +29,7 @@ export const walk = ( afterCallback && afterCallback(root, parent, isRoot, layerIndex, index) } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 18:47:20 - * @Desc: 广度优先遍历树 - */ +// 广度优先遍历树 export const bfsWalk = (root, callback) => { callback(root) let stack = [root] @@ -60,12 +50,7 @@ export const bfsWalk = (root, callback) => { } } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 10:44:54 - * @Desc: 缩放图片尺寸 - */ +// 缩放图片尺寸 export const resizeImgSize = (width, height, maxWidth, maxHeight) => { let nRatio = width / height let arr = [] @@ -98,12 +83,7 @@ export const resizeImgSize = (width, height, maxWidth, maxHeight) => { return arr } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 10:18:42 - * @Desc: 缩放图片 - */ +// 缩放图片 export const resizeImg = (imgUrl, maxWidth, maxHeight) => { return new Promise((resolve, reject) => { let img = new Image() @@ -123,11 +103,7 @@ export const resizeImg = (imgUrl, maxWidth, maxHeight) => { }) } -/** - * @Author: 王林 - * @Date: 2021-05-04 12:26:56 - * @Desc: 从头html结构字符串里获取带换行符的字符串 - */ +// 从头html结构字符串里获取带换行符的字符串 export const getStrWithBrFromHtml = str => { str = str.replace(/
/gim, '\n') let el = document.createElement('div') @@ -136,11 +112,7 @@ export const getStrWithBrFromHtml = str => { return str } -/** - * @Author: 王林 - * @Date: 2021-05-04 14:45:39 - * @Desc: 极简的深拷贝 - */ +// 极简的深拷贝 export const simpleDeepClone = data => { try { return JSON.parse(JSON.stringify(data)) @@ -149,11 +121,7 @@ export const simpleDeepClone = data => { } } -/** - * @Author: 王林 - * @Date: 2021-05-04 14:40:11 - * @Desc: 复制渲染树数据 - */ +// 复制渲染树数据 export const copyRenderTree = (tree, root) => { tree.data = simpleDeepClone(root.data) tree.children = [] @@ -165,11 +133,7 @@ export const copyRenderTree = (tree, root) => { return tree } -/** - * @Author: 王林 - * @Date: 2021-05-04 14:40:11 - * @Desc: 复制节点树数据 - */ +// 复制节点树数据 export const copyNodeTree = (tree, root, removeActiveState = false) => { tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data) if (removeActiveState) { @@ -192,11 +156,7 @@ export const copyNodeTree = (tree, root, removeActiveState = false) => { return tree } -/** - * @Author: 王林 - * @Date: 2021-07-04 09:08:43 - * @Desc: 图片转成dataURL - */ +// 图片转成dataURL export const imgToDataUrl = src => { return new Promise((resolve, reject) => { const img = new Image() @@ -222,11 +182,7 @@ export const imgToDataUrl = src => { }) } -/** - * @Author: 王林 - * @Date: 2021-07-04 16:20:06 - * @Desc: 下载文件 - */ +// 下载文件 export const downloadFile = (file, fileName) => { let a = document.createElement('a') a.href = file @@ -234,11 +190,7 @@ export const downloadFile = (file, fileName) => { a.click() } -/** - * @Author: 王林 - * @Date: 2021-07-11 10:36:47 - * @Desc: 节流函数 - */ +// 节流函数 export const throttle = (fn, time = 300, ctx) => { let timer = null return () => { @@ -252,12 +204,7 @@ export const throttle = (fn, time = 300, ctx) => { } } -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-07-12 10:27:36 - * @Desc: 异步执行任务队列 - */ +// 异步执行任务队列 export const asyncRun = (taskList, callback = () => {}) => { let index = 0 let len = taskList.length