Compare commits

...

34 Commits

Author SHA1 Message Date
街角小林
9a64094f14 打包0.10.4 2024-07-26 10:32:39 +08:00
街角小林
95fdb35f7b Fix:优化性能模式的懒加载 2024-07-26 10:15:54 +08:00
街角小林
497c2606df Feat:懒加载节点树时增加触发渲染开始和结束事件的派发 2024-07-26 09:38:22 +08:00
街角小林
d735be1204 Doc: update 2024-07-25 18:33:36 +08:00
街角小林
45d2da1337 update package.json 2024-07-25 18:32:04 +08:00
街角小林
194571d5fe Fix:修复在@svgdotjs/svg.js库版本为3.0.16时报错的问题 2024-07-25 18:05:22 +08:00
街角小林
418b24b039 Doc: update 2024-07-25 17:38:01 +08:00
街角小林
b32a8b5a85 Demo:基础配置里增加开启性能模式的开关 2024-07-25 16:41:21 +08:00
街角小林
2dee415a64 Feat:新增性能模式 2024-07-25 16:40:47 +08:00
街角小林
4e7d59b328 Fix:删除无用代码,修复鼠标悬浮节点上报错不显示展开收起按钮的问题 2024-07-24 17:13:49 +08:00
街角小林
9bde9ffaf3 Doc: update 2024-07-22 09:25:22 +08:00
街角小林
1b4ca19ad8 打包0.10.3 2024-07-19 14:22:00 +08:00
街角小林
eb72e0eed3 Doc: update 2024-07-19 14:11:23 +08:00
街角小林
6dea1ef9b2 Demo:支持配置外框内边距 2024-07-18 15:18:44 +08:00
街角小林
c6c1ef2117 Feat:新增设置外框内边距的实例化选项 2024-07-18 13:45:50 +08:00
街角小林
4423fd562b Demo:去除引入公式库样式的逻辑 2024-07-18 10:10:23 +08:00
街角小林
7b1ea5e354 Feat:1.支持自定义katex库渲染模式的实例化选项;2.公式插件会默认引入katex库的样式,增加自定义字体文件路径的实例化选项 2024-07-18 10:09:39 +08:00
街角小林
5192753816 Feat:如果开启了公式插件并且存在公式,那么导出svg时需要传入katex库的样式 2024-07-18 10:05:48 +08:00
街角小林
6382e8acd8 Doc: update 2024-07-16 16:47:29 +08:00
街角小林
e293039b3c Doc: update 2024-07-12 17:05:55 +08:00
街角小林
f819cbc5b1 Demo:修复对象类型的标签数据在标签弹窗里回显错误的问题 2024-07-12 15:18:48 +08:00
街角小林
44a883c473 Feat:复制、剪切、移动多个节点时,按其在节点上的顺序进行操作 2024-07-11 09:53:54 +08:00
街角小林
c1f600dc1f Fix:修复同时选中多个节点,可以不停插入概要的问题 2024-07-11 09:34:14 +08:00
街角小林
4777ab3e58 Demo:支持点击节点标签进行文本和颜色的修改 2024-07-09 15:57:46 +08:00
街角小林
12c6479c0d Feat:node_tag_click事件新增两个回调参数 2024-07-09 15:54:06 +08:00
街角小林
f79918ec6f Feat:1.支持定义标签样式;2.新增标签显示位置的实例化选项; 2024-07-09 14:11:18 +08:00
街角小林
ba9a6e501a Doc: update 2024-07-09 10:35:28 +08:00
街角小林
9b55d051dc Fix:修复编辑过节点文本后,再使用滚轮或快捷键缩放画布时上次被编辑的节点会进入编辑状态,以及思维导图快捷键会失效的问题 2024-07-08 16:52:53 +08:00
街角小林
159a4a202c Fix:修复customCreateNodeContent、createNodePrefixContent等方法里获取到的节点的isRoot和parent等值都为null的问题 2024-07-08 16:27:18 +08:00
街角小林
ac72c0c1dc Demo:恢复误删的文件 2024-07-05 10:57:06 +08:00
街角小林
f54f92c303 update 2024-07-04 17:32:00 +08:00
街角小林
c2dbfb41d5 打包0.10.2-fix.1 2024-07-03 12:11:59 +08:00
街角小林
5867649429 Fix:修复修改了外框的线条样式,激活后再取消激活,样式会变成默认的样式的问题 2024-07-03 12:00:44 +08:00
街角小林
de29ec59c5 打包Demo 2024-07-03 11:34:16 +08:00
151 changed files with 3027 additions and 329 deletions

View File

@@ -25,6 +25,8 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
> 客户端版本会落后于在线版本,尝试最新功能请优先使用在线版。
【云存储版本】如果你需要带后端的云存储版本,可以尝试我们开发的另一个项目[理想文档](https://github.com/wanglin2/lx-doc)。
# 特性
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
@@ -35,7 +37,7 @@ Github[releases](https://github.com/wanglin2/mind-map/releases)。百度云
- [x] 支持画布拖动、缩放
- [x] 支持鼠标按键拖动选择和 Ctrl+左键两种多选节点方式
- [x] 支持导出为`json``png``svg``pdf``markdown``xmind``txt`,支持从`json``xmind``markdown`导入
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条、手绘风格、彩虹线条
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印、滚动条、手绘风格、彩虹线条、标记、外框
- [x] 提供丰富的配置,满足各种场景各种使用习惯
- [x] 支持协同编辑
- [x] 支持演示模式
@@ -413,4 +415,28 @@ const mindMap = new MindMap({
<img src="./web/src/assets/avatar/buddy.jpg" style="width: 50px;height: 50px;" />
<span>buddy</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>小川</span>
</span>
<span>
<img src="./web/src/assets/avatar/Tobin.jpg" style="width: 50px;height: 50px;" />
<span>Tobin</span>
</span>
<span>
<img src="./web/src/assets/avatar/夏虫不语冰.jpg" style="width: 50px;height: 50px;" />
<span>夏虫不语冰</span>
</span>
<span>
<img src="./web/src/assets/avatar/晴空.jpg" style="width: 50px;height: 50px;" />
<span>晴空</span>
</span>
<span>
<img src="./web/src/assets/avatar/default.png" style="width: 50px;height: 50px;" />
<span>黄泳</span>
</span>
<span>
<img src="./web/src/assets/avatar/ccccs.jpg" style="width: 50px;height: 50px;" />
<span>ccccs</span>
</span>
</p>

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/img/Tobin.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
dist/img/ccccs.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
dist/img/夏虫不语冰.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
dist/img/晴空.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

2
dist/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d20f68f"],{b407:function(n,u,t){"use strict";t.r(u);var c=function(){var n=this,u=n._self._c;return u("div")},e=[],l={},s=l,i=t("2877"),o=Object(i["a"])(s,c,e,!1,null,null,null);u["default"]=o.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-583efa32.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-6f12c8fe.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/chunk-cba4e134.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,15 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
window.externalPublicPath = './dist/'
// 接管应用
window.takeOverApp = false</script><script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script><script>LA.init({
id: 'KRO0WxK8GT66tYCQ',
ck: 'KRO0WxK8GT66tYCQ',
autoTrack: false
})</script><link href="dist/css/chunk-vendors.css?dd8fa3cd99060d550179" rel="stylesheet"><link href="dist/css/app.css?dd8fa3cd99060d550179" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
window.takeOverApp = false</script><script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script><script>try {
LA.init({
id: 'KRO0WxK8GT66tYCQ',
ck: 'KRO0WxK8GT66tYCQ',
autoTrack: false
})
} catch (error) {
console.log(error)
}</script><link href="dist/css/chunk-vendors.css?4d8e2833c3860a9a5265" rel="stylesheet"><link href="dist/css/app.css?4d8e2833c3860a9a5265" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
@@ -70,4 +74,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?dd8fa3cd99060d550179"></script><script src="dist/js/app.js?dd8fa3cd99060d550179"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?4d8e2833c3860a9a5265"></script><script src="dist/js/app.js?4d8e2833c3860a9a5265"></script></body></html>

View File

@@ -31,7 +31,7 @@ MindMap.iconList = icons.nodeIconList
MindMap.constants = constants
MindMap.themes = themes
MindMap.defaultTheme = defaultTheme
MindMap.version = '0.10.2'
MindMap.version = '0.10.4'
MindMap.usePlugin(MiniMap)
.usePlugin(Watermark)

View File

@@ -194,7 +194,7 @@ class MindMap {
this.renderer.reRender = true // 标记为重新渲染
this.renderer.clearCache() // 清空节点缓存池
this.clearDraw() // 清空画布
this.render(callback, (source = ''))
this.render(callback, source)
}
// 获取或更新容器尺寸位置信息
@@ -288,7 +288,9 @@ class MindMap {
// 更新配置
updateConfig(opt = {}) {
this.emit('before_update_config', this.opt)
this.opt = this.handleOpt(merge.all([defaultOpt, this.opt, opt]))
this.emit('after_update_config', this.opt)
}
// 获取当前布局结构
@@ -379,6 +381,9 @@ class MindMap {
// 导出
async export(...args) {
try {
if (!this.doExport) {
throw new Error('请注册Export插件')
}
let result = await this.doExport.export(...args)
return result
} catch (error) {
@@ -416,6 +421,11 @@ class MindMap {
addContentToFooter,
node
} = {}) {
const { watermarkConfig, openPerformance } = this.opt
// 如果开启了性能模式,那么需要先渲染所有节点
if (openPerformance) {
this.renderer.forceLoadNode(node)
}
const { cssTextList, header, headerHeight, footer, footerHeight } =
handleGetSvgDataExtraContent({
addContentToHeader,
@@ -459,7 +469,7 @@ class MindMap {
if (!ignoreWatermark && hasWatermark) {
this.watermark.isInExport = true
// 是否是仅导出时需要水印
const { onlyExport } = this.opt.watermarkConfig
const { onlyExport } = watermarkConfig
// 是否需要重新绘制水印
const needReDrawWatermark =
rect.width > origWidth || rect.height > origHeight

View File

@@ -1,14 +1,14 @@
{
"name": "simple-mind-map",
"version": "0.10.0-fix.1",
"version": "0.10.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.10.0-fix.1",
"version": "0.10.4",
"license": "MIT",
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
"@svgdotjs/svg.js": "^3.2.0",
"deepmerge": "^1.5.2",
"eventemitter3": "^4.0.7",
"jszip": "^3.10.1",

View File

@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
"version": "0.10.2",
"version": "0.10.4",
"description": "一个简单的web在线思维导图",
"authors": [
{
@@ -28,7 +28,7 @@
"module": "index.js",
"main": "./dist/simpleMindMap.umd.min.js",
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
"@svgdotjs/svg.js": "^3.2.0",
"deepmerge": "^1.5.2",
"eventemitter3": "^4.0.7",
"jszip": "^3.10.1",

View File

@@ -235,6 +235,10 @@ export const CONSTANTS = {
DEFAULT: 'default',
NOT_ACTIVE: 'notActive',
ACTIVE_ONLY: 'activeOnly'
},
TAG_POSITION: {
RIGHT: 'right',
BOTTOM: 'bottom'
}
}

View File

@@ -23,6 +23,8 @@ export const defaultOpt = {
mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签
maxTag: 5,
// 标签显示的位置相对于节点文本bottom下方、right右侧
tagPosition: CONSTANTS.TAG_POSITION.RIGHT,
// 展开收缩按钮尺寸
expandBtnSize: 20,
// 节点里图片和文字的间距
@@ -226,6 +228,14 @@ export const defaultOpt = {
// 自定义超链接的跳转
// 如果不传默认会以新窗口的方式打开超链接可以传递一个函数函数接收两个参数link超链接的url、node所属节点实例只要传递了函数就会阻止默认的跳转
customHyperlinkJump: null,
// 是否开启性能模式默认情况下所有节点都会直接渲染无论是否处于画布可视区域这样当节点数量比较多时1000+会比较卡如果你的数据量比较大那么可以通过该配置开启性能模式即只渲染画布可视区域内的节点超出的节点不渲染这样会大幅提高渲染速度当然同时也会带来一些其他问题比如1.当拖动或是缩放画布时会实时计算并渲染未节点的节点所以会带来一定卡顿2.导出图片、svg、pdf时需要先渲染全部节点所以会比较慢3.其他目前未发现的问题
openPerformance: false,
// 性能优化模式配置
performanceConfig: {
time: 250,// 当视图改变后多久刷新一次节点单位ms
padding: 100,// 超出画布四周指定范围内依旧渲染节点
removeNodeWhenOutCanvas: true,// 节点移除画布可视区域后从画布删除
},
// 【Select插件】
// 多选节点时鼠标移动到边缘时的画布移动偏移量
@@ -379,6 +389,12 @@ export const defaultOpt = {
// 【Formula插件】
// 是否开启在富文本编辑框中直接编辑数学公式
enableEditFormulaInRichTextEdit: true,
// katex库的字体文件的请求路径。仅当katex的output配置为html时才会请求字体文件。可以通过mindMap.formula.getKatexConfig()方法来获取当前的配置
// 字体文件可以从node_modules中找到katex/dist/fonts/。可以上传到你的服务器或cdn
// 最终的字体请求路径为`${katexFontPath}fonts/KaTeX_AMS-Regular.woff2`,可以自行拼接进行测试是否可以访问
katexFontPath: 'https://unpkg.com/katex@0.16.11/dist/',
// 自定义katex库的输出模式。默认当Chrome内核100以下会使用html方式否则使用mathml方式如果你有自己的规则那么可以传递一个函数函数返回值为mathml或html
getKatexOutputType: null,
// 【RichText插件】
// 转换富文本内容,当进入富文本编辑时,可以通过该参数传递一个函数,函数接收文本内容,需要返回你处理后的文本内容
@@ -387,5 +403,9 @@ export const defaultOpt = {
beforeHideRichTextEdit: null,
// 设置富文本节点编辑框和节点大小一致,形成伪原地编辑的效果
// 需要注意的是,只有当节点内只有文本、且形状是矩形才会有比较好的效果
richTextEditFakeInPlace: false
richTextEditFakeInPlace: false,
// 【OuterFrame】插件
outerFramePaddingX: 10,
outerFramePaddingY: 10
}

View File

@@ -31,7 +31,9 @@ import {
checkSmmFormatData,
checkIsNodeStyleDataKey,
removeRichTextStyes,
formatGetNodeGeneralization
formatGetNodeGeneralization,
sortNodeList,
throttle
} from '../../utils'
import { shapeList } from './node/Shape'
import { lineStyleProps } from '../../themes/default'
@@ -143,13 +145,54 @@ class Render {
if (!this.mindMap.opt.enableDblclickBackToRootNode) return
this.setRootNodeCenter()
})
// let timer = null
// this.mindMap.on('view_data_change', () => {
// clearTimeout(timer)
// timer = setTimeout(() => {
// this.render()
// }, 300)
// })
// 性能模式
this.performanceMode()
}
// 性能模式,懒加载节点
performanceMode() {
const { openPerformance, performanceConfig } = this.mindMap.opt
const onViewDataChange = throttle(() => {
if (this.root) {
this.mindMap.emit('node_tree_render_start')
this.root.render(
() => {
this.mindMap.emit('node_tree_render_end')
},
false,
true
)
}
}, performanceConfig.time)
let lastOpen = false
this.mindMap.on('before_update_config', opt => {
lastOpen = opt.openPerformance
})
this.mindMap.on('after_update_config', opt => {
if (opt.openPerformance && !lastOpen) {
// 动态开启性能模式
this.mindMap.on('view_data_change', onViewDataChange)
this.forceLoadNode()
}
if (!opt.openPerformance && lastOpen) {
// 动态关闭性能模式
this.mindMap.off('view_data_change', onViewDataChange)
this.forceLoadNode()
}
})
if (!openPerformance) return
this.mindMap.on('view_data_change', onViewDataChange)
}
// 强制渲染节点,不考虑是否在画布可视区域内
forceLoadNode(node) {
node = node || this.root
if (node) {
this.mindMap.emit('node_tree_render_start')
node.render(() => {
this.mindMap.emit('node_tree_render_end')
}, true)
}
}
// 注册命令
@@ -452,6 +495,7 @@ class Render {
}
this.mindMap.emit('node_tree_render_start')
// 计算布局
this.root = null
this.layout.doLayout(root => {
// 删除本次渲染时不再需要的节点
Object.keys(this.lastNodeCache).forEach(uid => {
@@ -1373,7 +1417,8 @@ class Render {
if (this.activeNodeList.length <= 0) {
return null
}
const nodeList = getTopAncestorsFomNodeList(this.activeNodeList)
let nodeList = getTopAncestorsFomNodeList(this.activeNodeList)
nodeList = sortNodeList(nodeList)
return nodeList.map(node => {
return copyNodeTree({}, node, true)
})
@@ -1385,11 +1430,12 @@ class Render {
return
}
// 找出激活节点中的顶层节点列表,并过滤掉根节点
const nodeList = getTopAncestorsFomNodeList(this.activeNodeList).filter(
let nodeList = getTopAncestorsFomNodeList(this.activeNodeList).filter(
node => {
return !node.isRoot
}
)
nodeList = sortNodeList(nodeList)
// 复制数据
const copyData = nodeList.map(node => {
return copyNodeTree({}, node, true)
@@ -1665,11 +1711,13 @@ class Render {
)
})
const list = parseAddGeneralizationNodeList(nodeList)
if (list.length <= 0) return
const isRichText = !!this.mindMap.richText
const { focusNewNode, inserting } = this.getNewNodeBehavior(
openEdit,
list.length > 1
)
let needRender = false
list.forEach(item => {
const newData = {
inserting,
@@ -1683,15 +1731,30 @@ class Render {
isActive: focusNewNode
}
let generalization = item.node.getData('generalization')
if (generalization) {
if (Array.isArray(generalization)) {
generalization.push(newData)
} else {
generalization = [generalization, newData]
generalization = generalization
? Array.isArray(generalization)
? generalization
: [generalization]
: []
// 如果是范围概要,那么检查该范围是否存在
if (item.range) {
const isExist = !!generalization.find(item2 => {
return (
item2.range &&
item2.range[0] === item.range[0] &&
item2.range[1] === item.range[1]
)
})
if (isExist) {
return
}
// 不存在则添加
generalization.push(newData)
} else {
generalization = [newData]
// 不是范围概要直接添加,因为前面已经判断过是否存在
generalization.push(newData)
}
needRender = true
this.mindMap.execCommand('SET_NODE_DATA', item.node, {
generalization
})
@@ -1700,6 +1763,7 @@ class Render {
expand: true
})
})
if (!needRender) return
// 需要清除原来激活的节点
if (focusNewNode) {
this.clearActiveNodeList()

View File

@@ -187,7 +187,8 @@ export default class TextEdit {
// 处理画布缩放
onScale() {
if (!this.currentNode) return
const node = this.getCurrentEditNode()
if (!node) return
if (this.mindMap.richText) {
this.mindMap.richText.cacheEditingText =
this.mindMap.richText.getEditText()
@@ -197,7 +198,7 @@ export default class TextEdit {
this.showTextEdit = false
}
this.show({
node: this.currentNode,
node,
isFromScale: true
})
}

View File

@@ -253,11 +253,15 @@ class Node {
height: rect.height
}
}
const { tagPosition } = this.mindMap.opt
const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM
// 宽高
let imgContentWidth = 0
let imgContentHeight = 0
let textContentWidth = 0
let textContentHeight = 0
let tagContentWidth = 0
let tagContentHeight = 0
// 存在图片
if (this._imgData) {
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
@@ -290,10 +294,20 @@ class Node {
}
// 标签
if (this._tagData.length > 0) {
textContentWidth += this._tagData.reduce((sum, cur) => {
textContentHeight = Math.max(textContentHeight, cur.height)
let maxTagHeight = 0
const totalTagWidth = this._tagData.reduce((sum, cur) => {
maxTagHeight = Math.max(maxTagHeight, cur.height)
return (sum += cur.width + this.textContentItemMargin)
}, 0)
if (tagIsBottom) {
// 文字下方
tagContentWidth = totalTagWidth
tagContentHeight = maxTagHeight
} else {
// 否则在右侧
textContentWidth += totalTagWidth
textContentHeight = Math.max(textContentHeight, maxTagHeight)
}
}
// 备注
if (this._noteData) {
@@ -325,6 +339,15 @@ class Node {
// 纯内容宽高
let _width = Math.max(imgContentWidth, textContentWidth)
let _height = imgContentHeight + textContentHeight
// 如果标签在文字下方
if (tagIsBottom && tagContentHeight > 0 && textContentHeight > 0) {
// 那么文字和标签之间也需要间距
margin += this.blockContentMargin
// 整体高度要考虑标签宽度
_width = Math.max(_width, tagContentWidth)
// 整体高度要加上标签的高度
_height += tagContentHeight
}
// 计算节点形状需要的附加内边距
let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
@@ -342,7 +365,7 @@ class Node {
layout() {
// 清除之前的内容
this.group.clear()
const { hoverRectPadding } = this.mindMap.opt
const { hoverRectPadding, tagPosition } = this.mindMap.opt
let { width, height, textContentItemMargin } = this
let { paddingY } = this.getPaddingVale()
const halfBorderWidth = this.getBorderWidth() / 2
@@ -382,6 +405,8 @@ class Node {
addHoverNode()
return
}
const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM
const { textContentHeight } = this._rectInfo
// 图片节点
let imgHeight = 0
if (this._imgData) {
@@ -401,7 +426,7 @@ class Node {
})
foreignObject
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._prefixData.height) / 2)
.y((textContentHeight - this._prefixData.height) / 2)
textContentNested.add(foreignObject)
textContentOffsetX += this._prefixData.width + textContentItemMargin
}
@@ -412,7 +437,7 @@ class Node {
this._iconData.forEach(item => {
item.node
.x(textContentOffsetX + iconLeft)
.y((this._rectInfo.textContentHeight - item.height) / 2)
.y((textContentHeight - item.height) / 2)
iconNested.add(item.node)
iconLeft += item.width + textContentItemMargin
})
@@ -427,7 +452,7 @@ class Node {
;(this._textData.nodeContent || this._textData.node)
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._textData.height) / 2)
.y((textContentHeight - this._textData.height) / 2)
textContentNested.add(this._textData.node)
textContentOffsetX += this._textData.width + textContentItemMargin
}
@@ -435,29 +460,50 @@ class Node {
if (this._hyperlinkData) {
this._hyperlinkData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2)
.y((textContentHeight - this._hyperlinkData.height) / 2)
textContentNested.add(this._hyperlinkData.node)
textContentOffsetX += this._hyperlinkData.width + textContentItemMargin
}
// 标签
let tagNested = new G()
if (this._tagData && this._tagData.length > 0) {
let tagLeft = 0
this._tagData.forEach(item => {
item.node
.x(textContentOffsetX + tagLeft)
.y((this._rectInfo.textContentHeight - item.height) / 2)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
textContentNested.add(tagNested)
textContentOffsetX += tagLeft
if (tagIsBottom) {
// 标签显示在文字下方
let tagLeft = 0
this._tagData.forEach(item => {
item.node.x(tagLeft).y(0)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
tagNested.cx(width / 2).y(
paddingY + // 内边距
imgHeight + // 图片高度
textContentHeight + // 文本区域高度
(imgHeight > 0 && textContentHeight > 0
? this.blockContentMargin
: 0) + // 图片和文本之间的间距
this.blockContentMargin // 标签和文本之间的间距
)
this.group.add(tagNested)
} else {
// 标签显示在文字右侧
let tagLeft = 0
this._tagData.forEach(item => {
item.node
.x(textContentOffsetX + tagLeft)
.y((textContentHeight - item.height) / 2)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
textContentNested.add(tagNested)
textContentOffsetX += tagLeft
}
}
// 备注
if (this._noteData) {
this._noteData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._noteData.height) / 2)
.y((textContentHeight - this._noteData.height) / 2)
textContentNested.add(this._noteData.node)
textContentOffsetX += this._noteData.width
}
@@ -465,7 +511,7 @@ class Node {
if (this._attachmentData) {
this._attachmentData.node
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2)
.y((textContentHeight - this._attachmentData.height) / 2)
textContentNested.add(this._attachmentData.node)
textContentOffsetX += this._attachmentData.width
}
@@ -478,20 +524,18 @@ class Node {
})
foreignObject
.x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._postfixData.height) / 2)
.y((textContentHeight - this._postfixData.height) / 2)
textContentNested.add(foreignObject)
textContentOffsetX += this._postfixData.width
}
this.group.add(textContentNested)
// 文字内容整体
textContentNested.translate(
width / 2 - textContentNested.bbox().width / 2,
imgHeight +
paddingY +
(imgHeight > 0 && this._rectInfo.textContentHeight > 0
? this.blockContentMargin
: 0)
paddingY + // 内边距
imgHeight + // 图片高度
(imgHeight > 0 && textContentHeight > 0 ? this.blockContentMargin : 0) // 和图片的间距
)
this.group.add(textContentNested)
addHoverNode()
this.mindMap.emit('node_layout_end', this)
}
@@ -639,7 +683,7 @@ class Node {
}
// 更新节点
update() {
update(forceRender) {
if (!this.group) {
return
}
@@ -666,36 +710,11 @@ class Node {
}
}
// 更新概要
this.renderGeneralization()
this.renderGeneralization(forceRender)
// 更新协同头像
if (this.updateUserListNode) this.updateUserListNode()
// 更新节点位置
let t = this.group.transform()
// // 如果上次不在可视区内,且本次也不在,那么直接返回
// let { left: ox, top: oy } = this.getNodePosInClient(
// t.translateX,
// t.translateY
// )
// let oldIsInClient =
// ox > 0 && oy > 0 && ox < this.mindMap.width && oy < this.mindMap.height
// let { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
// let newIsNotInClient =
// nx + this.width < 0 ||
// ny + this.height < 0 ||
// nx > this.mindMap.width ||
// ny > this.mindMap.height
// if (!oldIsInClient && newIsNotInClient) {
// if (!this.isHide) {
// this.isHide = true
// this.group.hide()
// }
// return
// }
// // 如果当前是隐藏状态,那么先显示
// if (this.isHide) {
// this.isHide = false
// this.group.show()
// }
// 如果节点位置没有变化,则返回
if (this.left === t.translateX && this.top === t.translateY) return
this.group.translate(this.left - t.translateX, this.top - t.translateY)
@@ -713,6 +732,17 @@ class Node {
}
}
// 判断节点是否可见
checkIsInClient(padding = 0) {
const { left: nx, top: ny } = this.getNodePosInClient(this.left, this.top)
return (
nx + this.width > 0 - padding &&
ny + this.height > 0 - padding &&
nx < this.mindMap.width + padding &&
ny < this.mindMap.height + padding
)
}
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
reRender() {
let sizeChange = this.getSize()
@@ -741,32 +771,44 @@ class Node {
}
}
// 递归渲染
render(callback = () => {}) {
// 递归渲染
// forceRender强制渲染无论是否处于画布可视区域
// async异步渲染
render(callback = () => {}, forceRender = false, async = false) {
// 节点
// 重新渲染连线
this.renderLine()
if (!this.group) {
// 创建组
this.group = new G()
this.group.addClass('smm-node')
this.group.css({
cursor: 'default'
})
this.bindGroupEvent()
this.nodeDraw.add(this.group)
this.layout()
this.update()
} else {
if (!this.nodeDraw.has(this.group)) {
const { openPerformance, performanceConfig } = this.mindMap.opt
// 强制渲染、或没有开启性能模式、或不在画布可视区域内不渲染节点内容
if (
forceRender ||
!openPerformance ||
this.checkIsInClient(performanceConfig.padding)
) {
if (!this.group) {
// 创建组
this.group = new G()
this.group.addClass('smm-node')
this.group.css({
cursor: 'default'
})
this.bindGroupEvent()
this.nodeDraw.add(this.group)
}
if (this.needLayout) {
this.needLayout = false
this.layout()
this.update(forceRender)
} else {
if (!this.nodeDraw.has(this.group)) {
this.nodeDraw.add(this.group)
}
if (this.needLayout) {
this.needLayout = false
this.layout()
}
this.updateExpandBtnPlaceholderRect()
this.update(forceRender)
}
this.updateExpandBtnPlaceholderRect()
this.update()
} else if (openPerformance && performanceConfig.removeNodeWhenOutCanvas) {
this.removeSelf()
}
// 子节点
if (
@@ -776,12 +818,23 @@ class Node {
) {
let index = 0
this.children.forEach(item => {
item.render(() => {
index++
if (index >= this.children.length) {
callback()
}
})
const renderChild = () => {
item.render(
() => {
index++
if (index >= this.children.length) {
callback()
}
},
forceRender,
async
)
}
if (async) {
setTimeout(renderChild, 0)
} else {
renderChild()
}
})
} else {
callback()
@@ -796,6 +849,13 @@ class Node {
}
}
// 删除自身,只是从画布删除,节点容器还在,后续还可以重新插回画布
removeSelf() {
if (!this.group) return
this.group.remove()
this.removeGeneralization()
}
// 递归删除,只是从画布删除,节点容器还在,后续还可以重新插回画布
remove() {
if (!this.group) return
@@ -812,6 +872,10 @@ class Node {
// 销毁节点,不但会从画布删除,而且原节点直接置空,后续无法再插回画布
destroy() {
this.removeLine()
if (this.parent) {
this.parent.removeLine()
}
if (!this.group) return
if (this.emptyUser) {
this.emptyUser()
@@ -819,11 +883,7 @@ class Node {
this.resetWhenDelete()
this.group.remove()
this.removeGeneralization()
this.removeLine()
this.group = null
if (this.parent) {
this.parent.removeLine()
}
this.style.onRemove()
}

View File

@@ -1,8 +1,4 @@
import {
checkIsNodeStyleDataKey,
generateColorByContent
} from '../../../utils/index'
import { Gradient } from '@svgdotjs/svg.js'
import { checkIsNodeStyleDataKey } from '../../../utils/index'
const rootProp = ['paddingX', 'paddingY']
const backgroundStyleProps = [
@@ -182,21 +178,24 @@ class Style {
}
// 标签文字
tagText(node) {
tagText(node, style) {
node
.fill({
color: '#fff'
})
.css({
'font-size': '12px'
'font-size': style.fontSize + 'px'
})
}
// 标签矩形
tagRect(node, text, color) {
tagRect(node, style) {
node.fill({
color: color || generateColorByContent(text.node.textContent)
color: style.fill
})
if (style.radius) {
node.radius(style.radius)
}
}
// 内置图标

View File

@@ -6,12 +6,23 @@ import {
checkIsRichText,
isUndef,
createForeignObjectNode,
addXmlns
addXmlns,
generateColorByContent
} from '../../../utils'
import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js'
import iconsSvg from '../../../svg/icons'
import { CONSTANTS } from '../../../constants/constant'
// 标签默认的样式
const defaultTagStyle = {
radius: 3, // 标签矩形的圆角大小
fontSize: 12, // 字号建议文字高度不要大于height
fill: '', // 标签矩形的背景颜色
height: 20, // 标签矩形的高度
paddingX: 8 // 水平内边距如果设置了width将忽略该配置
//width: 30 // 标签矩形的宽度,如果不设置,默认以文字的宽度+paddingX*2为宽度
}
// 创建图片节点
function createImgNode() {
const img = this.getData('image')
@@ -284,31 +295,69 @@ function createHyperlinkNode() {
// 创建标签节点
function createTagNode() {
let tagData = this.getData('tag')
const tagData = this.getData('tag')
if (!tagData || tagData.length <= 0) {
return []
}
let nodes = []
tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => {
let tag = new G()
let { maxTag, tagsColorMap } = this.mindMap.opt
tagsColorMap = tagsColorMap || {}
const nodes = []
tagData.slice(0, maxTag).forEach((item, index) => {
let str = ''
let style = {
...defaultTagStyle
}
// 旧版只支持字符串类型
if (typeof item === 'string') {
str = item
} else {
// v0.10.3+版本支持对象类型
str = item.text
style = { ...defaultTagStyle, ...item.style }
}
// 是否手动设置了标签宽度
const hasCustomWidth = typeof style.width !== 'undefined'
// 创建容器节点
const tag = new G()
tag.on('click', () => {
this.mindMap.emit('node_tag_click', this, item)
this.mindMap.emit('node_tag_click', this, item, index, tag)
})
// 标签文本
let text = new Text().text(item).x(8).cy(8)
this.style.tagText(text, index)
let { width } = text.bbox()
const text = new Text().text(str)
this.style.tagText(text, style)
// 获取文本宽高
const { width: textWidth, height: textHeight } = text.bbox()
// 矩形宽度
const rectWidth = hasCustomWidth
? style.width
: textWidth + style.paddingX * 2
// 取文本和矩形最大宽高作为标签宽高
const maxWidth = hasCustomWidth ? Math.max(rectWidth, textWidth) : rectWidth
const maxHeight = Math.max(style.height, textHeight)
// 文本居中
if (hasCustomWidth) {
text.x((maxWidth - textWidth) / 2)
} else {
text.x(hasCustomWidth ? 0 : style.paddingX)
}
text.cy(-maxHeight / 2)
// 标签矩形
let rect = new Rect().size(width + 16, 20)
// 先从自定义的颜色中获取颜色,没有的话就按照内容生成
const tagsColorList = this.mindMap.opt.tagsColorMap || {}
const color = tagsColorList[text.node.textContent]
this.style.tagRect(rect, text, color)
const rect = new Rect().size(rectWidth, style.height).cy(-maxHeight / 2)
if (hasCustomWidth) {
rect.x((maxWidth - rectWidth) / 2)
}
this.style.tagRect(rect, {
...style,
fill:
style.fill || // 优先节点自身配置
tagsColorMap[text.node.textContent] || // 否则尝试从实例化选项tagsColorMap映射中获取颜色
generateColorByContent(text.node.textContent) // 否则按照标签内容生成
})
tag.add(rect).add(text)
nodes.push({
node: tag,
width: width + 16,
height: 20
width: maxWidth,
height: maxHeight
})
})
return nodes

View File

@@ -1,5 +1,5 @@
import btnsSvg from '../../../svg/btns'
import { SVG, Circle, G } from '@svgdotjs/svg.js'
import { SVG, Circle, G, Text } from '@svgdotjs/svg.js'
// 创建展开收起按钮的内容节点
function createExpandNodeContent() {
@@ -10,9 +10,7 @@ function createExpandNodeContent() {
// 根据配置判断是否显示数量按钮
if (this.mindMap.opt.isShowExpandNum) {
// 展开的节点
this._openExpandNode = SVG()
.text()
.size(this.expandBtnSize, this.expandBtnSize)
this._openExpandNode = new Text()
// 文本垂直居中
this._openExpandNode.attr({
'text-anchor': 'middle',
@@ -81,7 +79,7 @@ function updateExpandBtnNode() {
// 计算子节点数量
let count = this.sumNode(this.nodeData.children)
count = expandBtnNumHandler(count)
node.text(count)
node.text(String(count))
} else {
this._fillExpandNode.stroke('none')
}
@@ -126,11 +124,7 @@ function renderExpandBtn() {
this._expandBtn.on('click', e => {
e.stopPropagation()
// 展开收缩
this.mindMap.execCommand(
'SET_NODE_EXPAND',
this,
!this.getData('expand')
)
this.mindMap.execCommand('SET_NODE_EXPAND', this, !this.getData('expand'))
this.mindMap.emit('expand_btn_click', this)
})
this._expandBtn.on('dblclick', e => {

View File

@@ -85,7 +85,7 @@ function updateGeneralization() {
}
// 渲染概要节点
function renderGeneralization() {
function renderGeneralization(forceRender) {
if (this.isGeneralization) return
this.updateGeneralizationData()
const list = this.formatGetGeneralization()
@@ -100,7 +100,7 @@ function renderGeneralization() {
this.renderer.layout.renderGeneralization(this._generalizationList)
this._generalizationList.forEach(item => {
this.style.generalizationLine(item.generalizationLine)
item.generalizationNode.render()
item.generalizationNode.render(() => {}, forceRender)
})
}

View File

@@ -83,6 +83,11 @@ class Base {
)
newNode.reset()
newNode.layerIndex = layerIndex
if (isRoot) {
newNode.isRoot = true
} else {
newNode.parent = parent._node
}
this.cacheNode(data._node.uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
// 主题或主题配置改变了、节点层级改变了,需要重新渲染节点文本等情况需要重新计算节点大小和布局
@@ -112,6 +117,11 @@ class Base {
newNode.reset()
newNode.nodeData = newNode.handleData(data || {})
newNode.layerIndex = layerIndex
if (isRoot) {
newNode.isRoot = true
} else {
newNode.parent = parent._node
}
this.cacheNode(uid, newNode)
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
data._node = newNode
@@ -137,7 +147,9 @@ class Base {
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
layerIndex
layerIndex,
isRoot,
parent: !isRoot ? parent._node : null
})
// uid保存到数据上为了节点复用
data.data.uid = newUid
@@ -157,11 +169,9 @@ class Base {
}
// 根节点
if (isRoot) {
newNode.isRoot = true
this.root = newNode
} else {
// 互相收集
newNode.parent = parent._node
parent._node.addChildren(newNode)
}
return newNode

Some files were not shown because too many files have changed in this diff Show More