diff --git a/.gitignore b/.gitignore index 05223c9f..25c8fdba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -oss.js +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index f5c09125..a201eff4 100644 --- a/README.md +++ b/README.md @@ -1 +1,36 @@ -开发中... \ No newline at end of file +# web思维导图的简单实现 + +开发中... + +## 目录介绍 + +1.simple-mind-map + +思维导图工具包。 + +2.web + +使用`simple-mind-map`工具包搭建的在线思维导图。 + +## 开发 + +本地开发 + +```bash +git clone https://github.com/wanglin2/mind-map.git +cd simple-mind-map +npm i +npm link +cd .. +cd web +npm i +npm link simple-mind-map +npm run serve +``` + +打包 + +```bash +cd web +npm run build +``` \ No newline at end of file diff --git a/docs/assets/swdt.jpg b/docs/assets/swdt.jpg new file mode 100644 index 00000000..1b97cafe Binary files /dev/null and b/docs/assets/swdt.jpg differ diff --git a/docs/web思维导图实现的技术点分析.md b/docs/web思维导图实现的技术点分析.md new file mode 100644 index 00000000..63890a62 --- /dev/null +++ b/docs/web思维导图实现的技术点分析.md @@ -0,0 +1,93 @@ +![](./assets/swdt.jpg) + +# 简介 + +思维导图是一种常见的表达发散性思维的有效工具,市面上有非常多的工具可以用来画思维导图,百度一下,整页都是广告可供选择,此外也有一些可以用来帮助快速实现的`JavaScript`类库,如:[jsMind](https://github.com/hizzgdev/jsmind)、[KityMinder](https://github.com/fex-team/kityminder)。 + +本文会介绍如何从头实现一个简易的思维导图。 + + + +# 技术选型 + +这种图形类的绘制一般有两种选择,`svg`与`canvas`,因为思维导图主要是节点与线的连接,使用与`html`比较接近的`svg`比较好操作,`svg`类库也有挺多,在试用了[svgjs](https://svgjs.dev/docs/3.0/)和[snap](http://snapsvg.io/)后,有些需求在`snap`里没有找到对应的方法,所以最终选择了`svgjs`,视图库使用的是`vue2.x`全家桶。 + + + +# 数据结构 + +这里主要指每个节点的数据结构,大概需要包含是否是根节点、节点层级、节点内容(包括文本、图片、图标等固定格式)、节点展开状态、子节点、父节点等等,此外还包括该节点的特定样式,用来覆盖主题的默认样式: + +```js + +``` + +每次操作都会修改这份配置数据,然后整体刷新,有点数据驱动的意思,好处很明显,只用维护数据就行了,不用陷入对视图的操作。 + + + +# 逻辑结构图 + +思维导图常见的有几种变种,我们先看最基础的【逻辑结构图】如何布局,其他的可以在末尾小节查看。 + + + +## 节点定位 + + + +## 节点连线 + + + +# 支持图片、图标 + + + +# 展开收缩 + + + +# 文字编辑 + +# 拖动、放大缩小 + +# 主题 + +# 节点样式编辑 + +# 快捷键 + +快捷键就是监听了到特定的按键来执行特定的操作,包含单个按键和组合键,我们可以使用一个对象来保存快捷键和对应的命令,`key`代表按键,`value`代表要执行的命令,比如: + +```js +const shortcutKeys = { + 'enter': 'addSiblingNode', + 'ctrl+b': 'bold' +} +``` + +包含两种类型,单个按键、以`+`拼接的组合键,接下来只要监听`keydown`事件来检查即可,首先要说明的是组合键一般指的是`ctrl`、`alt`、`shift` + + + +# 实现过渡效果 + +# 回退 + +# 导入导出、其他格式 + +https://github.com/canvg/canvg + +https://github.com/fex-team/kityminder/tree/dev/src/protocol + +https://github.com/fex-team/kityminder/tree/dev/native-support + +json、freemind、xmind + +png、svg + +# 其他几种变种结构 + +逻辑结构图、鱼骨图、思维导图、组织结构图、目录组织图 + diff --git a/src/package/mind-map/.DS_Store b/simple-mind-map/.DS_Store similarity index 100% rename from src/package/mind-map/.DS_Store rename to simple-mind-map/.DS_Store diff --git a/src/package/mind-map/example/exampleData.js b/simple-mind-map/example/exampleData.js similarity index 100% rename from src/package/mind-map/example/exampleData.js rename to simple-mind-map/example/exampleData.js diff --git a/src/package/mind-map/index.js b/simple-mind-map/index.js similarity index 90% rename from src/package/mind-map/index.js rename to simple-mind-map/index.js index 90fc234c..c774c823 100644 --- a/src/package/mind-map/index.js +++ b/simple-mind-map/index.js @@ -1,174 +1,185 @@ -import View from './src/View' -import Event from './src/Event' -import Render from './src/Render' -import merge from 'deepmerge' -import theme from './src/themes' -import Style from './src/Style' -import KeyCommand from './src/KeyCommand' -import Command from './src/Command'; -import { SVG } from '@svgdotjs/svg.js' - -const defaultOpt = { - // 布局 - layout: 'logicalStructure', - // 放大缩小的增量比例,即step = scaleRatio * width|height - scaleRatio: 0.1, - // 主题 - theme: 'default',// 内置主题:default(默认主题) - // 主题配置,会和所选择的主题进行合并 - themeConfig: {} -} - -/** - * 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 = merge(defaultOpt, opt) - // 容器元素 - this.el = this.opt.el - let { - width, - height - } = this.el.getBoundingClientRect() - // 画布宽高 - this.width = width - this.height = height - // 画笔 - this.draw = SVG().addTo(this.el).size(width, height) - // 节点id - this.uid = 0 - - // 主题 - this.initTheme() - - // 事件类 - this.event = new Event({ - mindMap: this - }) - - // 按键类 - this.keyCommand = new KeyCommand({ - mindMap: this - }) - - // 命令类 - this.command = new Command({ - mindMap: this - }) - - // 渲染类 - this.renderer = new Render({ - mindMap: this - }) - - // 视图操作类 - this.view = new View({ - mindMap: this, - draw: this.draw - }) - - this.render() - setTimeout(() => { - this.command.addHistory() - }, 0); - } - - /** - * @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(this.opt.theme && theme[this.opt.theme] ? theme[this.opt.theme] : theme.default, this.opt.themeConfig) - Style.setBackgroundStyle(this.el, this.themeConfig) - } - - /** - * @Author: 王林 - * @Date: 2021-05-05 13:52:08 - * @Desc: 设置主题 - */ - setTheme(theme) { - this.opt.theme = theme - this.render() - } - - /** - * @Author: 王林 - * @Date: 2021-05-05 13:50:17 - * @Desc: 设置主题配置 - */ - setThemeConfig(config) { - this.opt.themeConfig = config - this.render() - } - - /** - * @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-04-06 18:47:29 - * @Desc: 渲染节点 - */ - render() { - this.draw.clear() - this.initTheme() - this.renderer.render() - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:01:00 - * @Desc: 执行命令 - */ - execCommand(...args) { - this.command.exec(...args) - } -} - +import View from './src/View' +import Event from './src/Event' +import Render from './src/Render' +import merge from 'deepmerge' +import theme from './src/themes' +import Style from './src/Style' +import KeyCommand from './src/KeyCommand' +import Command from './src/Command'; +import { + SVG +} from '@svgdotjs/svg.js' + +// 默认选项 +const defaultOpt = { + // 布局 + layout: 'logicalStructure', + // 主题 + theme: 'default', // 内置主题:default(默认主题) + // 主题配置,会和所选择的主题进行合并 + themeConfig: {}, + // 放大缩小的增量比例,即step = scaleRatio * width|height + scaleRatio: 0.1 +} + +/** + * 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 = merge(defaultOpt, opt) + + // 容器元素 + this.el = this.opt.el + let { + width, + height + } = this.el.getBoundingClientRect() + + // 画布宽高 + this.width = width + this.height = height + + // 画笔 + this.draw = SVG().addTo(this.el).size(width, height) + + // 节点id + this.uid = 0 + + // 初始化主题 + this.initTheme() + + // 事件类 + this.event = new Event({ + mindMap: this + }) + + // 按键类 + this.keyCommand = new KeyCommand({ + mindMap: this + }) + + // 命令类 + this.command = new Command({ + mindMap: this + }) + + // 渲染类 + this.renderer = new Render({ + mindMap: this + }) + + // 视图操作类 + this.view = new View({ + mindMap: this, + draw: this.draw + }) + + // 初始渲染 + this.renderer.render() + setTimeout(() => { + this.command.addHistory() + }, 0); + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 18:47:29 + * @Desc: 渲染 + */ + render() { + this.draw.clear() + this.initTheme() + this.renderer.render() + } + + /** + * @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(this.opt.theme && theme[this.opt.theme] ? theme[this.opt.theme] : theme.default, this.opt.themeConfig) + // 设置背景样式 + Style.setBackgroundStyle(this.el, this.themeConfig) + } + + /** + * @Author: 王林 + * @Date: 2021-05-05 13:52:08 + * @Desc: 设置主题 + */ + setTheme(theme) { + this.opt.theme = theme + this.render() + } + + /** + * @Author: 王林 + * @Date: 2021-05-05 13:50:17 + * @Desc: 设置主题配置 + */ + setThemeConfig(config) { + this.opt.themeConfig = config + this.render() + } + + /** + * @Author: 王林 + * @Date: 2021-05-05 14:01:29 + * @Desc: 获取某个主题配置值 + */ + getThemeConfig(prop) { + return prop === undefined ? this.themeConfig : this.themeConfig[prop] + } + + /** + * @Author: 王林 + * @Date: 2021-05-04 13:01:00 + * @Desc: 执行命令 + */ + execCommand(...args) { + this.command.exec(...args) + } +} + export default MindMap \ No newline at end of file diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json new file mode 100644 index 00000000..f645920c --- /dev/null +++ b/simple-mind-map/package.json @@ -0,0 +1,11 @@ +{ + "name": "simple-mind-map", + "version": "0.1.0", + "private": true, + "scripts": {}, + "dependencies": { + "@svgdotjs/svg.js": "^3.0.16", + "deepmerge": "^1.5.2", + "eventemitter3": "^4.0.7" + } +} diff --git a/src/package/mind-map/src/Command.js b/simple-mind-map/src/Command.js similarity index 96% rename from src/package/mind-map/src/Command.js rename to simple-mind-map/src/Command.js index aa023b0c..bec96595 100644 --- a/src/package/mind-map/src/Command.js +++ b/simple-mind-map/src/Command.js @@ -1,4 +1,4 @@ -import { copyRenderTree, simpleDeepClone } from './Utils'; +import { copyRenderTree, simpleDeepClone } from './utils'; /** * @Author: 王林 diff --git a/src/package/mind-map/src/Event.js b/simple-mind-map/src/Event.js similarity index 96% rename from src/package/mind-map/src/Event.js rename to simple-mind-map/src/Event.js index dab2c563..3f3ae06b 100644 --- a/src/package/mind-map/src/Event.js +++ b/simple-mind-map/src/Event.js @@ -1,149 +1,149 @@ -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 - this.mindMap = opt.mindMap - this.isMousedown = false - this.mousedownPos = { - x: 0, - y: 0 - } - this.mousemovePos = { - x: 0, - y: 0 - } - this.mousemoveOffset = { - x: 0, - y: 0 - } - this.bindFn() - 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) - this.onMousemove = this.onMousemove.bind(this) - this.onMouseup = this.onMouseup.bind(this) - this.onMousewheel = this.onMousewheel.bind(this) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 14:53:43 - * @Desc: 绑定事件 - */ - bind() { - this.mindMap.draw.on('click', this.onDrawClick) - this.mindMap.el.addEventListener('mousedown', this.onMousedown) - window.addEventListener('mousemove', this.onMousemove) - window.addEventListener('mouseup', this.onMouseup) - this.mindMap.el.addEventListener('mousewheel', this.onMousewheel) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:40:51 - * @Desc: 解绑事件 - */ - unbind() { - this.mindMap.el.removeEventListener('mousedown', this.onMousedown) - window.removeEventListener('mousemove', this.onMousemove) - window.removeEventListener('mouseup', this.onMouseup) - this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel) - } - - /** - * @Author: 王林 - * @Date: 2021-04-24 13:19:39 - * @Desc: 画布的单击事件 - */ - onDrawClick(e) { - this.emit('draw_click', e) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:17:35 - * @Desc: 鼠标按下事件 - */ - onMousedown(e) { - e.preventDefault() - this.isMousedown = true - this.mousedownPos.x = e.clientX - this.mousedownPos.y = e.clientY - 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 - this.mousemovePos.y = e.clientY - this.mousemoveOffset.x = e.clientX - this.mousedownPos.x - this.mousemoveOffset.y = e.clientY - this.mousedownPos.y - this.emit('mousemove', e, this) - if (this.isMousedown) { - this.emit('drag', e, this) - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:18:57 - * @Desc: 鼠标松开事件 - */ - onMouseup(e) { - this.isMousedown = false - this.emit('mouseup', e, this) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:46:27 - * @Desc: 鼠标滚动 - */ - onMousewheel(e) { - e.stopPropagation() - e.preventDefault() - let dir - if (e.wheelDeltaY > 0) { - dir = 'up' - } else { - dir = 'down' - } - this.emit('mousewheel', e, dir, this) - } -} - +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 + this.mindMap = opt.mindMap + this.isMousedown = false + this.mousedownPos = { + x: 0, + y: 0 + } + this.mousemovePos = { + x: 0, + y: 0 + } + this.mousemoveOffset = { + x: 0, + y: 0 + } + this.bindFn() + 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) + this.onMousemove = this.onMousemove.bind(this) + this.onMouseup = this.onMouseup.bind(this) + this.onMousewheel = this.onMousewheel.bind(this) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 14:53:43 + * @Desc: 绑定事件 + */ + bind() { + this.mindMap.draw.on('click', this.onDrawClick) + this.mindMap.el.addEventListener('mousedown', this.onMousedown) + window.addEventListener('mousemove', this.onMousemove) + window.addEventListener('mouseup', this.onMouseup) + this.mindMap.el.addEventListener('mousewheel', this.onMousewheel) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 15:40:51 + * @Desc: 解绑事件 + */ + unbind() { + this.mindMap.el.removeEventListener('mousedown', this.onMousedown) + window.removeEventListener('mousemove', this.onMousemove) + window.removeEventListener('mouseup', this.onMouseup) + this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel) + } + + /** + * @Author: 王林 + * @Date: 2021-04-24 13:19:39 + * @Desc: 画布的单击事件 + */ + onDrawClick(e) { + this.emit('draw_click', e) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 15:17:35 + * @Desc: 鼠标按下事件 + */ + onMousedown(e) { + e.preventDefault() + this.isMousedown = true + this.mousedownPos.x = e.clientX + this.mousedownPos.y = e.clientY + 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 + this.mousemovePos.y = e.clientY + this.mousemoveOffset.x = e.clientX - this.mousedownPos.x + this.mousemoveOffset.y = e.clientY - this.mousedownPos.y + this.emit('mousemove', e, this) + if (this.isMousedown) { + this.emit('drag', e, this) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 15:18:57 + * @Desc: 鼠标松开事件 + */ + onMouseup(e) { + this.isMousedown = false + this.emit('mouseup', e, this) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 15:46:27 + * @Desc: 鼠标滚动 + */ + onMousewheel(e) { + e.stopPropagation() + e.preventDefault() + let dir + if (e.wheelDeltaY > 0) { + dir = 'up' + } else { + dir = 'down' + } + this.emit('mousewheel', e, dir, this) + } +} + export default Event \ No newline at end of file diff --git a/src/package/mind-map/src/KeyCommand.js b/simple-mind-map/src/KeyCommand.js similarity index 100% rename from src/package/mind-map/src/KeyCommand.js rename to simple-mind-map/src/KeyCommand.js diff --git a/src/package/mind-map/src/Node.js b/simple-mind-map/src/Node.js similarity index 90% rename from src/package/mind-map/src/Node.js rename to simple-mind-map/src/Node.js index 38f1bedd..15c8d263 100644 --- a/src/package/mind-map/src/Node.js +++ b/simple-mind-map/src/Node.js @@ -1,397 +1,381 @@ -import Style from './Style'; -import { - resizeImgSize -} from './Utils' -import { - Image, - Text, - SVG, - Circle, - Element -} from '@svgdotjs/svg.js' -import btnsSvg from './svg/btns'; - -/** - * 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.originData = opt.originData - // 原始数据里的数据部分 - this.data = opt.data - // id - this.uid = opt.uid - // 控制实例 - this.mindMap = opt.mindMap - // 渲染实例 - this.renderer = opt.renderer - // 主题配置 - this.themeConfig = this.mindMap.themeConfig - // 样式实例 - this.style = new Style(this, this.themeConfig) - // 渲染器 - this.draw = opt.draw || null - // 是否是根节点 - this.isRoot = opt.isRoot === undefined ? false : opt.isRoot - // 是否激活 - this.isActive = opt.isActive === undefined ? false : opt.isActive - // 是否展开 - this.expand = opt.expand === undefined ? true : opt.expand - // 节点层级 - 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.parent = opt.parent || null - // 子节点 - this.children = opt.children || [] - // 全部子节点所占的高度之和 - this.childrenAreaHeight = opt.childrenAreaHeight || 0 - // 文本节点 - this.textNode = null - // 其他数据 - Object.keys(opt.data).forEach((key) => { - this[key] = opt.data[key] - }) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 15:55:04 - * @Desc: 添加子节点 - */ - addChildren(node) { - this.children.push(node) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 09:46:23 - * @Desc: 刷新节点的宽高 - */ - refreshSize() { - let { - width, - height - } = this.getNodeRect() - this.width = width - this.height = height - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:52:17 - * @Desc: 计算节点尺寸信息 - */ - getNodeRect() { - let width = this.themeConfig.paddingX * 2 - let height = this.themeConfig.paddingY * 2 - let maxWidth = 0 - if (this.img) { - let img = this.createImgNode() - if (img.width > maxWidth) { - maxWidth = img.width - } - height += img.height - } - if (this.icon && this.text) { - let icon = this.createIconNode() - let text = this.createTextNode() - if (icon.width + text.width > maxWidth) { - maxWidth = icon.width + text.width - } - height += Math.max(text.height, icon.height) - } else if (this.text) { - let text = this.createTextNode() - if (text.width > maxWidth) { - maxWidth = text.width - } - height += text.height - } else if (this.icon) { - let icon = this.createIconNode() - if (icon.width > maxWidth) { - maxWidth = icon.width - } - height += icon.height - } - return { - width: width + maxWidth, - height - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:06:17 - * @Desc: 创建图片节点 - */ - createImgNode() { - if (!this.img) { - return - } - let imgSize = this.getImgShowSize() - return { - node: new Image().load(this.img).size(...imgSize), - width: imgSize[0], - height: imgSize[1] - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:08:56 - * @Desc: 创建文本节点 - */ - createTextNode() { - if (!this.text) { - return - } - let node = this.draw.text(this.text) - this.style.text(node) - let { - width, - height - } = node.bbox() - let cloneNode = node.clone() - node.remove() - return { - node: cloneNode, - width, - height - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 14:10:48 - * @Desc: 创建icon节点 - */ - createIconNode() { - if (!this.icon) { - return - } - let node = SVG('').size(this.themeConfig.iconSize, this.themeConfig.iconSize) - return { - node, - width: this.themeConfig.iconSize, - height: this.themeConfig.iconSize - } - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 11:10:11 - * @Desc: 创建内容节点 - */ - createNode() { - let { - left, - top, - width, - height - } = this - let paddingY = this.themeConfig.paddingY - // 创建组 - let group = this.draw.group() - // 节点矩形 - let _rectNode = group.rect(width, height).x(left).y(top) - this.style.rect(_rectNode) - // 内容节点 - let imgNode = this.createImgNode() - let iconNode = this.createIconNode() - let textNode = this.createTextNode() - let imgHeight = imgNode ? imgNode.height : 0 - // 图片 - if (imgNode) { - group.add(imgNode.node) - imgNode.node.cx(left + width / 2).y(top + paddingY) - } - // icon - if (iconNode) { - group.add(iconNode.node) - iconNode.node.x(left + width / 2).y(top + paddingY + imgHeight + (textNode && textNode.height > iconNode.height ? (textNode.height - iconNode.height) / 2 : 0)).dx(textNode ? -textNode.width / 2 - iconNode.width / 2 : 0) - } - // 文字 - if (textNode) { - this.textNode = textNode - group.add(textNode.node) - textNode.node.cx(left + width / 2).y(top + paddingY + imgHeight).dx(iconNode ? iconNode.width / 2 : 0) - } - // 单击事件 - group.click((e) => { - e.stopPropagation() - if (this.isActive) { - return; - } - this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) - this.renderer.clearActive() - this.isActive = true - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - isActive: this.isActive - }) - this.renderer.activeNodeList.push(this) - this.mindMap.render() - this.mindMap.emit('node_active', this, this.renderer.activeNodeList) - }) - // 双击事件 - group.dblclick(() => { - this.showTextEditBox() - }) - return group - } - - /** - * @Author: 王林 - * @Date: 2021-04-13 22:15:56 - * @Desc: 显示文本编辑框 - */ - showTextEditBox() { - if (!this.text) { - return; - } - this.renderer.showEditTextBox(this, this.textNode.node.node.getBoundingClientRect()) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 13:55:58 - * @Desc: 渲染 - */ - render() { - // 连线 - this.drawLine() - // 按钮 - this.drawBtn() - // 节点 - this.draw.add(this.createNode()) - // 子节点 - if (this.children && this.children.length && this.expand) { - this.children.forEach((child) => { - child.render() - }) - } - } - - /** - * @Author: 王林 - * @Date: 2021-04-10 22:01:53 - * @Desc: 连线 - */ - drawLine() { - if (!this.expand) { - return; - } - let lines = this.renderer.layout.drawLine(this) - lines.forEach((line) => { - this.style.line(line) - }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-11 19:47:01 - * @Desc: 展开收缩按钮 - */ - drawBtn() { - if (this.children.length <= 0 || this.isRoot) { - return; - } - let g = this.draw.group() - let iconSvg - if (this.expand) { - iconSvg = btnsSvg.close - } else { - iconSvg = btnsSvg.open - } - let node = SVG(iconSvg).size(20, 20) - let fillNode = new Circle().size(20) - this.renderer.layout.drawIcon(this, [node, fillNode]) - node.dx(0).dy(-10) - fillNode.dx(0).dy(-10) - this.style.iconBtn(node, fillNode) - g.mouseover(() => { - g.css({ cursor: 'pointer' }) - }) - g.mouseout(() => { - g.css({ cursor: 'auto' }) - }) - g.click(() => { - this.expand = !this.expand - // 需要反映到实际数据上 - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - expand: this.expand - }) - this.mindMap.render() - this.mindMap.emit('expand_btn_click', this) - }) - g.add(fillNode) - g.add(node) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-09 10:12:51 - * @Desc: 获取图片显示宽高 - */ - getImgShowSize() { - return resizeImgSize(this.imgWidth, this.imgHeight, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight) - } - - /** - * @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 - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 22:18:07 - * @Desc: 修改某个样式 - */ - setStyle(prop, value, isActive) { - if (isActive) { - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - activeStyle: { - ...(this.data.activeStyle || {}), - [prop]: value - } - }) - } else { - this.mindMap.execCommand('UPDATE_NODE_DATA', this, { - [prop]: value - }) - } - this.mindMap.render() - } -} - +import Style from './Style' +import { + resizeImgSize +} from './utils' +import { + Image, + Text, + SVG, + Circle, + Element +} from '@svgdotjs/svg.js' +import btnsSvg from './svg/btns' + +/** + * 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.data = 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.isRoot = opt.isRoot === undefined ? false : opt.isRoot + // 是否激活 + this.isActive = opt.isActive === undefined ? false : opt.isActive + // 是否展开 + this.expand = opt.expand === undefined ? true : opt.expand + // 节点层级 + 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.parent = opt.parent || null + // 子节点 + this.children = opt.children || [] + // 文本节点 + this.textNode = null + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 15:55:04 + * @Desc: 添加子节点 + */ + addChildren(node) { + this.children.push(node) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 09:46:23 + * @Desc: 刷新节点的宽高 + */ + refreshSize() { + let { + width, + height + } = this.getNodeRect() + this.width = width + this.height = height + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:52:17 + * @Desc: 计算节点尺寸信息 + */ + getNodeRect() { + let width = this.themeConfig.paddingX * 2 + let height = this.themeConfig.paddingY * 2 + let maxWidth = 0 + if (this.img) { + let img = this.createImgNode() + if (img.width > maxWidth) { + maxWidth = img.width + } + height += img.height + } + if (this.icon && this.text) { + let icon = this.createIconNode() + let text = this.createTextNode() + if (icon.width + text.width > maxWidth) { + maxWidth = icon.width + text.width + } + height += Math.max(text.height, icon.height) + } else if (this.text) { + let text = this.createTextNode() + if (text.width > maxWidth) { + maxWidth = text.width + } + height += text.height + } else if (this.icon) { + let icon = this.createIconNode() + if (icon.width > maxWidth) { + maxWidth = icon.width + } + height += icon.height + } + return { + width: width + maxWidth, + height + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 14:06:17 + * @Desc: 创建图片节点 + */ + createImgNode() { + if (!this.img) { + return + } + let imgSize = this.getImgShowSize() + return { + node: new Image().load(this.img).size(...imgSize), + width: imgSize[0], + height: imgSize[1] + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 14:08:56 + * @Desc: 创建文本节点 + */ + createTextNode() { + if (!this.text) { + return + } + let node = this.draw.text(this.text) + this.style.text(node) + let { + width, + height + } = node.bbox() + let cloneNode = node.clone() + node.remove() + return { + node: cloneNode, + width, + height + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 14:10:48 + * @Desc: 创建icon节点 + */ + createIconNode() { + if (!this.icon) { + return + } + let node = SVG('').size(this.themeConfig.iconSize, this.themeConfig.iconSize) + return { + node, + width: this.themeConfig.iconSize, + height: this.themeConfig.iconSize + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 11:10:11 + * @Desc: 创建内容节点 + */ + createNode() { + let { + left, + top, + width, + height + } = this + let paddingY = this.themeConfig.paddingY + // 创建组 + let group = this.draw.group() + // 节点矩形 + let _rectNode = group.rect(width, height).x(left).y(top) + this.style.rect(_rectNode) + // 内容节点 + let imgNode = this.createImgNode() + let iconNode = this.createIconNode() + let textNode = this.createTextNode() + let imgHeight = imgNode ? imgNode.height : 0 + // 图片 + if (imgNode) { + group.add(imgNode.node) + imgNode.node.cx(left + width / 2).y(top + paddingY) + } + // icon + if (iconNode) { + group.add(iconNode.node) + iconNode.node.x(left + width / 2).y(top + paddingY + imgHeight + (textNode && textNode.height > iconNode.height ? (textNode.height - iconNode.height) / 2 : 0)).dx(textNode ? -textNode.width / 2 - iconNode.width / 2 : 0) + } + // 文字 + if (textNode) { + this.textNode = textNode + group.add(textNode.node) + textNode.node.cx(left + width / 2).y(top + paddingY + imgHeight).dx(iconNode ? iconNode.width / 2 : 0) + } + // 单击事件 + group.click((e) => { + e.stopPropagation() + if (this.isActive) { + return; + } + this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList) + this.renderer.clearActive() + this.isActive = true + this.mindMap.execCommand('UPDATE_NODE_DATA', this, { + isActive: this.isActive + }) + this.renderer.activeNodeList.push(this) + this.mindMap.render() + this.mindMap.emit('node_active', this, this.renderer.activeNodeList) + }) + // 双击事件 + group.dblclick(() => { + this.mindMap.emit('node_dblclick', this) + }) + return group + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 13:55:58 + * @Desc: 渲染 + */ + render() { + // 连线 + this.drawLine() + // 按钮 + this.drawBtn() + // 节点 + this.draw.add(this.createNode()) + // 子节点 + if (this.children && this.children.length && this.expand) { + this.children.forEach((child) => { + child.render() + }) + } + } + + /** + * @Author: 王林 + * @Date: 2021-04-10 22:01:53 + * @Desc: 连线 + */ + drawLine() { + if (!this.expand) { + return; + } + let lines = this.renderer.layout.drawLine(this) + lines.forEach((line) => { + this.style.line(line) + }) + } + + /** + * @Author: 王林 + * @Date: 2021-04-11 19:47:01 + * @Desc: 展开收缩按钮 + */ + drawBtn() { + if (this.children.length <= 0 || this.isRoot) { + return; + } + let g = this.draw.group() + let iconSvg + if (this.expand) { + iconSvg = btnsSvg.close + } else { + iconSvg = btnsSvg.open + } + let node = SVG(iconSvg).size(20, 20) + let fillNode = new Circle().size(20) + this.renderer.layout.drawIcon(this, [node, fillNode]) + node.dx(0).dy(-10) + fillNode.dx(0).dy(-10) + this.style.iconBtn(node, fillNode) + g.mouseover(() => { + g.css({ + cursor: 'pointer' + }) + }) + g.mouseout(() => { + g.css({ + cursor: 'auto' + }) + }) + g.click(() => { + this.expand = !this.expand + // 需要反映到实际数据上 + this.mindMap.execCommand('UPDATE_NODE_DATA', this, { + expand: this.expand + }) + this.mindMap.render() + this.mindMap.emit('expand_btn_click', this) + }) + g.add(fillNode) + g.add(node) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-09 10:12:51 + * @Desc: 获取图片显示宽高 + */ + getImgShowSize() { + return resizeImgSize(this.imgWidth, this.imgHeight, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight) + } + + /** + * @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 + } + + /** + * @Author: 王林 + * @Date: 2021-05-04 22:18:07 + * @Desc: 修改某个样式 + */ + setStyle(prop, value, isActive) { + if (isActive) { + this.mindMap.execCommand('UPDATE_NODE_DATA', this, { + activeStyle: { + ...(this.data.activeStyle || {}), + [prop]: value + } + }) + } else { + this.mindMap.execCommand('UPDATE_NODE_DATA', this, { + [prop]: value + }) + } + this.mindMap.render() + } +} + export default Node \ No newline at end of file diff --git a/src/package/mind-map/src/Render.js b/simple-mind-map/src/Render.js similarity index 56% rename from src/package/mind-map/src/Render.js rename to simple-mind-map/src/Render.js index afd75d57..c27eeb2b 100644 --- a/src/package/mind-map/src/Render.js +++ b/simple-mind-map/src/Render.js @@ -1,256 +1,175 @@ -import merge from 'deepmerge' -import LogicalStructure from './layouts/LogicalStructure' -import { getStrWithBrFromHtml } from './Utils' - -// 布局列表 -const layouts = { - logicalStructure: LogicalStructure -} - -/** - * 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.activeNodeList = [] - // 根节点 - this.root = null - // 文本编辑框 - this.textEditNode = null - // 文本编辑框是否显示 - this.showTextEdit = false - // 布局 - this.layout = new (layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)({ - mindMap: this.mindMap, - renderer: this, - renderTree: this.renderTree, - themeConfig: this.mindMap.themeConfig, - draw: this.mindMap.draw - }) - // 绑定事件 - this.bindEvent() - // 注册命令 - this.registerCommands() - } - - /** - * @Author: 王林 - * @Date: 2021-04-24 13:27:04 - * @Desc: 事件 - */ - bindEvent() { - this.mindMap.on('draw_click', () => { - // 隐藏文本编辑框 - this.hideEditTextBox() - // 清除激活状态 - if (this.activeNodeList.length > 0) { - this.clearActive() - this.mindMap.render() - this.mindMap.emit('node_active', null, []) - } - }) - this.mindMap.on('expand_btn_click', () => { - this.hideEditTextBox() - }) - this.mindMap.on('before_node_active', () => { - this.hideEditTextBox() - }) - this.mindMap.keyCommand.addShortcut('Enter', () => { - this.hideEditTextBox() - }) - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:19:06 - * @Desc: 注册命令 - */ - registerCommands() { - 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.removeNode = this.removeNode.bind(this) - this.mindMap.command.add('REMOVE_NODE', this.removeNode) - this.updateNodeData = this.updateNodeData.bind(this) - this.mindMap.command.add('UPDATE_NODE_DATA', this.updateNodeData) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-08 16:27:55 - * @Desc: 渲染 - */ - render() { - this.root = this.layout.doLayout() - this.root.render() - } - - /** - * @Author: 王林 - * @Date: 2021-04-12 22:45:01 - * @Desc: 清楚当前激活的节点 - */ - clearActive() { - this.activeNodeList.forEach((item) => { - this.mindMap.execCommand('UPDATE_NODE_DATA', item, { - isActive: false - }) - }) - this.activeNodeList = [] - } - - /** - * @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-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 index = this.getNodeIndex(first) - first.parent.originData.children.splice(index + 1, 0, { - "data": { - "text": "分支主题", - "expand": true - }, - "children": [] - }) - this.mindMap.render() - } - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:31:02 - * @Desc: 插入子节点 - */ - insertChildNode() { - if (this.activeNodeList.length <= 0) { - return; - } - let first = this.activeNodeList[0] - first.originData.children.push({ - "data": { - "text": "分支主题", - "expand": true - }, - "children": [] - }) - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 13:40:39 - * @Desc: 移除节点 - */ - removeNode() { - if (this.activeNodeList.length <= 0) { - return; - } - this.activeNodeList.forEach((item) => { - if (item.isRoot) { - item.children = [] - item.originData.children = [] - } else { - let index = this.getNodeIndex(item) - item.parent.children.splice(index, 1) - item.parent.originData.children.splice(index, 1) - } - }) - this.clearActive() - this.mindMap.render() - } - - /** - * @Author: 王林 - * @Date: 2021-05-04 14:19:48 - * @Desc: 更新节点数据 - */ - updateNodeData(node, data) { - Object.keys(data).forEach((key) => { - node.data[key] = data[key] - }) - } - - /** - * @Author: 王林 - * @Date: 2021-04-13 22:13:02 - * @Desc: 显示文本编辑框 - */ - showEditTextBox(node, rect) { - if (!this.textEditNode) { - this.textEditNode = document.createElement('div') - this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none;` - this.textEditNode.setAttribute('contenteditable', true) - document.body.appendChild(this.textEditNode) - } - node.style.domText(this.textEditNode) - this.textEditNode.innerHTML = node.data.text.split(/\n/img).join('
') - this.textEditNode.style.minWidth = rect.width + 10 + 'px' - this.textEditNode.style.minHeight = rect.height + 6 + 'px' - this.textEditNode.style.left = rect.left + 'px' - this.textEditNode.style.top = rect.top + 'px' - this.textEditNode.style.display = 'block' - this.showTextEdit = true - } - - /** - * @Author: 王林 - * @Date: 2021-04-24 13:48:16 - * @Desc: 隐藏文本编辑框 - */ - hideEditTextBox() { - if (!this.showTextEdit) { - return - } - this.activeNodeList.forEach((node) => { - let str = getStrWithBrFromHtml(this.textEditNode.innerHTML) - node.data.text = str - this.mindMap.render() - }) - this.mindMap.emit('hide_text_edit', this.textEditNode, this.activeNodeList) - this.textEditNode.style.display = 'none' - this.textEditNode.innerHTML = '' - this.textEditNode.style.fontFamily = 'inherit' - this.textEditNode.style.fontSize = 'inherit' - this.textEditNode.style.fontWeight = 'normal' - this.showTextEdit = false - } -} - +import merge from 'deepmerge' +import LogicalStructure from './layouts/LogicalStructure' +import TextEdit from './TextEdit' + +// 布局列表 +const layouts = { + // 思维导图 + logicalStructure: LogicalStructure +} + +/** + * 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.activeNodeList = [] + // 根节点 + this.root = null + // 文本编辑框 + this.textEdit = new TextEdit(this) + // 布局 + this.layout = new(layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)(this) + // 注册命令 + this.registerCommands() + } + + /** + * @Author: 王林 + * @Date: 2021-05-04 13:19:06 + * @Desc: 注册命令 + */ + registerCommands() { + 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.removeNode = this.removeNode.bind(this) + this.mindMap.command.add('REMOVE_NODE', this.removeNode) + this.updateNodeData = this.updateNodeData.bind(this) + this.mindMap.command.add('UPDATE_NODE_DATA', this.updateNodeData) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:27:55 + * @Desc: 渲染 + */ + render() { + this.root = this.layout.doLayout() + this.root.render() + } + + /** + * @Author: 王林 + * @Date: 2021-04-12 22:45:01 + * @Desc: 清楚当前激活的节点 + */ + clearActive() { + this.activeNodeList.forEach((item) => { + this.mindMap.execCommand('UPDATE_NODE_DATA', item, { + isActive: false + }) + }) + this.activeNodeList = [] + } + + /** + * @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-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 index = this.getNodeIndex(first) + first.parent.originData.children.splice(index + 1, 0, { + "data": { + "text": "分支主题", + "expand": true + }, + "children": [] + }) + this.mindMap.render() + } + } + + /** + * @Author: 王林 + * @Date: 2021-05-04 13:31:02 + * @Desc: 插入子节点 + */ + insertChildNode() { + if (this.activeNodeList.length <= 0) { + return; + } + let first = this.activeNodeList[0] + first.originData.children.push({ + "data": { + "text": "分支主题", + "expand": true + }, + "children": [] + }) + this.mindMap.render() + } + + /** + * @Author: 王林 + * @Date: 2021-05-04 13:40:39 + * @Desc: 移除节点 + */ + removeNode() { + if (this.activeNodeList.length <= 0) { + return; + } + this.activeNodeList.forEach((item) => { + if (item.isRoot) { + item.children = [] + item.originData.children = [] + } else { + let index = this.getNodeIndex(item) + item.parent.children.splice(index, 1) + item.parent.originData.children.splice(index, 1) + } + }) + this.clearActive() + this.mindMap.render() + } + + /** + * @Author: 王林 + * @Date: 2021-05-04 14:19:48 + * @Desc: 更新节点数据 + */ + updateNodeData(node, data) { + Object.keys(data).forEach((key) => { + node.data[key] = data[key] + }) + } +} + export default Render \ No newline at end of file diff --git a/src/package/mind-map/src/Style.js b/simple-mind-map/src/Style.js similarity index 100% rename from src/package/mind-map/src/Style.js rename to simple-mind-map/src/Style.js diff --git a/simple-mind-map/src/TextEdit.js b/simple-mind-map/src/TextEdit.js new file mode 100644 index 00000000..3472eff0 --- /dev/null +++ b/simple-mind-map/src/TextEdit.js @@ -0,0 +1,117 @@ +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.mindMap = renderer.mindMap + // 文本编辑框 + this.textEditNode = null + // 文本编辑框是否显示 + this.showTextEdit = false + this.bindEvent() + } + + /** + * @Author: 王林 + * @Date: 2021-04-24 13:27:04 + * @Desc: 事件 + */ + bindEvent() { + this.show = this.show.bind(this) + // 节点双击事件 + this.mindMap.on('node_dblclick', this.show) + // 点击事件 + this.mindMap.on('draw_click', () => { + // 隐藏文本编辑框 + this.hideEditTextBox() + // 清除激活状态 + if (this.activeNodeList.length > 0) { + this.clearActive() + this.mindMap.render() + this.mindMap.emit('node_active', null, []) + } + }) + // 展开收缩按钮点击事件 + this.mindMap.on('expand_btn_click', () => { + this.hideEditTextBox() + }) + // 节点激活前事件 + this.mindMap.on('before_node_active', () => { + this.hideEditTextBox() + }) + // 注册回车快捷键 + this.mindMap.keyCommand.addShortcut('Enter', () => { + this.hideEditTextBox() + }) + } + + /** + * @Author: 王林 + * @Date: 2021-04-13 22:15:56 + * @Desc: 显示文本编辑框 + */ + show(node) { + if (!node.text) { + return; + } + this.showEditTextBox(this, this.textNode.node.node.getBoundingClientRect()) + } + + /** + * @Author: 王林 + * @Date: 2021-04-13 22:13:02 + * @Desc: 显示文本编辑框 + */ + showEditTextBox(node, rect) { + if (!this.textEditNode) { + this.textEditNode = document.createElement('div') + this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none;` + this.textEditNode.setAttribute('contenteditable', true) + document.body.appendChild(this.textEditNode) + } + node.style.domText(this.textEditNode) + this.textEditNode.innerHTML = node.data.text.split(/\n/img).join('
') + this.textEditNode.style.minWidth = rect.width + 10 + 'px' + this.textEditNode.style.minHeight = rect.height + 6 + 'px' + this.textEditNode.style.left = rect.left + 'px' + this.textEditNode.style.top = rect.top + 'px' + this.textEditNode.style.display = 'block' + this.showTextEdit = true + } + + /** + * @Author: 王林 + * @Date: 2021-04-24 13:48:16 + * @Desc: 隐藏文本编辑框 + */ + hideEditTextBox() { + if (!this.showTextEdit) { + return + } + this.activeNodeList.forEach((node) => { + let str = getStrWithBrFromHtml(this.textEditNode.innerHTML) + node.data.text = str + this.mindMap.render() + }) + this.mindMap.emit('hide_text_edit', this.textEditNode, this.activeNodeList) + this.textEditNode.style.display = 'none' + this.textEditNode.innerHTML = '' + this.textEditNode.style.fontFamily = 'inherit' + this.textEditNode.style.fontSize = 'inherit' + this.textEditNode.style.fontWeight = 'normal' + this.showTextEdit = false + } +} \ No newline at end of file diff --git a/src/package/mind-map/src/View.js b/simple-mind-map/src/View.js similarity index 96% rename from src/package/mind-map/src/View.js rename to simple-mind-map/src/View.js index 981fe615..713a2d4c 100644 --- a/src/package/mind-map/src/View.js +++ b/simple-mind-map/src/View.js @@ -1,87 +1,87 @@ -import merge from 'deepmerge' - -/** - * 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 - this.viewBox = { - x: 0, - y: 0, - width: this.mindMap.width, - height: this.mindMap.height - } - this.cacheViewBox = { - x: 0, - y: 0, - width: this.mindMap.width, - height: this.mindMap.height - } - this.scale = 1 - this.bind() - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:38:51 - * @Desc: 绑定 - */ - bind() { - // 拖动视图 - this.mindMap.event.on('mousedown', () => { - this.cacheViewBox = merge({}, this.viewBox) - }) - this.mindMap.event.on('drag', (e, event) => { - // 视图放大缩小后拖动的距离也要相应变化 - this.viewBox.x = this.cacheViewBox.x - event.mousemoveOffset.x * this.scale - this.viewBox.y = this.cacheViewBox.y - event.mousemoveOffset.y * this.scale - this.setViewBox() - }) - // 放大缩小视图 - this.mindMap.event.on('mousewheel', (e, dir) => { - let stepWidth = this.viewBox.width * this.mindMap.opt.scaleRatio - let stepHeight = this.viewBox.height * this.mindMap.opt.scaleRatio - // 放大 - if (dir === 'down') { - this.scale += this.mindMap.opt.scaleRatio - this.viewBox.width += stepWidth - this.viewBox.height += stepHeight - } else { // 缩小 - this.scale -= this.mindMap.opt.scaleRatio - this.viewBox.width -= stepWidth - this.viewBox.height -= stepHeight - } - this.setViewBox() - }) - } - - /** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 15:43:26 - * @Desc: 设置视图 - */ - setViewBox() { - let { - x, - y, - width, - height - } = this.viewBox - this.opt.draw.viewbox(x, y, width, height) - } -} - +import merge from 'deepmerge' + +/** + * 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 + this.viewBox = { + x: 0, + y: 0, + width: this.mindMap.width, + height: this.mindMap.height + } + this.cacheViewBox = { + x: 0, + y: 0, + width: this.mindMap.width, + height: this.mindMap.height + } + this.scale = 1 + this.bind() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 15:38:51 + * @Desc: 绑定 + */ + bind() { + // 拖动视图 + this.mindMap.event.on('mousedown', () => { + this.cacheViewBox = merge({}, this.viewBox) + }) + this.mindMap.event.on('drag', (e, event) => { + // 视图放大缩小后拖动的距离也要相应变化 + this.viewBox.x = this.cacheViewBox.x - event.mousemoveOffset.x * this.scale + this.viewBox.y = this.cacheViewBox.y - event.mousemoveOffset.y * this.scale + this.setViewBox() + }) + // 放大缩小视图 + this.mindMap.event.on('mousewheel', (e, dir) => { + let stepWidth = this.viewBox.width * this.mindMap.opt.scaleRatio + let stepHeight = this.viewBox.height * this.mindMap.opt.scaleRatio + // 放大 + if (dir === 'down') { + this.scale += this.mindMap.opt.scaleRatio + this.viewBox.width += stepWidth + this.viewBox.height += stepHeight + } else { // 缩小 + this.scale -= this.mindMap.opt.scaleRatio + this.viewBox.width -= stepWidth + this.viewBox.height -= stepHeight + } + this.setViewBox() + }) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 15:43:26 + * @Desc: 设置视图 + */ + setViewBox() { + let { + x, + y, + width, + height + } = this.viewBox + this.opt.draw.viewbox(x, y, width, height) + } +} + export default View \ No newline at end of file diff --git a/src/package/mind-map/src/layouts/Base.js b/simple-mind-map/src/layouts/Base.js similarity index 86% rename from src/package/mind-map/src/layouts/Base.js rename to simple-mind-map/src/layouts/Base.js index f5324fdd..17965b64 100644 --- a/src/package/mind-map/src/layouts/Base.js +++ b/simple-mind-map/src/layouts/Base.js @@ -9,17 +9,17 @@ class Base { * @Date: 2021-04-12 22:25:16 * @Desc: 构造函数 */ - constructor(opt) { - // 控制实例 - this.mindMap = opt.mindMap + constructor(renderer) { // 渲染实例 - this.renderer = opt.renderer + this.renderer = renderer + // 控制实例 + this.mindMap = renderer.mindMap // 渲染树 - this.renderTree = opt.renderTree + this.renderTree = renderer.renderTree // 主题配置 - this.themeConfig = opt.themeConfig + this.themeConfig = this.mindMap.themeConfig // 绘图对象 - this.draw = opt.draw + this.draw = this.mindMap.draw // 根节点 this.root = null } diff --git a/simple-mind-map/src/layouts/BubbleChart.js b/simple-mind-map/src/layouts/BubbleChart.js new file mode 100644 index 00000000..84f73fe9 --- /dev/null +++ b/simple-mind-map/src/layouts/BubbleChart.js @@ -0,0 +1,186 @@ +import { + walk +} from '../Utils' +import Node from '../Node' +import merge from 'deepmerge' + +/** + * 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.draw = this.mindMap.draw + // 渲染树 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 根节点 + this.root = null + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:27:55 + * @Desc: 渲染 + */ + render() { + this.computed() + this.root.render() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:04:20 + * @Desc: 计算位置数据 + */ + computed() { + // 计算节点的width、height + this.computedBaseValue() + // 计算节点的left、top + this.computedLeftTopValue() + // 调整节点top + // this.adjustTopValue() + // 调整节点left + // this.adjustLeftValue() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:49:32 + * @Desc: 计算节点的width、height + */ + computedBaseValue() { + walk(this.renderTree, null, (node, parent, isRoot, index, layerIndex) => { + // 设置width、height + let { + children, + ...props + } = node + let newNode = new Node({ + ...props, + mindMap: this.mindMap, + draw: this.draw, + layerIndex + }) + // 计算节点的宽高 + newNode.refreshSize() + // 计算节点的top + if (isRoot) { + newNode.isRoot = true + newNode.left = this.mindMap.width / 2 + newNode.top = this.mindMap.height / 2 + this.root = newNode + } else { + newNode.parent = parent._node + parent._node.addChildren(newNode) + } + node._node = newNode + }, (node) => { + // 遍历完子节点返回时 + }, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:59:25 + * @Desc: 计算节点的left、top + */ + computedLeftTopValue() { + let margin = Math.max(this.mindMap.opt.marginX, this.mindMap.opt.marginY) + walk(this.root, null, (node) => { + if (node.children && node.children.length) { + let rad = (360 / node.children.length) * (Math.PI / 180) + let totalRad = 0 + node.children.forEach((item) => { + let r = node.width / 2 + margin + item.width / 2 + item.left = node.left + r * Math.cos(totalRad) + item.top = node.top + r * Math.sin(totalRad) + totalRad += rad + }) + } + }, null, true) + // return + walk(this.root, null, null, (node) => { + if (node.children && node.children.length) { + let minLeft = Infinity, + minTop = Infinity, + maxRight = -Infinity, + maxBottom = -Infinity + node.children.concat([node]).forEach((item) => { + if ((item.left - item.width / 2) < minLeft) { + minLeft = item.left - item.width / 2 + } + if ((item.top - item.width / 2) < minTop) { + minTop = item.top - item.width / 2 + } + if ((item.left + item.width / 2) > maxRight) { + maxRight = item.left + item.width / 2 + } + if ((item.top + item.width / 2) < maxBottom) { + maxBottom = item.top + item.width / 2 + } + }) + let width = Math.max(maxRight - minLeft, maxBottom - minTop) + let difference = width - node.width + this.update(node, difference) + } + }, true) + } + + update(node, difference) { + if (node.parent) { + // console.log(node.text, difference) + let rad = (360 / node.parent.children.length) * (Math.PI / 180) + let totalRad = 0 + node.parent.children.forEach((item) => { + if (item === node) { + item.left += difference * Math.cos(totalRad) + item.top += difference * Math.sin(totalRad) + if (node.children && node.children.length) { + // this.updateChildren(node) + } + } + totalRad += rad + }) + + this.update(node.parent, difference) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 11:25:52 + * @Desc: 更新子节点 + */ + updateChildren(node, difference) { + let margin = Math.max(this.mindMap.opt.marginX, this.mindMap.opt.marginY) + walk(node, null, (node) => { + if (node.children && node.children.length) { + let rad = (360 / node.children.length) * (Math.PI / 180) + let totalRad = 0 + node.children.forEach((item) => { + let r = node.width / 2 + margin + item.width / 2 + item.left = node.left + r * Math.cos(totalRad) + item.top = node.top + r * Math.sin(totalRad) + totalRad += rad + }) + } + }, null, true) + } +} + +export default Render \ No newline at end of file diff --git a/simple-mind-map/src/layouts/CatalogOrganization.js b/simple-mind-map/src/layouts/CatalogOrganization.js new file mode 100644 index 00000000..909aad42 --- /dev/null +++ b/simple-mind-map/src/layouts/CatalogOrganization.js @@ -0,0 +1,270 @@ +import { + walk +} from '../Utils' +import Node from '../Node' +import merge from 'deepmerge' + +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:25:07 + * @Desc: 目录组织图 + * 思路:第一轮只计算节点的宽高,以及某个节点的所有子节点所占的高度之和,以及该节点里所有子节点中宽度最宽是多少、第二轮计算节点的left和top,需要区分二级节点和其他节点,二级节点top相同,一行依次从做向右排开,其他节点的left相同,一列从上往下依次排开 + */ +class Render { + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:25:32 + * @Desc: 构造函数 + */ + constructor(opt = {}) { + this.opt = opt + this.mindMap = opt.mindMap + this.draw = this.mindMap.draw + // 渲染树 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 根节点 + this.root = null + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:27:55 + * @Desc: 渲染 + */ + render() { + this.computed() + this.root.render() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:04:20 + * @Desc: 计算位置数据 + */ + computed() { + // 计算节点的width、height + this.computedBaseValue() + // 计算节点的left、top + this.computedLeftTopValue() + // 调整节点top + this.adjustTopValue() + // 调整节点left + this.adjustLeftValue() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:49:32 + * @Desc: 计算节点的width、height + */ + computedBaseValue() { + walk(this.renderTree, null, (node, parent, isRoot, index) => { + // 设置width、height + let { + children, + ...props + } = node + let newNode = new Node({ + ...props, + mindMap: this.mindMap, + draw: this.draw + }) + // 计算节点的宽高 + newNode.refreshSize() + // 计算节点的top + if (isRoot) { + newNode.isRoot = true + newNode.left = (this.mindMap.width - newNode.width) / 2 + newNode.top = (this.mindMap.height - newNode.height) / 2 + this.root = newNode + } else { + newNode.parent = parent._node + parent._node.addChildren(newNode) + } + node._node = newNode + }, (node) => { + // 遍历完子节点返回时 + // 计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距 + let len = node._node.children.length + if (node._node.isRoot) { + node._node.childrenAreaWidth = len ? node._node.children.reduce((h, cur) => { + return h + cur.width + }, 0) + (len + 1) * this.mindMap.opt.marginX : 0 + } + node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => { + return h + cur.height + }, 0) + (len + 1) * this.mindMap.opt.marginY : 0 + }, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:59:25 + * @Desc: 计算节点的left、top + */ + computedLeftTopValue() { + walk(this.root, null, (node) => { + if (node.children && node.children.length) { + if (node.isRoot) { + let left = node.left + node.width / 2 - node.childrenAreaWidth / 2 + let totalLeft = left + this.mindMap.opt.marginX + node.children.forEach((cur) => { + // left + cur.left = totalLeft + totalLeft += cur.width + this.mindMap.opt.marginX + // top + cur.top = node.top + node.height + this.mindMap.opt.marginY + }) + } else { + let totalTop = node.top + node.height + this.mindMap.opt.marginY + node.children.forEach((cur) => { + cur.left = node.left + node.width / 5 + this.mindMap.opt.marginX + cur.top = totalTop + totalTop += cur.height + this.mindMap.opt.marginY + }) + } + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-12 17:07:29 + * @Desc: 调整节点left + */ + adjustLeftValue() { + walk(this.root, null, (node) => { + if (node.parent && node.parent.isRoot) { + let childrenAreaWidth = this.getNodeWidth(node) + let difference = childrenAreaWidth - node.width + if (difference > 0) { + this.updateBrothersLeftValue(node, difference / 2) + } + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-12 18:55:03 + * @Desc: 计算节点的宽度,包括子节点 + */ + getNodeWidth(node) { + let widthArr = [] + let loop = (node, width) => { + if (node.children.length) { + width += node.width / 5 + this.mindMap.opt.marginX + 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-04-12 18:21:46 + * @Desc: 调整兄弟节点的left + */ + updateBrothersLeftValue(node, addWidth) { + if (node.parent) { + let childrenList = node.parent.children + let index = childrenList.findIndex((item) => { + return item === node + }) + childrenList.forEach((item, _index) => { + let _offset = 0 + if (_index > index) { + _offset = addWidth + } else { + _offset = -addWidth + } + item.left += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'left', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothersLeftValue(node.parent, addWidth) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 10:04:05 + * @Desc: 调整节点top,该节点之后的节点都往下进行偏移 + */ + adjustTopValue() { + let marginY = this.mindMap.opt.marginY + walk(this.root, null, (node) => { + if (!node.isRoot && !node.parent.isRoot) { + // 判断子节点的areaHeight是否大于该节点自身,大于则需要调整位置 + if (node.children && node.children.length > 0) { + let difference = node.childrenAreaHeight - marginY + this.updateBrothersTopValue(node, difference) + } + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 14:26:03 + * @Desc: 更新兄弟节点的top + */ + updateBrothersTopValue(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) => { + let _offset = 0 + if (_index > index) { + _offset = addHeight + } + item.top += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'top', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothersTopValue(node.parent, addHeight) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 11:25:52 + * @Desc: 更新子节点属性 + */ + updateChildren(children, prop, offset) { + children.forEach((item) => { + item[prop] += offset + if (item.children && item.children.length) { + this.updateChildren(item.children, prop, offset) + } + }) + } +} + +export default Render \ No newline at end of file diff --git a/simple-mind-map/src/layouts/Fishbone.js b/simple-mind-map/src/layouts/Fishbone.js new file mode 100644 index 00000000..b42c644b --- /dev/null +++ b/simple-mind-map/src/layouts/Fishbone.js @@ -0,0 +1,376 @@ +import { + walk +} from '../Utils' +import Node from '../Node' +import merge from 'deepmerge' + +/** + * 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.draw = this.mindMap.draw + // 渲染树 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 根节点 + this.root = null + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:27:55 + * @Desc: 渲染 + */ + render() { + this.computed() + this.root.render() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:04:20 + * @Desc: 计算位置数据 + */ + computed() { + // 计算节点的width、height + this.computedBaseValue() + // 计算节点的left、top + this.computedLeftTopValue() + // 调整节点top + // this.adjustTopValue() + // 调整节点left + // this.adjustLeftValue() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:49:32 + * @Desc: 计算节点的width、height + */ + computedBaseValue() { + walk(this.renderTree, null, (node, parent, isRoot, index, layerIndex) => { + // 生长方向 + let dir = '' + if (isRoot) { + dir = '' + } else if (parent._node.isRoot) { + dir = index % 2 === 0 ? 'up' : 'down' + } else { + dir = parent._node.dir + } + // 设置width、height + let { + children, + ...props + } = node + let newNode = new Node({ + ...props, + mindMap: this.mindMap, + draw: this.draw, + dir, + layerIndex + }) + // 计算节点的宽高 + newNode.refreshSize() + // 计算节点的top + if (isRoot) { + newNode.isRoot = true + newNode.left = (this.mindMap.width - newNode.width) / 2 + newNode.top = (this.mindMap.height - newNode.height) / 2 + this.root = newNode + } else { + newNode.parent = parent._node + parent._node.addChildren(newNode) + } + node._node = newNode + }, (node) => { + // 遍历完子节点返回时 + let len = node._node.children.length + node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => { + return h + cur.height + }, 0) + (len + 1) * this.mindMap.opt.marginY : 0 + }, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:59:25 + * @Desc: 计算节点的left、top + */ + computedLeftTopValue() { + walk(this.root, null, (node) => { + // 二级节点 + if (node.isRoot && node.children && node.children.length) { + let totalLeft = node.left + node.width + this.mindMap.opt.marginX + node.children.forEach((item) => { + item.left = totalLeft + item.top = node.top + node.height / 2 - this.mindMap.opt.marginY - item.height + totalLeft += item.width + this.mindMap.opt.marginX + this.computedThirdLevelLeftTopValue(item) + }) + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-13 09:33:04 + * @Desc: 计算三级节点 + */ + computedThirdLevelLeftTopValue(node) { + if (node.children && node.children.length > 0) { + let totalLeft = node.left + let totalTop = node.top - this.mindMap.opt.marginY + node.children.forEach((item) => { + let h = node.height + this.mindMap.opt.marginY + let w = h / Math.tan(70) + item.left = totalLeft + w + totalLeft += w + item.top = totalTop - item.height + totalTop -= this.mindMap.opt.marginY + item.height + this.computedThirdAfterLevelLeftTopValue(item) + }) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-13 09:55:54 + * @Desc: 计算三级以后的节点 + */ + computedThirdAfterLevelLeftTopValue(root) { + let marginY = this.mindMap.opt.marginY + let marginX = this.mindMap.opt.marginX + // 计算left、top + walk(root, null, (node) => { + if (node.children && node.children.length) { + let totalTop = node.top + node.height + marginY + node.children.forEach((cur) => { + cur.left = node.left + node.width / 5 + marginX + cur.top = totalTop + totalTop += cur.height + marginY + }) + } + }, null, true) + // 调整top + const updateBrothersTopValue = (node, addHeight) => { + if (node.parent) { + let childrenList = node.parent.children + let index = childrenList.findIndex((item) => { + return item === node + }) + childrenList.forEach((item, _index) => { + let _offset = 0 + if (_index > index) { + _offset = addHeight + } + item.top += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'top', _offset) + } + }) + // 更新父节点的位置 + updateBrothersTopValue(node.parent, addHeight) + } + } + walk(root, null, (node) => { + // 判断子节点的areaHeight是否大于该节点自身,大于则需要调整位置 + if (node.children && node.children.length > 0) { + let difference = node.childrenAreaHeight - marginY + updateBrothersTopValue(node, difference) + } + }, null, true) + // 调整left + const updateBrothersLeftValue = (node, w, h) => { + if (node.parent && node.parent.layerIndex > 0) { + let childrenList = node.parent.children + let index = childrenList.findIndex((item) => { + return item === node + }) + childrenList.forEach((item, _index) => { + let _w = 0 + let _h = 0 + if (_index >= index) { + _w = w + _h = -h + } + console.log(item.text, _w, _h) + item.left += _w + item.top += _h + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'left', _w) + this.updateChildren(item.children, 'left', _h) + } + }) + // 更新父节点的位置 + updateBrothersLeftValue(node.parent, w, h) + } + } + walk(root, null, (node) => { + if (node.layerIndex > 1) { + let h = node.childrenAreaHeight - marginY + if (h > 0) { + let w = h / Math.tan(70) + console.log(node.text, w, h) + // let childrenAreaWidth = getNodeWidth(node) + // let differenceX = childrenAreaWidth - node.width + // updateBrothersLeftValue(node, w, h) + } + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-12 17:07:29 + * @Desc: 调整节点left + */ + adjustLeftValue() { + walk(this.root, null, (node) => { + if (node.parent && node.parent.isRoot) { + let childrenAreaWidth = this.getNodeWidth(node) + let difference = childrenAreaWidth - node.width + if (difference > 0) { + this.updateBrothersLeftValue(node, difference / 2) + } + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-12 18:55:03 + * @Desc: 计算节点的宽度,包括子节点 + */ + getNodeWidth(node) { + let widthArr = [] + let loop = (node, width) => { + if (node.children.length) { + width += node.width / 5 + this.mindMap.opt.marginX + 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-04-12 18:21:46 + * @Desc: 调整兄弟节点的left + */ + updateBrothersLeftValue(node, addWidth) { + if (node.parent) { + let childrenList = node.parent.children + let index = childrenList.findIndex((item) => { + return item === node + }) + childrenList.forEach((item, _index) => { + let _offset = 0 + if (_index > index) { + _offset = addWidth + } else { + _offset = -addWidth + } + item.left += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'left', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothersLeftValue(node.parent, addWidth) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 10:04:05 + * @Desc: 调整节点top,该节点之后的节点都往下进行偏移 + */ + adjustTopValue() { + let marginY = this.mindMap.opt.marginY + walk(this.root, null, (node) => { + if (!node.isRoot && !node.parent.isRoot) { + // 判断子节点的areaHeight是否大于该节点自身,大于则需要调整位置 + if (node.children && node.children.length > 0) { + let difference = node.childrenAreaHeight - marginY + this.updateBrothersTopValue(node, difference) + } + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 14:26:03 + * @Desc: 更新兄弟节点的top + */ + updateBrothersTopValue(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) => { + let _offset = 0 + if (_index > index) { + _offset = addHeight + } + item.top += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'top', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothersTopValue(node.parent, addHeight) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 11:25:52 + * @Desc: 更新子节点属性 + */ + updateChildren(children, prop, offset) { + children.forEach((item) => { + item[prop] += offset + if (item.children && item.children.length) { + this.updateChildren(item.children, prop, offset) + } + }) + } +} + +export default Render \ No newline at end of file diff --git a/src/package/mind-map/src/layouts/LogicalStructure.js b/simple-mind-map/src/layouts/LogicalStructure.js similarity index 97% rename from src/package/mind-map/src/layouts/LogicalStructure.js rename to simple-mind-map/src/layouts/LogicalStructure.js index 3c41ec35..40905e61 100644 --- a/src/package/mind-map/src/layouts/LogicalStructure.js +++ b/simple-mind-map/src/layouts/LogicalStructure.js @@ -1,7 +1,7 @@ import Base from './Base'; import { walk -} from '../Utils' +} from '../utils' import Node from '../Node' /** @@ -45,14 +45,10 @@ class LogicalStructure extends Base { computedBaseValue() { walk(this.renderTree, null, (node, parent, isRoot, layerIndex) => { // 遍历子节点前设置left、width、height - if (!node.data) { - node.data = {} - } // 创建节点 let newNode = new Node({ uid: this.mindMap.uid++, - originData: node, - data: node.data, + data: node, renderer: this.renderer, mindMap: this.mindMap, draw: this.draw, diff --git a/simple-mind-map/src/layouts/MindMap.js b/simple-mind-map/src/layouts/MindMap.js new file mode 100644 index 00000000..cba93d07 --- /dev/null +++ b/simple-mind-map/src/layouts/MindMap.js @@ -0,0 +1,195 @@ +import { + walk +} from '../Utils' +import Node from '../Node' +import merge from 'deepmerge' + +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:25:07 + * @Desc: 思维导图 + * 思路:在逻辑结构图的基础上增加一个变量来记录生长方向,向左还是向右,同时在计算left的时候根据方向来计算、调整top时只考虑同方向的节点即可 + */ +class Render { + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:25:32 + * @Desc: 构造函数 + */ + constructor(opt = {}) { + this.opt = opt + this.mindMap = opt.mindMap + this.draw = this.mindMap.draw + // 渲染树 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 根节点 + this.root = null + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:27:55 + * @Desc: 渲染 + */ + render() { + this.computed() + this.root.render() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:04:20 + * @Desc: 计算位置数据 + */ + computed() { + // 计算节点的left、width、height + this.computedBaseValue() + // 计算节点的top + this.computedTopValue() + // 调整节点top + this.adjustTopValue() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:49:32 + * @Desc: 计算节点的left、width、height + */ + computedBaseValue() { + walk(this.renderTree, null, (node, parent, isRoot, index) => { + // 生长方向 + let dir = '' + if (isRoot) { + dir = '' + } else if (parent._node.isRoot) { + dir = index % 2 === 0 ? 'right' : 'left' + } else { + dir = parent._node.dir + } + // 设置left、width、height + let { + children, + ...props + } = node + let newNode = new Node({ + ...props, + mindMap: this.mindMap, + draw: this.draw, + dir + }) + // 计算节点的宽高 + newNode.refreshSize() + // 计算节点的left + if (isRoot) { + newNode.isRoot = true + newNode.left = (this.mindMap.width - newNode.width) / 2 + newNode.top = (this.mindMap.height - newNode.height) / 2 + this.root = newNode + } else { + newNode.left = dir === 'right' ? parent._node.left + parent._node.width + this.mindMap.opt.marginX : parent._node.left - this.mindMap.opt.marginX - newNode.width + newNode.parent = parent._node + parent._node.addChildren(newNode) + } + node._node = newNode + }, (node) => { + // 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距 + let len = node._node.children.length + node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => { + return h + cur.height + }, 0) + (len + 1) * this.mindMap.opt.marginY : 0 + }, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:59:25 + * @Desc: 计算节点的top + */ + computedTopValue() { + walk(this.root, null, (node) => { + if (node.children && node.children.length) { + // 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半 + let top = node.top + node.height / 2 - node.childrenAreaHeight / 2 + let totalTop = top + this.mindMap.opt.marginY + node.children.forEach((cur) => { + cur.top = totalTop + totalTop += cur.height + this.mindMap.opt.marginY + }) + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 10:04:05 + * @Desc: 调整节点top + */ + adjustTopValue() { + let margin = this.mindMap.opt.marginY * 2 + walk(this.root, null, (node) => { + // 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置 + let difference = node.childrenAreaHeight - margin - 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.filter((item) => { + return item.dir === node.dir + }) + let index = childrenList.findIndex((item) => { + return item === node + }) + childrenList.forEach((item, _index) => { + 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) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 11:25:52 + * @Desc: 更新子节点属性 + */ + updateChildren(children, prop, offset) { + children.forEach((item) => { + item[prop] += offset + if (item.children && item.children.length) { + this.updateChildren(item.children, prop, offset) + } + }) + } + + +} + +export default Render \ No newline at end of file diff --git a/simple-mind-map/src/layouts/OrganizationStructure.js b/simple-mind-map/src/layouts/OrganizationStructure.js new file mode 100644 index 00000000..7b329b7c --- /dev/null +++ b/simple-mind-map/src/layouts/OrganizationStructure.js @@ -0,0 +1,183 @@ +import { + walk +} from '../Utils' +import Node from '../Node' +import merge from 'deepmerge' + +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:25:07 + * @Desc: 组织结构图 + * 思路:和逻辑结构图基本一样,只是方向变成向下生长,所以先计算节点的top,后计算节点的left、最后调整节点的left即可 + */ +class Render { + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:25:32 + * @Desc: 构造函数 + */ + constructor(opt = {}) { + this.opt = opt + this.mindMap = opt.mindMap + this.draw = this.mindMap.draw + // 渲染树 + this.renderTree = merge({}, this.mindMap.opt.data || {}) + // 根节点 + this.root = null + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 16:27:55 + * @Desc: 渲染 + */ + render() { + this.computed() + this.root.render() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:04:20 + * @Desc: 计算位置数据 + */ + computed() { + // 计算节点的top、width、height + this.computedBaseValue() + // 计算节点的left + this.computedLeftValue() + // 调整节点left + this.adjustLeftValue() + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:49:32 + * @Desc: 计算节点的top、width、height + */ + computedBaseValue() { + walk(this.renderTree, null, (node, parent, isRoot, index) => { + // 设置top、width、height + let { + children, + ...props + } = node + let newNode = new Node({ + ...props, + mindMap: this.mindMap, + draw: this.draw + }) + // 计算节点的宽高 + newNode.refreshSize() + // 计算节点的top + if (isRoot) { + newNode.isRoot = true + newNode.left = (this.mindMap.width - newNode.width) / 2 + newNode.top = (this.mindMap.height - newNode.height) / 2 + this.root = newNode + } else { + newNode.top = parent._node.top + parent._node.height + this.mindMap.opt.marginY + newNode.parent = parent._node + parent._node.addChildren(newNode) + } + node._node = newNode + }, (node) => { + // 返回时计算节点的areaWidth,也就是子节点所占的宽度之和,包括外边距 + let len = node._node.children.length + node._node.childrenAreaWidth = len ? node._node.children.reduce((h, cur) => { + return h + cur.width + }, 0) + (len + 1) * this.mindMap.opt.marginX : 0 + }, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 09:59:25 + * @Desc: 计算节点的left + */ + computedLeftValue() { + walk(this.root, null, (node) => { + if (node.children && node.children.length) { + // 第一个子节点的left值 = 该节点中心的left值 - 子节点的宽度之和的一半 + let left = node.left + node.width / 2 - node.childrenAreaWidth / 2 + let totalLeft = left + this.mindMap.opt.marginX + node.children.forEach((cur) => { + cur.left = totalLeft + totalLeft += cur.width + this.mindMap.opt.marginX + }) + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-08 10:04:05 + * @Desc: 调整节点left + */ + adjustLeftValue() { + let margin = this.mindMap.opt.marginX * 2 + walk(this.root, null, (node) => { + // 判断子节点所占的宽度之和是否大于该节点自身,大于则需要调整位置 + let difference = node.childrenAreaWidth - margin - node.width + if (difference > 0) { + this.updateBrothers(node, difference / 2) + } + }, null, true) + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 14:26:03 + * @Desc: 更新兄弟节点的left + */ + updateBrothers(node, addWidth) { + if (node.parent) { + let childrenList = node.parent.children + let index = childrenList.findIndex((item) => { + return item === node + }) + childrenList.forEach((item, _index) => { + let _offset = 0 + if (_index < index) { + _offset = -addWidth + } else if (_index > index) { + _offset = addWidth + } + item.left += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'left', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothers(node.parent, addWidth) + } + } + + /** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 11:25:52 + * @Desc: 更新子节点属性 + */ + updateChildren(children, prop, offset) { + children.forEach((item) => { + item[prop] += offset + if (item.children && item.children.length) { + this.updateChildren(item.children, prop, offset) + } + }) + } + + +} + +export default Render \ No newline at end of file diff --git a/simple-mind-map/src/layouts/Structure.js b/simple-mind-map/src/layouts/Structure.js new file mode 100644 index 00000000..2cc8c28c --- /dev/null +++ b/simple-mind-map/src/layouts/Structure.js @@ -0,0 +1,11 @@ +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-12 17:21:04 + * @Desc: 基类 + */ +class Structure { + +} + +export default Structure \ No newline at end of file diff --git a/src/package/mind-map/src/svg/btns.js b/simple-mind-map/src/svg/btns.js similarity index 100% rename from src/package/mind-map/src/svg/btns.js rename to simple-mind-map/src/svg/btns.js diff --git a/src/package/mind-map/src/themes/blueSky.js b/simple-mind-map/src/themes/blueSky.js similarity index 100% rename from src/package/mind-map/src/themes/blueSky.js rename to simple-mind-map/src/themes/blueSky.js diff --git a/src/package/mind-map/src/themes/brainImpairedPink.js b/simple-mind-map/src/themes/brainImpairedPink.js similarity index 100% rename from src/package/mind-map/src/themes/brainImpairedPink.js rename to simple-mind-map/src/themes/brainImpairedPink.js diff --git a/src/package/mind-map/src/themes/classic.js b/simple-mind-map/src/themes/classic.js similarity index 100% rename from src/package/mind-map/src/themes/classic.js rename to simple-mind-map/src/themes/classic.js diff --git a/src/package/mind-map/src/themes/classic2.js b/simple-mind-map/src/themes/classic2.js similarity index 100% rename from src/package/mind-map/src/themes/classic2.js rename to simple-mind-map/src/themes/classic2.js diff --git a/src/package/mind-map/src/themes/classic3.js b/simple-mind-map/src/themes/classic3.js similarity index 100% rename from src/package/mind-map/src/themes/classic3.js rename to simple-mind-map/src/themes/classic3.js diff --git a/src/package/mind-map/src/themes/dark.js b/simple-mind-map/src/themes/dark.js similarity index 100% rename from src/package/mind-map/src/themes/dark.js rename to simple-mind-map/src/themes/dark.js diff --git a/src/package/mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js similarity index 100% rename from src/package/mind-map/src/themes/default.js rename to simple-mind-map/src/themes/default.js diff --git a/src/package/mind-map/src/themes/earthYellow.js b/simple-mind-map/src/themes/earthYellow.js similarity index 100% rename from src/package/mind-map/src/themes/earthYellow.js rename to simple-mind-map/src/themes/earthYellow.js diff --git a/src/package/mind-map/src/themes/freshGreen.js b/simple-mind-map/src/themes/freshGreen.js similarity index 100% rename from src/package/mind-map/src/themes/freshGreen.js rename to simple-mind-map/src/themes/freshGreen.js diff --git a/src/package/mind-map/src/themes/freshRed.js b/simple-mind-map/src/themes/freshRed.js similarity index 100% rename from src/package/mind-map/src/themes/freshRed.js rename to simple-mind-map/src/themes/freshRed.js diff --git a/src/package/mind-map/src/themes/index.js b/simple-mind-map/src/themes/index.js similarity index 100% rename from src/package/mind-map/src/themes/index.js rename to simple-mind-map/src/themes/index.js diff --git a/src/package/mind-map/src/themes/romanticPurple.js b/simple-mind-map/src/themes/romanticPurple.js similarity index 100% rename from src/package/mind-map/src/themes/romanticPurple.js rename to simple-mind-map/src/themes/romanticPurple.js diff --git a/src/package/mind-map/src/Utils.js b/simple-mind-map/src/utils/index.js similarity index 96% rename from src/package/mind-map/src/Utils.js rename to simple-mind-map/src/utils/index.js index 895abd14..d658f2e2 100644 --- a/src/package/mind-map/src/Utils.js +++ b/simple-mind-map/src/utils/index.js @@ -1,134 +1,134 @@ -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-06 14:13:17 - * @Desc: 深度优先遍历树 - */ -export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0) => { - beforeCallback && beforeCallback(root, parent, isRoot, layerIndex) - if (root.children && root.children.length > 0) { - let _layerIndex = layerIndex + 1 - root.children.forEach((node) => { - walk(node, root, beforeCallback, afterCallback, false, _layerIndex) - }) - } - afterCallback && afterCallback(root, parent, isRoot, layerIndex) -} - -/** - * javascript comment - * @Author: 王林25 - * @Date: 2021-04-07 18:47:20 - * @Desc: 广度优先遍历树 - */ -export const bfsWalk = (root, callback) => { - callback(root) - let stack = [root] - while (stack.length) { - let cur = stack.shift() - if (cur.children && cur.children.length) { - cur.children.forEach((item) => { - stack.push(item) - callback(item) - }) - } - } -} - -/** - * 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 = [] - if (maxWidth && maxHeight) { - if (width <= maxWidth && height <= maxHeight) { - arr = [width, height] - } else { - let mRatio = maxWidth / maxHeight - if (nRatio > mRatio) { // 固定高度 - arr = [nRatio * maxHeight, maxHeight] - } else { // 固定宽度 - arr = [maxWidth, maxWidth / nRatio] - } - } - } else if (maxWidth) { - if (width <= maxWidth) { - arr = [width, height] - } else { - arr = [maxWidth, maxWidth / nRatio] - } - } else if (maxHeight) { - if (height <= maxHeight) { - arr = [width, height] - } else { - arr = [nRatio * maxHeight, 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() - img.src = imgUrl - img.onload = () => { - let arr = resizeImgSize(img.naturalWidth, img.naturalHeight, maxWidth, maxHeight) - resolve(arr) - } - img.onerror = (e) => { - reject(e) - } - }) -} - -/** - * @Author: 王林 - * @Date: 2021-05-04 12:26:56 - * @Desc: 从头html结构字符串里获取带换行符的字符串 - */ -export const getStrWithBrFromHtml = (str) => { - str = str.replace(/
/img, '\n') - let el = document.createElement('div') - el.innerHTML = str - str = el.textContent - return str; -} - -/** - * @Author: 王林 - * @Date: 2021-05-04 14:45:39 - * @Desc: 极简的深拷贝 - */ -export const simpleDeepClone = (data) => { - try { - return JSON.parse(JSON.stringify(data)) - } catch (error) { - return null - } -} - -/** - * @Author: 王林 - * @Date: 2021-05-04 14:40:11 - * @Desc: 复制渲染树数据 - */ -export const copyRenderTree = (tree, root) => { - tree.data = simpleDeepClone(root.data) - tree.children = [] - if (root.children.length > 0) { - root.children.forEach((item, index) => { - tree.children[index] = copyRenderTree({}, item) - }) - } - return tree; +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-06 14:13:17 + * @Desc: 深度优先遍历树 + */ +export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0) => { + beforeCallback && beforeCallback(root, parent, isRoot, layerIndex) + if (root.children && root.children.length > 0) { + let _layerIndex = layerIndex + 1 + root.children.forEach((node) => { + walk(node, root, beforeCallback, afterCallback, false, _layerIndex) + }) + } + afterCallback && afterCallback(root, parent, isRoot, layerIndex) +} + +/** + * javascript comment + * @Author: 王林25 + * @Date: 2021-04-07 18:47:20 + * @Desc: 广度优先遍历树 + */ +export const bfsWalk = (root, callback) => { + callback(root) + let stack = [root] + while (stack.length) { + let cur = stack.shift() + if (cur.children && cur.children.length) { + cur.children.forEach((item) => { + stack.push(item) + callback(item) + }) + } + } +} + +/** + * 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 = [] + if (maxWidth && maxHeight) { + if (width <= maxWidth && height <= maxHeight) { + arr = [width, height] + } else { + let mRatio = maxWidth / maxHeight + if (nRatio > mRatio) { // 固定高度 + arr = [nRatio * maxHeight, maxHeight] + } else { // 固定宽度 + arr = [maxWidth, maxWidth / nRatio] + } + } + } else if (maxWidth) { + if (width <= maxWidth) { + arr = [width, height] + } else { + arr = [maxWidth, maxWidth / nRatio] + } + } else if (maxHeight) { + if (height <= maxHeight) { + arr = [width, height] + } else { + arr = [nRatio * maxHeight, 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() + img.src = imgUrl + img.onload = () => { + let arr = resizeImgSize(img.naturalWidth, img.naturalHeight, maxWidth, maxHeight) + resolve(arr) + } + img.onerror = (e) => { + reject(e) + } + }) +} + +/** + * @Author: 王林 + * @Date: 2021-05-04 12:26:56 + * @Desc: 从头html结构字符串里获取带换行符的字符串 + */ +export const getStrWithBrFromHtml = (str) => { + str = str.replace(/
/img, '\n') + let el = document.createElement('div') + el.innerHTML = str + str = el.textContent + return str; +} + +/** + * @Author: 王林 + * @Date: 2021-05-04 14:45:39 + * @Desc: 极简的深拷贝 + */ +export const simpleDeepClone = (data) => { + try { + return JSON.parse(JSON.stringify(data)) + } catch (error) { + return null + } +} + +/** + * @Author: 王林 + * @Date: 2021-05-04 14:40:11 + * @Desc: 复制渲染树数据 + */ +export const copyRenderTree = (tree, root) => { + tree.data = simpleDeepClone(root.data) + tree.children = [] + if (root.children.length > 0) { + root.children.forEach((item, index) => { + tree.children[index] = copyRenderTree({}, item) + }) + } + return tree; } \ No newline at end of file diff --git a/src/package/mind-map/src/utils/keyMap.js b/simple-mind-map/src/utils/keyMap.js similarity index 100% rename from src/package/mind-map/src/utils/keyMap.js rename to simple-mind-map/src/utils/keyMap.js diff --git a/src/package/.DS_Store b/src/package/.DS_Store deleted file mode 100644 index fa590cfd..00000000 Binary files a/src/package/.DS_Store and /dev/null differ diff --git a/src/pages/Index/Index.vue b/src/pages/Index/Index.vue deleted file mode 100644 index 308dabc0..00000000 --- a/src/pages/Index/Index.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/src/store.js b/src/store.js deleted file mode 100644 index b8baeb93..00000000 --- a/src/store.js +++ /dev/null @@ -1,68 +0,0 @@ -import Vue from 'vue' -import Vuex from 'vuex' -import exampleData from './package/mind-map/example/exampleData'; - -Vue.use(Vuex) - -const store = new Vuex.Store({ - state: { - userInfo: null,// 用户信息 - mindMapData: null// 思维导图数据 - }, - mutations: { - /** - * @Author: 王林 - * @Date: 2020-11-28 15:32:32 - * @Desc: 设置用户信息 - */ - setUserInfo(state, userInfo) { - state.userInfo = userInfo - }, - - /** - * @Author: 王林 - * @Date: 2021-04-10 14:50:01 - * @Desc: 设置思维导图数据 - */ - setMindMapData(state, data) { - state.mindMapData = data - } - }, - actions: { - /** - * @Author: 王林 - * @Date: 2020-11-28 15:28:03 - * @Desc: 获取用户信息 - */ - async getUserInfo(ctx) { - try { - let { data } = await api.getUserInfo() - ctx.commit('setUserInfo', data.data) - } catch (error) { - console.log(error) - } - }, - - /** - * @Author: 王林 - * @Date: 2021-04-10 14:50:40 - * @Desc: 获取思维导图数据 - */ - async getUserMindMapData(ctx) { - try { - let { data } = { - data: { - data: { - mindMapData: exampleData - } - } - } - ctx.commit('setMindMapData', data.data) - } catch (error) { - console.log(error) - } - } - } -}) - -export default store \ No newline at end of file diff --git a/.DS_Store b/web/.DS_Store similarity index 100% rename from .DS_Store rename to web/.DS_Store diff --git a/babel.config.js b/web/babel.config.js similarity index 100% rename from babel.config.js rename to web/babel.config.js diff --git a/package.json b/web/package.json similarity index 94% rename from package.json rename to web/package.json index 01d4412c..1b04cfd0 100644 --- a/package.json +++ b/web/package.json @@ -8,9 +8,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { - "@svgdotjs/svg.js": "^3.0.16", "core-js": "^3.6.5", - "deepmerge": "^1.5.2", "element-ui": "^2.15.1", "vue": "^2.6.11", "vue-router": "^3.5.1", diff --git a/public/index.html b/web/public/index.html similarity index 79% rename from public/index.html rename to web/public/index.html index 37d3bac8..3cfbc940 100644 --- a/public/index.html +++ b/web/public/index.html @@ -10,8 +10,6 @@ - -
diff --git a/src/.DS_Store b/web/src/.DS_Store similarity index 100% rename from src/.DS_Store rename to web/src/.DS_Store diff --git a/src/App.vue b/web/src/App.vue similarity index 100% rename from src/App.vue rename to web/src/App.vue diff --git a/src/assets/.DS_Store b/web/src/assets/.DS_Store similarity index 100% rename from src/assets/.DS_Store rename to web/src/assets/.DS_Store diff --git a/src/assets/icon-font/.DS_Store b/web/src/assets/icon-font/.DS_Store similarity index 100% rename from src/assets/icon-font/.DS_Store rename to web/src/assets/icon-font/.DS_Store diff --git a/src/assets/icon-font/demo.css b/web/src/assets/icon-font/demo.css similarity index 100% rename from src/assets/icon-font/demo.css rename to web/src/assets/icon-font/demo.css diff --git a/src/assets/icon-font/demo_index.html b/web/src/assets/icon-font/demo_index.html similarity index 100% rename from src/assets/icon-font/demo_index.html rename to web/src/assets/icon-font/demo_index.html diff --git a/src/assets/icon-font/iconfont.css b/web/src/assets/icon-font/iconfont.css similarity index 100% rename from src/assets/icon-font/iconfont.css rename to web/src/assets/icon-font/iconfont.css diff --git a/src/assets/icon-font/iconfont.js b/web/src/assets/icon-font/iconfont.js similarity index 100% rename from src/assets/icon-font/iconfont.js rename to web/src/assets/icon-font/iconfont.js diff --git a/src/assets/icon-font/iconfont.json b/web/src/assets/icon-font/iconfont.json similarity index 100% rename from src/assets/icon-font/iconfont.json rename to web/src/assets/icon-font/iconfont.json diff --git a/src/assets/icon-font/iconfont.ttf b/web/src/assets/icon-font/iconfont.ttf similarity index 100% rename from src/assets/icon-font/iconfont.ttf rename to web/src/assets/icon-font/iconfont.ttf diff --git a/src/assets/icon-font/iconfont.woff b/web/src/assets/icon-font/iconfont.woff similarity index 100% rename from src/assets/icon-font/iconfont.woff rename to web/src/assets/icon-font/iconfont.woff diff --git a/src/assets/icon-font/iconfont.woff2 b/web/src/assets/icon-font/iconfont.woff2 similarity index 100% rename from src/assets/icon-font/iconfont.woff2 rename to web/src/assets/icon-font/iconfont.woff2 diff --git a/src/components/ImgUpload/index.vue b/web/src/components/ImgUpload/index.vue similarity index 84% rename from src/components/ImgUpload/index.vue rename to web/src/components/ImgUpload/index.vue index 5841c686..09da3823 100644 --- a/src/components/ImgUpload/index.vue +++ b/web/src/components/ImgUpload/index.vue @@ -31,8 +31,6 @@