Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47328236f7 | ||
|
|
566147c530 | ||
|
|
54fad1ee08 | ||
|
|
8c8e283a88 | ||
|
|
4553bc37f5 | ||
|
|
41b0b21354 | ||
|
|
39b55b0cd7 | ||
|
|
ede01c069e | ||
|
|
aa56e53c4d | ||
|
|
3a723a15bf | ||
|
|
1b2da4eb72 | ||
|
|
6f351d4cee | ||
|
|
dba93d4363 | ||
|
|
b46a94bd96 | ||
|
|
fadd8217e8 | ||
|
|
b0d898054e | ||
|
|
3e84892a28 | ||
|
|
f663f8d60a | ||
|
|
0aeee0ff28 | ||
|
|
140e9cf893 | ||
|
|
89e426e522 | ||
|
|
c77062b660 | ||
|
|
bca2912390 | ||
|
|
ed7177e29d | ||
|
|
12ca3ed556 | ||
|
|
d7de86209c | ||
|
|
e53c6cd559 | ||
|
|
352711c871 | ||
|
|
d304ebc544 | ||
|
|
27291fe5c9 | ||
|
|
f714e47722 | ||
|
|
ce135dd111 | ||
|
|
fb4bfedef4 | ||
|
|
5cbe5d2906 | ||
|
|
b6c15164ac | ||
|
|
1960e96a19 | ||
|
|
7a0fd5adfb | ||
|
|
9ae40cff32 | ||
|
|
0070cab9f1 | ||
|
|
79ae876eeb | ||
|
|
ca35b84308 | ||
|
|
3b4f386900 | ||
|
|
34ef1e908e | ||
|
|
6a3f016920 | ||
|
|
d1ab67cd4c | ||
|
|
0d81f9ff9c | ||
|
|
471bd3c5e5 | ||
|
|
5d4cf8a3c3 | ||
|
|
5a5062ecaf | ||
|
|
13c4b51f69 | ||
|
|
a3ddcc93e5 | ||
|
|
365e51e2e9 | ||
|
|
fd096c4444 | ||
|
|
4c1786e127 | ||
|
|
bfeb59d43f | ||
|
|
aa998b1829 | ||
|
|
c8f5c94683 | ||
|
|
2aea7a3c88 | ||
|
|
9f16691cd6 | ||
|
|
8363819933 | ||
|
|
7d6149dbdf | ||
|
|
b088c40101 | ||
|
|
5b5aab1c9e | ||
|
|
13a4b12ad4 | ||
|
|
3e59d84e6b | ||
|
|
cd987a382a | ||
|
|
fdada41327 | ||
|
|
46d44e6753 | ||
|
|
d63d9873ac | ||
|
|
7eeb7ab51b | ||
|
|
7a2075df51 | ||
|
|
ba5ff54b9a | ||
|
|
8af44b2d45 | ||
|
|
72c989ae11 | ||
|
|
861309d517 | ||
|
|
2f0d64cf41 | ||
|
|
5df8a28403 | ||
|
|
3c9940e076 | ||
|
|
0b11aff105 | ||
|
|
3e4fe06197 | ||
|
|
8bf23f8397 | ||
|
|
10d04549fc | ||
|
|
b4193d50a3 | ||
|
|
0620d31d0a | ||
|
|
a804a5e2fa | ||
|
|
06daffbaab | ||
|
|
b901c08156 | ||
|
|
e27eb63627 | ||
|
|
990a4e5c4c | ||
|
|
7f79d4881d | ||
|
|
2d8643015f | ||
|
|
30f7713af1 | ||
|
|
7ab9db4bbd | ||
|
|
fa7548afaf | ||
|
|
676efa792a | ||
|
|
9ffa1e2744 | ||
|
|
1108b29da0 | ||
|
|
b00d5f6adb | ||
|
|
c6438668f2 | ||
|
|
608adb21e1 | ||
|
|
495cb4c62a | ||
|
|
3048cba1ae | ||
|
|
5b1f5f803e | ||
|
|
0d79e28ec9 | ||
|
|
806be0b537 | ||
|
|
c15b9be7ef | ||
|
|
f6ac2c80ed | ||
|
|
5f19509079 | ||
|
|
4677b11dfb | ||
|
|
b3e9797b0e | ||
|
|
f72deb0478 | ||
|
|
455e97074f | ||
|
|
3e52fa6585 | ||
|
|
48172bc035 | ||
|
|
09d1f82021 | ||
|
|
786e183091 | ||
|
|
0ebac39716 | ||
|
|
10b8a33ab7 | ||
|
|
72b9001b56 | ||
|
|
1efabd44ec | ||
|
|
5e66bd29ff | ||
|
|
2f1114b722 | ||
|
|
4428028146 | ||
|
|
e660a74630 | ||
|
|
8f29d63441 | ||
|
|
98afa6eb5b | ||
|
|
c06971987d | ||
|
|
e385ecf35d | ||
|
|
62fb0b15e0 | ||
|
|
19da1a4ff3 | ||
|
|
a798a40fab | ||
|
|
7982a1373f | ||
|
|
a812637cf0 | ||
|
|
53c4d3945a | ||
|
|
2e7519cf29 | ||
|
|
acad210d57 | ||
|
|
ba5807932e | ||
|
|
59d572ae18 | ||
|
|
14aa8659ee | ||
|
|
59c8ed4ef8 | ||
|
|
b886a0572d | ||
|
|
42755cac8a | ||
|
|
ab1306357e | ||
|
|
fa506f4212 | ||
|
|
364aed858f | ||
|
|
58703597e3 | ||
|
|
5d4b42d519 | ||
|
|
cd55db7ed7 | ||
|
|
ca9b3678dc | ||
|
|
395e802b6b | ||
|
|
f38b92a2e2 | ||
|
|
5c6fef71bd | ||
|
|
f3780baedc | ||
|
|
388656f6e0 | ||
|
|
df60f103cc | ||
|
|
7a977d74dc | ||
|
|
7336c57e80 | ||
|
|
66963ca3ac | ||
|
|
1bb1afd1b7 | ||
|
|
52cfc40c24 | ||
|
|
debb058889 | ||
|
|
3d5e3ac9a0 | ||
|
|
a06cb2e031 |
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
oss.js
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
15
copy.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const src = path.resolve(__dirname, './dist/index.html')
|
||||
const dest = path.resolve(__dirname, './index.html')
|
||||
|
||||
if (fs.existsSync(dest)) {
|
||||
fs.unlinkSync(dest)
|
||||
}
|
||||
|
||||
if (fs.existsSync(src)) {
|
||||
fs.copyFileSync(src, dest)
|
||||
fs.unlinkSync(src)
|
||||
}
|
||||
|
||||
BIN
docs/assets/1.gif
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/assets/2.gif
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/assets/2021-07-21-19-54-48.gif
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
docs/assets/3.gif
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/assets/image-20210717174716810.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/assets/image-20210717202607786.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/assets/image-20210717210355763.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/assets/image-20210717224527681.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/assets/image-20210717230604086.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/assets/image-20210717230805200.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/image-20210717230808662.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/image-20210718083006111.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/assets/image-20210718083616076.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/assets/image-20210718092817830.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/assets/image-20210718102434530.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/assets/image-20210718110652705.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/assets/image-20210718111334085.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
docs/assets/image-20210718134525691.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/assets/image-20210718184835414.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
docs/assets/image-20210718191124627.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
docs/assets/image-20210718221814590.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/assets/image-20210718222150055.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/assets/image-20210718222612078.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
docs/assets/image-20210720191943989.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
docs/assets/image-20210720192008277.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/assets/image-20210720200816281.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/assets/image-20210721183307656.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/assets/image-20210721183340310.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/assets/image-20210721183823754.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/assets/image-20210721184140488.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/assets/image-20210721185453825.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
docs/assets/image-20210721190700979.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/assets/swdt.jpg
Normal file
|
After Width: | Height: | Size: 254 KiB |
1317
docs/web思维导图实现的技术点分析.md
Normal file
1
index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!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,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d20ec02.10aa67e3.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.2d06497a.js" rel="prefetch"><link href="dist/js/chunk-5397ae43.e756f28b.js" rel="prefetch"><link href="dist/css/app.f735ed26.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.f631d5ff.css" rel="preload" as="style"><link href="dist/js/app.2fa527aa.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.68a34765.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.f631d5ff.css" rel="stylesheet"><link href="dist/css/app.f735ed26.css" 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 src="dist/js/chunk-vendors.68a34765.js"></script><script src="dist/js/app.2fa527aa.js"></script></body></html>
|
||||
17
simple-mind-map/.eslintrc.js
Normal file
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": 'babel-eslint',
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
10
simple-mind-map/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
src/assets
|
||||
*/.DS_Store
|
||||
dist
|
||||
example
|
||||
node_modules
|
||||
*.json
|
||||
*.md
|
||||
.eslintrc.js
|
||||
.prettierignore
|
||||
.prettierrc
|
||||
5
simple-mind-map/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
semi: false
|
||||
singleQuote: true
|
||||
printWidth: 80
|
||||
trailingComma: 'none'
|
||||
arrowParens: 'avoid'
|
||||
3
simple-mind-map/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 一个web思维导图的简单实现
|
||||
|
||||
详细文档见:[https://github.com/wanglin2/mind-map](https://github.com/wanglin2/mind-map)
|
||||
BIN
simple-mind-map/dist/img/blueSky.3c7f8ccb.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
simple-mind-map/dist/img/brainImpairedPink.511fee22.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
simple-mind-map/dist/img/catalogOrganization.380bb277.jpg
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
simple-mind-map/dist/img/classic.733f273c.jpg
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
simple-mind-map/dist/img/classic2.cdfe2a8d.jpg
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
simple-mind-map/dist/img/classic3.19d6c347.jpg
vendored
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
simple-mind-map/dist/img/classic4.087902fc.jpg
vendored
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
simple-mind-map/dist/img/classicBlue.4b8243c6.jpg
vendored
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
simple-mind-map/dist/img/classicGreen.c2ae7bde.jpg
vendored
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
simple-mind-map/dist/img/dark.894c1d36.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
simple-mind-map/dist/img/dark2.c49dc11c.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
simple-mind-map/dist/img/default.1312a3ba.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
simple-mind-map/dist/img/earthYellow.c35e546d.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
simple-mind-map/dist/img/freshGreen.0e344e3e.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
simple-mind-map/dist/img/freshRed.1c5bde77.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
simple-mind-map/dist/img/gold.3093b3c8.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
simple-mind-map/dist/img/greenLeaf.6789e8fc.jpg
vendored
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
simple-mind-map/dist/img/logicalStructure.624920ce.jpg
vendored
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
simple-mind-map/dist/img/mindMap.223b38aa.jpg
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
simple-mind-map/dist/img/minions.c2a93f9e.jpg
vendored
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
simple-mind-map/dist/img/mint.7933f60a.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
simple-mind-map/dist/img/organizationStructure.8064f4da.jpg
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
simple-mind-map/dist/img/pinkGrape.32c2587b.jpg
vendored
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
simple-mind-map/dist/img/romanticPurple.7607e58a.jpg
vendored
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
simple-mind-map/dist/img/skyGreen.4cfa829a.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
simple-mind-map/dist/img/vitalityOrange.5dd9014f.jpg
vendored
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
929
simple-mind-map/example/exampleData.js
Normal file
@@ -0,0 +1,929 @@
|
||||
const createFullData = () => {
|
||||
return {
|
||||
"image": "/enJFNMHnedQTYTESGfDkctCp2.jpeg",
|
||||
"imageTitle": "图片名称",
|
||||
"imageSize": {
|
||||
"width": 1000,
|
||||
"height": 563
|
||||
},
|
||||
"icon": ['priority_1'],
|
||||
"tag": ["标签1", "标签2"],
|
||||
"hyperlink": "http://lxqnsys.com/",
|
||||
"hyperlinkTitle": "理想青年实验室",
|
||||
"note": "理想青年实验室\n一个有意思的角落",
|
||||
// 自定义位置
|
||||
// "customLeft": 1318,
|
||||
// "customTop": 374.5
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-15 22:23:24
|
||||
* @Desc: 节点较多示例数据
|
||||
*/
|
||||
const data1 = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点1",
|
||||
"expand": true,
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
...createFullData()
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
...createFullData()
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点2",
|
||||
"expand": true,
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点3",
|
||||
"expand": true,
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点4",
|
||||
"expand": true,
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
},
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-12 13:49:43
|
||||
* @Desc: 真实场景数据
|
||||
*/
|
||||
const data2 = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "一周安排"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "生活"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "锻炼"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "晨跑"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "7:00-8:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "夜跑"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "20:00-21:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "饮食"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "早餐"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "8:30"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "午餐"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "11:30"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "晚餐"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "19:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "休息"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "午休"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "12:30-13:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "晚休"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "23:00-6:30"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "工作日\n周一至周五"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "日常工作"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "9:00-18:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "工作总结"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "21:00-22:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "学习"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "工作日"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "早间新闻"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "8:00-8:30"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "阅读"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "21:00-23:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "休息日"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "财务管理"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "9:00-10:30"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "职场技能"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "14:00-15:30"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "其他书籍"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "16:00-18:00"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "休闲娱乐"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "看电影"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "1~2部"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "逛街"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "1~2次"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-12 14:29:10
|
||||
* @Desc: 极简数据
|
||||
*/
|
||||
const data3 = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "分支主题"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "分支主题"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const data4 = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点1"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点1-1"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点1-2"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点1-2-1"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点1-2-2"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点1-2-3"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点2"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-1"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-1-1-1"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "子节点2-2"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 带概要
|
||||
const data5 = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "二级节点",
|
||||
"generalization": {
|
||||
"text": "概要",
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"data": {
|
||||
"text": "分支主题"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text": "分支主题"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const rootData = {
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
// ...data1,
|
||||
// ...data2,
|
||||
// ...data3,
|
||||
// ...data4,
|
||||
...data5,
|
||||
// ...rootData,
|
||||
"theme": {
|
||||
"template": "classic4",
|
||||
"config": {
|
||||
// 自定义配置...
|
||||
}
|
||||
},
|
||||
"layout": "logicalStructure",
|
||||
// "layout": "mindMap",
|
||||
// "layout": "catalogOrganization"
|
||||
// "layout": "organizationStructure"
|
||||
}
|
||||
66
simple-mind-map/example/exportFullData.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"layout": "logicalStructure",
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "二级节点",
|
||||
"generalization": {
|
||||
"text": "概要",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}]
|
||||
}]
|
||||
},
|
||||
"theme": {
|
||||
"template": "classic4",
|
||||
"config": {}
|
||||
},
|
||||
"view": {
|
||||
"transform": {
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"shear": 0,
|
||||
"rotate": 0,
|
||||
"translateX": 0,
|
||||
"translateY": 0,
|
||||
"originX": 0,
|
||||
"originY": 0,
|
||||
"a": 1,
|
||||
"b": 0,
|
||||
"c": 0,
|
||||
"d": 1,
|
||||
"e": 0,
|
||||
"f": 0
|
||||
},
|
||||
"state": {
|
||||
"scale": 1,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"sx": 0,
|
||||
"sy": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
435
simple-mind-map/index.js
Normal file
@@ -0,0 +1,435 @@
|
||||
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 BatchExecution from './src/BatchExecution'
|
||||
import Export from './src/Export'
|
||||
import Select from './src/Select'
|
||||
import Drag from './src/Drag'
|
||||
import MiniMap from './src/MiniMap'
|
||||
import { layoutValueList } from './src/utils/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import xmind from './src/parse/xmind'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import KeyboardNavigation from './src/KeyboardNavigation'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: 'logicalStructure',
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.1,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 11:18:47
|
||||
* @Desc: 思维导图
|
||||
*/
|
||||
class MindMap {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 11:19:01
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
// 合并选项
|
||||
this.opt = this.handleOpt(merge(defaultOpt, opt))
|
||||
|
||||
// 容器元素
|
||||
this.el = this.opt.el
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
|
||||
// 画布宽高
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
|
||||
// 画布
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点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.miniMap = new MiniMap({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 导出类
|
||||
this.doExport = new Export({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 选择类
|
||||
this.select = new Select({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 拖动类
|
||||
this.drag = new Drag({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 键盘导航类
|
||||
this.keyboardNavigation = new KeyboardNavigation({
|
||||
mindMap: this
|
||||
})
|
||||
|
||||
// 批量执行类
|
||||
this.batchExecution = new BatchExecution()
|
||||
|
||||
// 初始渲染
|
||||
this.reRender()
|
||||
setTimeout(() => {
|
||||
this.command.addHistory()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:15:22
|
||||
* @Desc: 配置参数处理
|
||||
*/
|
||||
handleOpt(opt) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = 'logicalStructure'
|
||||
}
|
||||
// 检查主题配置
|
||||
opt.theme = opt.theme && theme[opt.theme] ? opt.theme : 'default'
|
||||
return opt
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-06 18:47:29
|
||||
* @Desc: 渲染,部分渲染
|
||||
*/
|
||||
render() {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.initTheme()
|
||||
this.renderer.reRender = false
|
||||
this.renderer.render()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-08 22:05:11
|
||||
* @Desc: 重新渲染
|
||||
*/
|
||||
reRender() {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 21:16:52
|
||||
* @Desc: 容器尺寸变化,调整尺寸
|
||||
*/
|
||||
resize() {
|
||||
this.elRect = this.el.getBoundingClientRect()
|
||||
this.width = this.elRect.width
|
||||
this.height = this.elRect.height
|
||||
this.svg.size(this.width, this.height)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:25:50
|
||||
* @Desc: 监听事件
|
||||
*/
|
||||
on(event, fn) {
|
||||
this.event.on(event, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:51:35
|
||||
* @Desc: 触发事件
|
||||
*/
|
||||
emit(event, ...args) {
|
||||
this.event.emit(event, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:53:54
|
||||
* @Desc: 解绑事件
|
||||
*/
|
||||
off(event, fn) {
|
||||
this.event.off(event, fn)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:32:43
|
||||
* @Desc: 设置主题
|
||||
*/
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
this.themeConfig = merge(theme[this.opt.theme], this.opt.themeConfig)
|
||||
// 设置背景样式
|
||||
Style.setBackgroundStyle(this.el, this.themeConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:52:08
|
||||
* @Desc: 设置主题
|
||||
*/
|
||||
setTheme(theme) {
|
||||
this.renderer.clearAllActive()
|
||||
this.opt.theme = theme
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-25 23:52:37
|
||||
* @Desc: 获取当前主题
|
||||
*/
|
||||
getTheme() {
|
||||
return this.opt.theme
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 13:50:17
|
||||
* @Desc: 设置主题配置
|
||||
*/
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-01 10:38:34
|
||||
* @Desc: 获取自定义主题配置
|
||||
*/
|
||||
getCustomThemeConfig() {
|
||||
return this.opt.themeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 14:01:29
|
||||
* @Desc: 获取某个主题配置值
|
||||
*/
|
||||
getThemeConfig(prop) {
|
||||
return prop === undefined ? this.themeConfig : this.themeConfig[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 16:17:06
|
||||
* @Desc: 获取当前布局结构
|
||||
*/
|
||||
getLayout() {
|
||||
return this.opt.layout
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 16:17:33
|
||||
* @Desc: 设置布局结构
|
||||
*/
|
||||
setLayout(layout) {
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(layout)) {
|
||||
layout = 'logicalStructure'
|
||||
}
|
||||
this.opt.layout = layout
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:01:00
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
execCommand(...args) {
|
||||
this.command.exec(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:58:12
|
||||
* @Desc: 动态设置思维导图数据,纯节点数据
|
||||
*/
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
this.command.clearHistory()
|
||||
this.renderer.renderTree = data
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 16:39:13
|
||||
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
*/
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
}
|
||||
if (data.layout) {
|
||||
this.setLayout(data.layout)
|
||||
}
|
||||
if (data.theme) {
|
||||
if (data.theme.template) {
|
||||
this.setTheme(data.theme.template)
|
||||
}
|
||||
if (data.theme.config) {
|
||||
this.setThemeConfig(data.theme.config)
|
||||
}
|
||||
}
|
||||
if (data.view) {
|
||||
this.view.setTransformData(data.view)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-24 14:42:07
|
||||
* @Desc: 获取思维导图数据,节点树、主题、布局等
|
||||
*/
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
layout: this.getLayout(),
|
||||
root: nodeData,
|
||||
theme: {
|
||||
template: this.getTheme(),
|
||||
config: this.getCustomThemeConfig()
|
||||
},
|
||||
view: this.view.getTransformData()
|
||||
}
|
||||
} else {
|
||||
data = nodeData
|
||||
}
|
||||
return simpleDeepClone(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:06:38
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(...args) {
|
||||
let result = await this.doExport.export(...args)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 09:20:03
|
||||
* @Desc: 转换位置
|
||||
*/
|
||||
toPos(x, y) {
|
||||
return {
|
||||
x: x - this.elRect.left,
|
||||
y: y - this.elRect.top
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-06-08 14:12:38
|
||||
* @Desc: 设置只读模式、编辑模式
|
||||
*/
|
||||
setMode(mode) {
|
||||
if (!['readonly', 'edit'].includes(mode)) {
|
||||
return
|
||||
}
|
||||
this.opt.readonly = mode === 'readonly'
|
||||
if (this.opt.readonly) {
|
||||
// 取消当前激活的元素
|
||||
this.renderer.clearAllActive()
|
||||
}
|
||||
this.emit('mode_change', mode)
|
||||
}
|
||||
}
|
||||
|
||||
MindMap.xmind = xmind
|
||||
|
||||
export default MindMap
|
||||
46
simple-mind-map/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.20",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
"name": "街角小林",
|
||||
"email": "1013335014@qq.com"
|
||||
},
|
||||
{
|
||||
"name": "理想青年实验室",
|
||||
"url": "http://lxqnsys.com/"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wanglin2/mind-map"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src/",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"module": "index.js",
|
||||
"main": "./dist/simpleMindMap.umd.min.js",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
"svg",
|
||||
"mind-map",
|
||||
"mindMap",
|
||||
"MindMap"
|
||||
],
|
||||
"devDependencies": {
|
||||
"eslint": "^8.25.0",
|
||||
"prettier": "^2.7.1"
|
||||
}
|
||||
}
|
||||
85
simple-mind-map/src/BatchExecution.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-27 13:16:23
|
||||
* @Desc: 在下一个事件循环里执行任务
|
||||
*/
|
||||
const nextTick = function (fn, ctx) {
|
||||
let pending = false
|
||||
let timerFunc = null
|
||||
let handle = () => {
|
||||
pending = false
|
||||
ctx ? fn.call(ctx) : fn()
|
||||
}
|
||||
// 支持MutationObserver接口的话使用MutationObserver
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
let counter = 1
|
||||
let observer = new MutationObserver(handle)
|
||||
let textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2 // counter会在0和1两者循环变化
|
||||
textNode.data = counter // 节点变化会触发回调handle,
|
||||
}
|
||||
} else {
|
||||
// 否则使用定时器
|
||||
timerFunc = setTimeout
|
||||
}
|
||||
return function () {
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(handle, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-26 22:40:52
|
||||
* @Desc: 批量执行
|
||||
*/
|
||||
class BatchExecution {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-26 22:41:41
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor() {
|
||||
this.has = {}
|
||||
this.queue = []
|
||||
this.nextTick = nextTick(this.flush, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-27 12:54:04
|
||||
* @Desc: 添加任务
|
||||
*/
|
||||
push(name, fn) {
|
||||
if (this.has[name]) {
|
||||
return
|
||||
}
|
||||
this.has[name] = true
|
||||
this.queue.push({
|
||||
name,
|
||||
fn
|
||||
})
|
||||
this.nextTick()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-27 13:09:24
|
||||
* @Desc: 执行队列
|
||||
*/
|
||||
flush() {
|
||||
let fns = this.queue.slice(0)
|
||||
this.queue = []
|
||||
fns.forEach(({ name, fn }) => {
|
||||
this.has[name] = false
|
||||
fn()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default BatchExecution
|
||||
160
simple-mind-map/src/Command.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import { copyRenderTree, simpleDeepClone } from './utils'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:10:06
|
||||
* @Desc: 命令类
|
||||
*/
|
||||
class Command {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:10:24
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt = {}) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.commands = {}
|
||||
this.history = []
|
||||
this.activeHistoryIndex = 0
|
||||
// 注册快捷键
|
||||
this.registerShortcutKeys()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 23:06:55
|
||||
* @Desc: 清空历史数据
|
||||
*/
|
||||
clearHistory() {
|
||||
this.history = []
|
||||
this.activeHistoryIndex = 0
|
||||
this.mindMap.emit('back_forward', 0, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 23:23:19
|
||||
* @Desc: 注册快捷键
|
||||
*/
|
||||
registerShortcutKeys() {
|
||||
this.mindMap.keyCommand.addShortcut('Control+z', () => {
|
||||
this.mindMap.execCommand('BACK')
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+y', () => {
|
||||
this.mindMap.execCommand('FORWARD')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:12:30
|
||||
* @Desc: 执行命令
|
||||
*/
|
||||
exec(name, ...args) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].forEach(fn => {
|
||||
fn(...args)
|
||||
})
|
||||
if (name === 'BACK' || name === 'FORWARD') {
|
||||
return
|
||||
}
|
||||
this.addHistory()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 13:13:01
|
||||
* @Desc: 添加命令
|
||||
*/
|
||||
add(name, fn) {
|
||||
if (this.commands[name]) {
|
||||
this.commands[name].push(fn)
|
||||
} else {
|
||||
this.commands[name] = [fn]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-15 23:02:41
|
||||
* @Desc: 移除命令
|
||||
*/
|
||||
remove(name, fn) {
|
||||
if (!this.commands[name]) {
|
||||
return
|
||||
}
|
||||
if (!fn) {
|
||||
this.commands[name] = []
|
||||
delete this.commands[name]
|
||||
} else {
|
||||
let index = this.commands[name].find(item => {
|
||||
return item === fn
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.commands[name].splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 14:35:43
|
||||
* @Desc: 添加回退数据
|
||||
*/
|
||||
addHistory() {
|
||||
let data = this.getCopyData()
|
||||
this.history.push(simpleDeepClone(data))
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
this.history.length
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 22:34:53
|
||||
* @Desc: 回退
|
||||
*/
|
||||
back(step = 1) {
|
||||
if (this.activeHistoryIndex - step >= 0) {
|
||||
this.activeHistoryIndex -= step
|
||||
this.mindMap.emit(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
this.history.length
|
||||
)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-12 10:45:31
|
||||
* @Desc: 前进
|
||||
*/
|
||||
forward(step = 1) {
|
||||
let len = this.history.length
|
||||
if (this.activeHistoryIndex + step <= len - 1) {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex)
|
||||
return simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 15:02:58
|
||||
* @Desc: 获取渲染树数据副本
|
||||
*/
|
||||
getCopyData() {
|
||||
return copyRenderTree({}, this.mindMap.renderer.renderTree)
|
||||
}
|
||||
}
|
||||
|
||||
export default Command
|
||||
295
simple-mind-map/src/Drag.js
Normal file
@@ -0,0 +1,295 @@
|
||||
import { bfsWalk, throttle } from './utils'
|
||||
import Base from './layouts/Base'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 17:38:55
|
||||
* @Desc: 节点拖动类
|
||||
*/
|
||||
class Drag extends Base {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:35:16
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor({ mindMap }) {
|
||||
super(mindMap.renderer)
|
||||
this.mindMap = mindMap
|
||||
this.reset()
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:33:56
|
||||
* @Desc: 复位
|
||||
*/
|
||||
reset() {
|
||||
// 当前拖拽节点
|
||||
this.node = null
|
||||
// 当前重叠节点
|
||||
this.overlapNode = null
|
||||
// 当前上一个同级节点
|
||||
this.prevNode = null
|
||||
// 当前下一个同级节点
|
||||
this.nextNode = null
|
||||
// 画布的变换数据
|
||||
this.drawTransform = null
|
||||
// 克隆节点
|
||||
this.clone = null
|
||||
// 连接线
|
||||
this.line = null
|
||||
// 同级位置占位符
|
||||
this.placeholder = null
|
||||
// 鼠标按下位置和节点左上角的偏移量
|
||||
this.offsetX = 0
|
||||
this.offsetY = 0
|
||||
// 克隆节点左上角的坐标
|
||||
this.cloneNodeLeft = 0
|
||||
this.cloneNodeTop = 0
|
||||
// 当前鼠标是否按下
|
||||
this.isMousedown = false
|
||||
// 拖拽的鼠标位置变量
|
||||
this.mouseDownX = 0
|
||||
this.mouseDownY = 0
|
||||
this.mouseMoveX = 0
|
||||
this.mouseMoveY = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:36:36
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bindEvent() {
|
||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 300, this)
|
||||
this.mindMap.on('node_mousedown', (node, e) => {
|
||||
if (this.mindMap.opt.readonly || node.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (e.which !== 1 || node.isRoot) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
// 计算鼠标按下的位置距离节点左上角的距离
|
||||
this.drawTransform = this.mindMap.draw.transform()
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.offsetX = x - (node.left * scaleX + translateX)
|
||||
this.offsetY = y - (node.top * scaleY + translateY)
|
||||
this.node = node
|
||||
this.isMousedown = true
|
||||
this.mouseDownX = x
|
||||
this.mouseDownY = y
|
||||
})
|
||||
this.mindMap.on('mousemove', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseMoveX = x
|
||||
this.mouseMoveY = y
|
||||
if (
|
||||
Math.abs(x - this.mouseDownX) <= 10 &&
|
||||
Math.abs(y - this.mouseDownY) <= 10 &&
|
||||
!this.node.isDrag
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.clearAllActive()
|
||||
this.onMove(x, y)
|
||||
})
|
||||
this.onMouseup = this.onMouseup.bind(this)
|
||||
this.mindMap.on('node_mouseup', this.onMouseup)
|
||||
this.mindMap.on('mouseup', this.onMouseup)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:38:02
|
||||
* @Desc: 鼠标松开事件
|
||||
*/
|
||||
onMouseup(e) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.isMousedown = false
|
||||
let _nodeIsDrag = this.node.isDrag
|
||||
this.node.isDrag = false
|
||||
this.node.show()
|
||||
this.removeCloneNode()
|
||||
// 存在重叠子节点,则移动作为其子节点
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, false)
|
||||
this.mindMap.execCommand('MOVE_NODE_TO', this.node, this.overlapNode)
|
||||
} else if (this.prevNode) {
|
||||
// 存在前一个相邻节点,作为其下一个兄弟节点
|
||||
this.mindMap.renderer.setNodeActive(this.prevNode, false)
|
||||
this.mindMap.execCommand('INSERT_AFTER', this.node, this.prevNode)
|
||||
} else if (this.nextNode) {
|
||||
// 存在下一个相邻节点,作为其前一个兄弟节点
|
||||
this.mindMap.renderer.setNodeActive(this.nextNode, false)
|
||||
this.mindMap.execCommand('INSERT_BEFORE', this.node, this.nextNode)
|
||||
} else if (_nodeIsDrag) {
|
||||
// 自定义位置
|
||||
let { x, y } = this.mindMap.toPos(
|
||||
e.clientX - this.offsetX,
|
||||
e.clientY - this.offsetY
|
||||
)
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
x = (x - translateX) / scaleX
|
||||
y = (y - translateY) / scaleY
|
||||
this.node.left = x
|
||||
this.node.top = y
|
||||
this.node.customLeft = x
|
||||
this.node.customTop = y
|
||||
this.mindMap.execCommand('SET_NODE_CUSTOM_POSITION', this.node, x, y)
|
||||
this.mindMap.render()
|
||||
}
|
||||
this.reset()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:34:53
|
||||
* @Desc: 创建克隆节点
|
||||
*/
|
||||
createCloneNode() {
|
||||
if (!this.clone) {
|
||||
// 节点
|
||||
this.clone = this.node.group.clone()
|
||||
this.clone.opacity(0.5)
|
||||
this.clone.css('z-index', 99999)
|
||||
this.node.isDrag = true
|
||||
this.node.hide()
|
||||
// 连接线
|
||||
this.line = this.draw.path()
|
||||
this.line.opacity(0.5)
|
||||
this.node.styleLine(this.line, this.node)
|
||||
// 同级位置占位符
|
||||
this.placeholder = this.draw.rect().fill({
|
||||
color: this.node.style.merge('lineColor', true)
|
||||
})
|
||||
this.mindMap.draw.add(this.clone)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 19:35:16
|
||||
* @Desc: 移除克隆节点
|
||||
*/
|
||||
removeCloneNode() {
|
||||
if (!this.clone) {
|
||||
return
|
||||
}
|
||||
this.clone.remove()
|
||||
this.line.remove()
|
||||
this.placeholder.remove()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-23 18:53:47
|
||||
* @Desc: 拖动中
|
||||
*/
|
||||
onMove(x, y) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.createCloneNode()
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
this.cloneNodeLeft = x - this.offsetX
|
||||
this.cloneNodeTop = y - this.offsetY
|
||||
x = (this.cloneNodeLeft - translateX) / scaleX
|
||||
y = (this.cloneNodeTop - translateY) / scaleY
|
||||
let t = this.clone.transform()
|
||||
this.clone.translate(x - t.translateX, y - t.translateY)
|
||||
// 连接线
|
||||
let parent = this.node.parent
|
||||
this.line.plot(
|
||||
this.quadraticCurvePath(
|
||||
parent.left + parent.width / 2,
|
||||
parent.top + parent.height / 2,
|
||||
x + this.node.width / 2,
|
||||
y + this.node.height / 2
|
||||
)
|
||||
)
|
||||
this.checkOverlapNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 10:20:43
|
||||
* @Desc: 检测重叠节点
|
||||
*/
|
||||
checkOverlapNode() {
|
||||
if (!this.drawTransform) {
|
||||
return
|
||||
}
|
||||
let { scaleX, scaleY, translateX, translateY } = this.drawTransform
|
||||
let checkRight = this.cloneNodeLeft + this.node.width * scaleX
|
||||
let checkBottom = this.cloneNodeTop + this.node.height * scaleX
|
||||
this.overlapNode = null
|
||||
this.prevNode = null
|
||||
this.nextNode = null
|
||||
this.placeholder.size(0, 0)
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
if (node.nodeData.data.isActive) {
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
}
|
||||
if (node === this.node || this.node.isParent(node)) {
|
||||
return
|
||||
}
|
||||
if (this.overlapNode || (this.prevNode && this.nextNode)) {
|
||||
return
|
||||
}
|
||||
let { left, top, width, height } = node
|
||||
let _left = left
|
||||
let _top = top
|
||||
let _bottom = top + height
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
// 检测是否重叠
|
||||
if (!this.overlapNode) {
|
||||
if (
|
||||
left <= checkRight &&
|
||||
right >= this.cloneNodeLeft &&
|
||||
top <= checkBottom &&
|
||||
bottom >= this.cloneNodeTop
|
||||
) {
|
||||
this.overlapNode = node
|
||||
}
|
||||
}
|
||||
// 检测兄弟节点位置
|
||||
if (!this.prevNode && !this.nextNode && !node.isRoot) {// && this.node.isBrother(node)
|
||||
if (left <= checkRight && right >= this.cloneNodeLeft) {
|
||||
if (this.cloneNodeTop > bottom && this.cloneNodeTop <= bottom + 10) {
|
||||
this.prevNode = node
|
||||
this.placeholder.size(node.width, 10).move(_left, _bottom)
|
||||
} else if (checkBottom < top && checkBottom >= top - 10) {
|
||||
this.nextNode = node
|
||||
this.placeholder.size(node.width, 10).move(_left, _top - 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (this.overlapNode) {
|
||||
this.mindMap.renderer.setNodeActive(this.overlapNode, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Drag
|
||||
195
simple-mind-map/src/Event.js
Normal file
@@ -0,0 +1,195 @@
|
||||
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.isLeftMousedown = 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)
|
||||
this.onContextmenu = this.onContextmenu.bind(this)
|
||||
this.onSvgMousedown = this.onSvgMousedown.bind(this)
|
||||
this.onKeyup = this.onKeyup.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 14:53:43
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bind() {
|
||||
this.mindMap.svg.on('click', this.onDrawClick)
|
||||
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
|
||||
this.mindMap.svg.on('mousedown', this.onSvgMousedown)
|
||||
window.addEventListener('mousemove', this.onMousemove)
|
||||
window.addEventListener('mouseup', this.onMouseup)
|
||||
// 兼容火狐浏览器
|
||||
if (window.navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
|
||||
this.mindMap.el.addEventListener('DOMMouseScroll', this.onMousewheel)
|
||||
} else {
|
||||
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
|
||||
}
|
||||
this.mindMap.svg.on('contextmenu', this.onContextmenu)
|
||||
window.addEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:40:51
|
||||
* @Desc: 解绑事件
|
||||
*/
|
||||
unbind() {
|
||||
this.mindMap.svg.off('click', this.onDrawClick)
|
||||
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
|
||||
window.removeEventListener('mousemove', this.onMousemove)
|
||||
window.removeEventListener('mouseup', this.onMouseup)
|
||||
this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel)
|
||||
this.mindMap.svg.off('contextmenu', this.onContextmenu)
|
||||
window.removeEventListener('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:19:39
|
||||
* @Desc: 画布的单击事件
|
||||
*/
|
||||
onDrawClick(e) {
|
||||
this.emit('draw_click', e)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-16 13:37:30
|
||||
* @Desc: svg画布的鼠标按下事件
|
||||
*/
|
||||
onSvgMousedown(e) {
|
||||
this.emit('svg_mousedown', e)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:17:35
|
||||
* @Desc: 鼠标按下事件
|
||||
*/
|
||||
onMousedown(e) {
|
||||
// e.preventDefault()
|
||||
// 鼠标左键
|
||||
if (e.which === 1) {
|
||||
this.isLeftMousedown = 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.isLeftMousedown) {
|
||||
this.emit('drag', e, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:18:57
|
||||
* @Desc: 鼠标松开事件
|
||||
*/
|
||||
onMouseup(e) {
|
||||
this.isLeftMousedown = false
|
||||
this.emit('mouseup', e, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:46:27
|
||||
* @Desc: 鼠标滚动
|
||||
*/
|
||||
onMousewheel(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let dir
|
||||
if ((e.wheelDeltaY || e.detail) > 0) {
|
||||
dir = 'up'
|
||||
} else {
|
||||
dir = 'down'
|
||||
}
|
||||
this.emit('mousewheel', e, dir, this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:34:13
|
||||
* @Desc: 鼠标右键菜单事件
|
||||
*/
|
||||
onContextmenu(e) {
|
||||
e.preventDefault()
|
||||
this.emit('contextmenu', e)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 11:12:11
|
||||
* @Desc: 按键松开事件
|
||||
*/
|
||||
onKeyup(e) {
|
||||
this.emit('keyup', e)
|
||||
}
|
||||
}
|
||||
|
||||
export default Event
|
||||
265
simple-mind-map/src/Export.js
Normal file
@@ -0,0 +1,265 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:05:16
|
||||
* @Desc: 导出类
|
||||
*/
|
||||
class Export {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:05:42
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.exportPadding = this.mindMap.opt.exportPadding
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-02 07:44:06
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(type, isDownload = true, name = '思维导图', ...args) {
|
||||
if (this[type]) {
|
||||
let result = await this[type](name, ...args)
|
||||
if (isDownload && type !== 'pdf') {
|
||||
downloadFile(result, name + '.' + type)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 14:57:40
|
||||
* @Desc: 获取svg数据
|
||||
*/
|
||||
async getSvgData() {
|
||||
let { svg, svgHTML } = this.mindMap.miniMap.getMiniMap()
|
||||
// 把图片的url转换成data:url类型,否则导出会丢失图片
|
||||
let imageList = svg.find('image')
|
||||
let task = imageList.map(async item => {
|
||||
let imgUlr = item.attr('href') || item.attr('xlink:href')
|
||||
let imgData = await imgToDataUrl(imgUlr)
|
||||
item.attr('href', imgData)
|
||||
})
|
||||
await Promise.all(task)
|
||||
return {
|
||||
node: svg,
|
||||
str: svgHTML
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:25:19
|
||||
* @Desc: svg转png
|
||||
*/
|
||||
svgToPng(svgSrc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||
img.setAttribute('crossOrigin', 'anonymous')
|
||||
img.onload = async () => {
|
||||
try {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = img.width + this.exportPadding * 2
|
||||
canvas.height = img.height + this.exportPadding * 2
|
||||
let ctx = canvas.getContext('2d')
|
||||
// 绘制背景
|
||||
await this.drawBackgroundToCanvas(ctx, canvas.width, canvas.height)
|
||||
// 图片绘制到canvas里
|
||||
ctx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
img.width,
|
||||
img.height,
|
||||
this.exportPadding,
|
||||
this.exportPadding,
|
||||
img.width,
|
||||
img.height
|
||||
)
|
||||
resolve(canvas.toDataURL())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
img.onerror = e => {
|
||||
reject(e)
|
||||
}
|
||||
img.src = svgSrc
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:32:07
|
||||
* @Desc: 在canvas上绘制思维导图背景
|
||||
*/
|
||||
drawBackgroundToCanvas(ctx, width, height) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
ctx.save()
|
||||
ctx.rect(0, 0, width, height)
|
||||
ctx.fillStyle = backgroundColor
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
ctx.save()
|
||||
let img = new Image()
|
||||
img.src = backgroundImage
|
||||
img.onload = () => {
|
||||
let pat = ctx.createPattern(img, backgroundRepeat)
|
||||
ctx.rect(0, 0, width, height)
|
||||
ctx.fillStyle = pat
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
resolve()
|
||||
}
|
||||
img.onerror = e => {
|
||||
reject(e)
|
||||
}
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:09:51
|
||||
* @Desc: 导出为png
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||
*/
|
||||
async png() {
|
||||
let { str } = await this.getSvgData()
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
// 转换成data:url数据
|
||||
let svgUrl = URL.createObjectURL(blob)
|
||||
// 绘制到canvas上
|
||||
let imgDataUrl = await this.svgToPng(svgUrl)
|
||||
URL.revokeObjectURL(svgUrl)
|
||||
return imgDataUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-08 19:23:08
|
||||
* @Desc: 导出为pdf
|
||||
*/
|
||||
async pdf(name) {
|
||||
let img = await this.png()
|
||||
let pdf = new JsPDF('', 'pt', 'a4')
|
||||
let a4Width = 595
|
||||
let a4Height = 841
|
||||
let a4Ratio = a4Width / a4Height
|
||||
let image = new Image()
|
||||
image.onload = () => {
|
||||
let imageWidth = image.width
|
||||
let imageHeight = image.height
|
||||
let imageRatio = imageWidth / imageHeight
|
||||
let w, h
|
||||
if (imageWidth <= a4Width && imageHeight <= a4Height) {
|
||||
// 使用图片原始宽高
|
||||
w = imageWidth
|
||||
h = imageHeight
|
||||
} else if (a4Ratio > imageRatio) {
|
||||
// 以a4Height为高度,缩放图片宽度
|
||||
w = imageRatio * a4Height
|
||||
h = a4Height
|
||||
} else {
|
||||
// 以a4Width为宽度,缩放图片高度
|
||||
w = a4Width
|
||||
h = a4Width / imageRatio
|
||||
}
|
||||
pdf.addImage(img, 'PNG', (a4Width - w) / 2, (a4Height - h) / 2, w, h)
|
||||
pdf.save(name)
|
||||
}
|
||||
image.src = img
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 15:32:07
|
||||
* @Desc: 在svg上绘制思维导图背景
|
||||
*/
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 14:54:07
|
||||
* @Desc: 导出为svg
|
||||
*/
|
||||
async svg(name) {
|
||||
let { node } = await this.getSvgData()
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:19:17
|
||||
* @Desc: 导出为json
|
||||
*/
|
||||
json(name, withConfig = true) {
|
||||
let data = this.mindMap.getData(withConfig)
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:24:24
|
||||
* @Desc: 专有文件,其实就是json文件
|
||||
*/
|
||||
smm(name, withConfig) {
|
||||
return this.json(name, withConfig)
|
||||
}
|
||||
}
|
||||
|
||||
export default Export
|
||||
200
simple-mind-map/src/KeyCommand.js
Normal file
@@ -0,0 +1,200 @@
|
||||
import { keyMap } from './utils/keyMap'
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:20:46
|
||||
* @Desc: 快捷按键、命令处理类
|
||||
*/
|
||||
export default class KeyCommand {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:21:32
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.shortcutMap = {
|
||||
//Enter: [fn]
|
||||
}
|
||||
this.shortcutMapCache = {}
|
||||
this.isPause = false
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:57:55
|
||||
* @Desc: 暂停快捷键响应
|
||||
*/
|
||||
pause() {
|
||||
this.isPause = true
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:58:43
|
||||
* @Desc: 恢复快捷键响应
|
||||
*/
|
||||
recovery() {
|
||||
this.isPause = false
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-16 16:29:01
|
||||
* @Desc: 保存当前注册的快捷键数据,然后清空快捷键数据
|
||||
*/
|
||||
save() {
|
||||
this.shortcutMapCache = this.shortcutMap
|
||||
this.shortcutMap = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-16 16:29:38
|
||||
* @Desc: 恢复保存的快捷键数据,然后清空缓存数据
|
||||
*/
|
||||
restore() {
|
||||
this.shortcutMap = this.shortcutMapCache
|
||||
this.shortcutMapCache = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:23:22
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bindEvent() {
|
||||
window.addEventListener('keydown', e => {
|
||||
if (this.isPause) {
|
||||
return
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:24:53
|
||||
* @Desc: 检查键值是否符合
|
||||
*/
|
||||
checkKey(e, key) {
|
||||
let o = this.getOriginEventCodeArr(e)
|
||||
let k = this.getKeyCodeArr(key)
|
||||
if (o.length !== k.length) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < o.length; i++) {
|
||||
let index = k.findIndex(item => {
|
||||
return item === o[i]
|
||||
})
|
||||
if (index === -1) {
|
||||
return false
|
||||
} else {
|
||||
k.splice(index, 1)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:15:19
|
||||
* @Desc: 获取事件对象里的键值数组
|
||||
*/
|
||||
getOriginEventCodeArr(e) {
|
||||
let arr = []
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
arr.push(keyMap['Control'])
|
||||
}
|
||||
if (e.altKey) {
|
||||
arr.push(keyMap['Alt'])
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
arr.push(keyMap['Shift'])
|
||||
}
|
||||
if (!arr.includes(e.keyCode)) {
|
||||
arr.push(e.keyCode)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 19:40:11
|
||||
* @Desc: 获取快捷键对应的键值数组
|
||||
*/
|
||||
getKeyCodeArr(key) {
|
||||
let keyArr = key.split(/\s*\+\s*/)
|
||||
let arr = []
|
||||
keyArr.forEach(item => {
|
||||
arr.push(keyMap[item])
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 15:23:00
|
||||
* @Desc: 添加快捷键命令
|
||||
* Enter
|
||||
* Tab | Insert
|
||||
* Shift + a
|
||||
*/
|
||||
addShortcut(key, fn) {
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
if (this.shortcutMap[item]) {
|
||||
this.shortcutMap[item].push(fn)
|
||||
} else {
|
||||
this.shortcutMap[item] = [fn]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-27 14:06:16
|
||||
* @Desc: 移除快捷键命令
|
||||
*/
|
||||
removeShortcut(key, fn) {
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
if (this.shortcutMap[item]) {
|
||||
if (fn) {
|
||||
let index = this.shortcutMap[item].findIndex(f => {
|
||||
return f === fn
|
||||
})
|
||||
if (index !== -1) {
|
||||
this.shortcutMap[item].splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
this.shortcutMap[item] = []
|
||||
delete this.shortcutMap[item]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-14 08:49:58
|
||||
* @Desc: 获取指定快捷键的处理函数
|
||||
*/
|
||||
getShortcutFn(key) {
|
||||
let res = []
|
||||
key.split(/\s*\|\s*/).forEach(item => {
|
||||
res = this.shortcutMap[item] || []
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
276
simple-mind-map/src/KeyboardNavigation.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import { isKey } from './utils/keyMap'
|
||||
import { bfsWalk } from './utils'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 11:06:50
|
||||
* @Desc: 键盘导航类
|
||||
*/
|
||||
export default class KeyboardNavigation {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 11:07:24
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.opt = opt
|
||||
this.mindMap = opt.mindMap
|
||||
this.onKeyup = this.onKeyup.bind(this)
|
||||
this.mindMap.on('keyup', this.onKeyup)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 14:12:27
|
||||
* @Desc: 处理按键事件
|
||||
*/
|
||||
onKeyup(e) {
|
||||
;['Left', 'Up', 'Right', 'Down'].forEach(dir => {
|
||||
if (isKey(e, dir)) {
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.focus(dir)
|
||||
} else {
|
||||
let root = this.mindMap.renderer.root
|
||||
this.mindMap.renderer.moveNodeToCenter(root)
|
||||
root.active()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 14:12:39
|
||||
* @Desc: 聚焦到下一个节点
|
||||
*/
|
||||
focus(dir) {
|
||||
// 当前聚焦的节点
|
||||
let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
|
||||
// 当前聚焦节点的位置信息
|
||||
let currentActiveNodeRect = this.getNodeRect(currentActiveNode)
|
||||
// 寻找的下一个聚焦节点
|
||||
let targetNode = null
|
||||
let targetDis = Infinity
|
||||
// 保存并维护距离最近的节点
|
||||
let checkNodeDis = (rect, node) => {
|
||||
let dis = this.getDistance(currentActiveNodeRect, rect)
|
||||
if (dis < targetDis) {
|
||||
targetNode = node
|
||||
targetDis = dis
|
||||
}
|
||||
}
|
||||
|
||||
// 第一优先级:阴影算法
|
||||
this.getFocusNodeByShadowAlgorithm({
|
||||
currentActiveNode,
|
||||
currentActiveNodeRect,
|
||||
dir,
|
||||
checkNodeDis
|
||||
})
|
||||
|
||||
// 第二优先级:区域算法
|
||||
if (!targetNode) {
|
||||
this.getFocusNodeByAreaAlgorithm({
|
||||
currentActiveNode,
|
||||
currentActiveNodeRect,
|
||||
dir,
|
||||
checkNodeDis
|
||||
})
|
||||
}
|
||||
|
||||
// 第三优先级:简单算法
|
||||
if (!targetNode) {
|
||||
this.getFocusNodeBySimpleAlgorithm({
|
||||
currentActiveNode,
|
||||
currentActiveNodeRect,
|
||||
dir,
|
||||
checkNodeDis
|
||||
})
|
||||
}
|
||||
|
||||
// 找到了则让目标节点聚焦
|
||||
if (targetNode) {
|
||||
this.mindMap.renderer.moveNodeToCenter(targetNode)
|
||||
targetNode.active()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-12 16:22:54
|
||||
* @Desc: 1.简单算法
|
||||
*/
|
||||
getFocusNodeBySimpleAlgorithm({
|
||||
currentActiveNode,
|
||||
currentActiveNodeRect,
|
||||
dir,
|
||||
checkNodeDis
|
||||
}) {
|
||||
// 遍历节点树
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
// 跳过当前聚焦的节点
|
||||
if (node === currentActiveNode) return
|
||||
// 当前遍历到的节点的位置信息
|
||||
let rect = this.getNodeRect(node)
|
||||
let { left, top, right, bottom } = rect
|
||||
let match = false
|
||||
// 按下了左方向键
|
||||
if (dir === 'Left') {
|
||||
// 判断节点是否在当前节点的左侧
|
||||
match = right <= currentActiveNodeRect.left
|
||||
// 按下了右方向键
|
||||
} else if (dir === 'Right') {
|
||||
// 判断节点是否在当前节点的右侧
|
||||
match = left >= currentActiveNodeRect.right
|
||||
// 按下了上方向键
|
||||
} else if (dir === 'Up') {
|
||||
// 判断节点是否在当前节点的上面
|
||||
match = bottom <= currentActiveNodeRect.top
|
||||
// 按下了下方向键
|
||||
} else if (dir === 'Down') {
|
||||
// 判断节点是否在当前节点的下面
|
||||
match = top >= currentActiveNodeRect.bottom
|
||||
}
|
||||
// 符合要求,判断是否是最近的节点
|
||||
if (match) {
|
||||
checkNodeDis(rect, node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-12 16:24:54
|
||||
* @Desc: 2.阴影算法
|
||||
*/
|
||||
getFocusNodeByShadowAlgorithm({
|
||||
currentActiveNode,
|
||||
currentActiveNodeRect,
|
||||
dir,
|
||||
checkNodeDis
|
||||
}) {
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
if (node === currentActiveNode) return
|
||||
let rect = this.getNodeRect(node)
|
||||
let { left, top, right, bottom } = rect
|
||||
let match = false
|
||||
if (dir === 'Left') {
|
||||
match =
|
||||
left < currentActiveNodeRect.left &&
|
||||
top < currentActiveNodeRect.bottom &&
|
||||
bottom > currentActiveNodeRect.top
|
||||
} else if (dir === 'Right') {
|
||||
match =
|
||||
right > currentActiveNodeRect.right &&
|
||||
top < currentActiveNodeRect.bottom &&
|
||||
bottom > currentActiveNodeRect.top
|
||||
} else if (dir === 'Up') {
|
||||
match =
|
||||
top < currentActiveNodeRect.top &&
|
||||
left < currentActiveNodeRect.right &&
|
||||
right > currentActiveNodeRect.left
|
||||
} else if (dir === 'Down') {
|
||||
match =
|
||||
bottom > currentActiveNodeRect.bottom &&
|
||||
left < currentActiveNodeRect.right &&
|
||||
right > currentActiveNodeRect.left
|
||||
}
|
||||
if (match) {
|
||||
checkNodeDis(rect, node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-13 16:15:36
|
||||
* @Desc: 3.区域算法
|
||||
*/
|
||||
getFocusNodeByAreaAlgorithm({
|
||||
currentActiveNode,
|
||||
currentActiveNodeRect,
|
||||
dir,
|
||||
checkNodeDis
|
||||
}) {
|
||||
// 当前聚焦节点的中心点
|
||||
let cX = (currentActiveNodeRect.right + currentActiveNodeRect.left) / 2
|
||||
let cY = (currentActiveNodeRect.bottom + currentActiveNodeRect.top) / 2
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
if (node === currentActiveNode) return
|
||||
let rect = this.getNodeRect(node)
|
||||
let { left, top, right, bottom } = rect
|
||||
// 遍历到的节点的中心点
|
||||
let ccX = (right + left) / 2
|
||||
let ccY = (bottom + top) / 2
|
||||
// 节点的中心点坐标和当前聚焦节点的中心点坐标的差值
|
||||
let offsetX = ccX - cX
|
||||
let offsetY = ccY - cY
|
||||
if (offsetX === 0 && offsetY === 0) return
|
||||
let match = false
|
||||
if (dir === 'Left') {
|
||||
match = offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY
|
||||
} else if (dir === 'Right') {
|
||||
match = offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY
|
||||
} else if (dir === 'Up') {
|
||||
match = offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX
|
||||
} else if (dir === 'Down') {
|
||||
match = offsetY > 0 && -offsetY < offsetX && offsetY > offsetX
|
||||
}
|
||||
if (match) {
|
||||
checkNodeDis(rect, node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 14:12:50
|
||||
* @Desc: 获取节点的位置信息
|
||||
*/
|
||||
getNodeRect(node) {
|
||||
let { scaleX, scaleY, translateX, translateY } =
|
||||
this.mindMap.draw.transform()
|
||||
let { left, top, width, height } = node
|
||||
return {
|
||||
right: (left + width) * scaleX + translateX,
|
||||
bottom: (top + height) * scaleY + translateY,
|
||||
left: left * scaleX + translateX,
|
||||
top: top * scaleY + translateY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 14:13:04
|
||||
* @Desc: 获取两个节点的距离
|
||||
*/
|
||||
getDistance(node1Rect, node2Rect) {
|
||||
let center1 = this.getCenter(node1Rect)
|
||||
let center2 = this.getCenter(node2Rect)
|
||||
return Math.sqrt(
|
||||
Math.pow(center1.x - center2.x, 2) + Math.pow(center1.y - center2.y, 2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 14:13:11
|
||||
* @Desc: 获取节点的中心点
|
||||
*/
|
||||
getCenter({ left, right, top, bottom }) {
|
||||
return {
|
||||
x: (left + right) / 2,
|
||||
y: (top + bottom) / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
175
simple-mind-map/src/MiniMap.js
Normal file
@@ -0,0 +1,175 @@
|
||||
// 小地图类
|
||||
class MiniMap {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:00:45
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
this.isMousedown = false
|
||||
this.mousedownPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
this.startViewPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:00:43
|
||||
* @Desc: 获取小地图相关数据
|
||||
*/
|
||||
getMiniMap() {
|
||||
const svg = this.mindMap.svg
|
||||
const draw = this.mindMap.draw
|
||||
// 保存原始信息
|
||||
const origWidth = svg.width()
|
||||
const origHeight = svg.height()
|
||||
const origTransform = draw.transform()
|
||||
const elRect = this.mindMap.el.getBoundingClientRect()
|
||||
// 去除放大缩小的变换效果
|
||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||
const rect = draw.rbox()
|
||||
// 将svg设置为实际内容的宽高
|
||||
svg.size(rect.width, rect.height)
|
||||
// 把实际内容变换
|
||||
draw.translate(-rect.x + elRect.left, -rect.y + elRect.top)
|
||||
// 克隆一份数据
|
||||
const clone = svg.clone()
|
||||
// 恢复原先的大小和变换信息
|
||||
svg.size(origWidth, origHeight)
|
||||
draw.transform(origTransform)
|
||||
|
||||
return {
|
||||
svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)
|
||||
svgHTML: clone.svg(), // svg字符串
|
||||
rect: {
|
||||
...rect, // 思维导图图形未缩放时的位置尺寸等信息
|
||||
ratio: rect.width / rect.height // 思维导图图形的宽高比
|
||||
},
|
||||
origWidth, // 画布宽度
|
||||
origHeight, // 画布高度
|
||||
scaleX: origTransform.scaleX, // 思维导图图形的水平缩放值
|
||||
scaleY: origTransform.scaleY // 思维导图图形的垂直缩放值
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:05:51
|
||||
* @Desc: 计算小地图的渲染数据
|
||||
* boxWidth:小地图容器的宽度
|
||||
* boxHeight:小地图容器的高度
|
||||
*/
|
||||
calculationMiniMap(boxWidth, boxHeight) {
|
||||
let { svgHTML, rect, origWidth, origHeight, scaleX, scaleY } =
|
||||
this.getMiniMap()
|
||||
// 计算数据
|
||||
let boxRatio = boxWidth / boxHeight
|
||||
let actWidth = 0
|
||||
let actHeight = 0
|
||||
if (boxRatio > rect.ratio) {
|
||||
// 高度以box为准,缩放宽度
|
||||
actHeight = boxHeight
|
||||
actWidth = rect.ratio * actHeight
|
||||
} else {
|
||||
// 宽度以box为准,缩放高度
|
||||
actWidth = boxWidth
|
||||
actHeight = actWidth / rect.ratio
|
||||
}
|
||||
// svg图形的缩放及位置
|
||||
let miniMapBoxScale = actWidth / rect.width
|
||||
let miniMapBoxLeft = (boxWidth - actWidth) / 2
|
||||
let miniMapBoxTop = (boxHeight - actHeight) / 2
|
||||
// 视口框大小及位置
|
||||
let _rectX = rect.x - (rect.width * scaleX - rect.width) / 2
|
||||
let _rectX2 = rect.x2 + (rect.width * scaleX - rect.width) / 2
|
||||
let _rectY = rect.y - (rect.height * scaleY - rect.height) / 2
|
||||
let _rectY2 = rect.y2 + (rect.height * scaleY - rect.height) / 2
|
||||
let _rectWidth = rect.width * scaleX
|
||||
let _rectHeight = rect.height * scaleY
|
||||
let viewBoxStyle = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
}
|
||||
viewBoxStyle.left =
|
||||
Math.max(0, (-_rectX / _rectWidth) * actWidth) + miniMapBoxLeft + 'px'
|
||||
viewBoxStyle.right =
|
||||
Math.max(0, ((_rectX2 - origWidth) / _rectWidth) * actWidth) +
|
||||
miniMapBoxLeft +
|
||||
'px'
|
||||
|
||||
viewBoxStyle.top =
|
||||
Math.max(0, (-_rectY / _rectHeight) * actHeight) + miniMapBoxTop + 'px'
|
||||
viewBoxStyle.bottom =
|
||||
Math.max(0, ((_rectY2 - origHeight) / _rectHeight) * actHeight) +
|
||||
miniMapBoxTop +
|
||||
'px'
|
||||
return {
|
||||
svgHTML, // 小地图html
|
||||
viewBoxStyle, // 视图框的位置信息
|
||||
miniMapBoxScale, // 视图框的缩放值
|
||||
miniMapBoxLeft, // 视图框的left值
|
||||
miniMapBoxTop // 视图框的top值
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:22:40
|
||||
* @Desc: 小地图鼠标按下事件
|
||||
*/
|
||||
onMousedown(e) {
|
||||
this.isMousedown = true
|
||||
this.mousedownPos = {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
}
|
||||
// 保存视图当前的偏移量
|
||||
let transformData = this.mindMap.view.getTransformData()
|
||||
this.startViewPos = {
|
||||
x: transformData.state.x,
|
||||
y: transformData.state.y
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:22:55
|
||||
* @Desc: 小地图鼠标移动事件
|
||||
*/
|
||||
onMousemove(e, sensitivityNum = 5) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
let ox = e.clientX - this.mousedownPos.x
|
||||
let oy = e.clientY - this.mousedownPos.y
|
||||
// 在视图最初偏移量上累加更新量
|
||||
this.mindMap.view.translateXTo(ox * sensitivityNum + this.startViewPos.x)
|
||||
this.mindMap.view.translateYTo(oy * sensitivityNum + this.startViewPos.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:23:01
|
||||
* @Desc: 小地图鼠标松开事件
|
||||
*/
|
||||
onMouseup() {
|
||||
this.isMousedown = false
|
||||
}
|
||||
}
|
||||
|
||||
export default MiniMap
|
||||
1390
simple-mind-map/src/Node.js
Normal file
1119
simple-mind-map/src/Render.js
Normal file
198
simple-mind-map/src/Select.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import { bfsWalk, throttle } from './utils'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:34:51
|
||||
* @Desc: 选择节点类
|
||||
*/
|
||||
|
||||
class Select {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:35:16
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
this.rect = null
|
||||
this.isMousedown = false
|
||||
this.mouseDownX = 0
|
||||
this.mouseDownY = 0
|
||||
this.mouseMoveX = 0
|
||||
this.mouseMoveY = 0
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-10 22:36:36
|
||||
* @Desc: 绑定事件
|
||||
*/
|
||||
bindEvent() {
|
||||
this.checkInNodes = throttle(this.checkInNodes, 500, this)
|
||||
this.mindMap.on('mousedown', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!e.ctrlKey && e.which !== 3) {
|
||||
return
|
||||
}
|
||||
this.isMousedown = true
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseDownX = x
|
||||
this.mouseDownY = y
|
||||
this.createRect(x, y)
|
||||
})
|
||||
this.mindMap.on('mousemove', e => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
this.mouseMoveX = x
|
||||
this.mouseMoveY = y
|
||||
if (
|
||||
Math.abs(x - this.mouseDownX) <= 10 &&
|
||||
Math.abs(y - this.mouseDownY) <= 10
|
||||
) {
|
||||
return
|
||||
}
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.onMove(x, y)
|
||||
})
|
||||
this.mindMap.on('mouseup', () => {
|
||||
if (this.mindMap.opt.readonly) {
|
||||
return
|
||||
}
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
}
|
||||
this.mindMap.emit(
|
||||
'node_active',
|
||||
null,
|
||||
this.mindMap.renderer.activeNodeList
|
||||
)
|
||||
clearTimeout(this.autoMoveTimer)
|
||||
this.isMousedown = false
|
||||
if (this.rect) this.rect.remove()
|
||||
this.rect = null
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-13 07:55:49
|
||||
* @Desc: 鼠标移动事件
|
||||
*/
|
||||
onMove(x, y) {
|
||||
// 绘制矩形
|
||||
this.rect.plot([
|
||||
[this.mouseDownX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseDownY],
|
||||
[this.mouseMoveX, this.mouseMoveY],
|
||||
[this.mouseDownX, this.mouseMoveY]
|
||||
])
|
||||
this.checkInNodes()
|
||||
// 检测边缘移动
|
||||
let step = this.mindMap.opt.selectTranslateStep
|
||||
let limit = this.mindMap.opt.selectTranslateLimit
|
||||
let count = 0
|
||||
// 左边缘
|
||||
if (x <= this.mindMap.elRect.left + limit) {
|
||||
this.mouseDownX += step
|
||||
this.mindMap.view.translateX(step)
|
||||
count++
|
||||
}
|
||||
// 右边缘
|
||||
if (x >= this.mindMap.elRect.right - limit) {
|
||||
this.mouseDownX -= step
|
||||
this.mindMap.view.translateX(-step)
|
||||
count++
|
||||
}
|
||||
// 上边缘
|
||||
if (y <= this.mindMap.elRect.top + limit) {
|
||||
this.mouseDownY += step
|
||||
this.mindMap.view.translateY(step)
|
||||
count++
|
||||
}
|
||||
// 下边缘
|
||||
if (y >= this.mindMap.elRect.bottom - limit) {
|
||||
this.mouseDownY -= step
|
||||
this.mindMap.view.translateY(-step)
|
||||
count++
|
||||
}
|
||||
if (count > 0) {
|
||||
this.startAutoMove(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-22 08:02:23
|
||||
* @Desc: 开启自动移动
|
||||
*/
|
||||
startAutoMove(x, y) {
|
||||
this.autoMoveTimer = setTimeout(() => {
|
||||
this.onMove(x, y)
|
||||
}, 20)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 10:19:37
|
||||
* @Desc: 创建矩形
|
||||
*/
|
||||
createRect(x, y) {
|
||||
this.rect = this.mindMap.svg
|
||||
.polygon()
|
||||
.stroke({
|
||||
color: '#0984e3'
|
||||
})
|
||||
.fill({
|
||||
color: 'rgba(9,132,227,0.3)'
|
||||
})
|
||||
.plot([[x, y]])
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 10:20:43
|
||||
* @Desc: 检测在选区里的节点
|
||||
*/
|
||||
checkInNodes() {
|
||||
let { scaleX, scaleY, translateX, translateY } =
|
||||
this.mindMap.draw.transform()
|
||||
let minx = Math.min(this.mouseDownX, this.mouseMoveX)
|
||||
let miny = Math.min(this.mouseDownY, this.mouseMoveY)
|
||||
let maxx = Math.max(this.mouseDownX, this.mouseMoveX)
|
||||
let maxy = Math.max(this.mouseDownY, this.mouseMoveY)
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { left, top, width, height } = node
|
||||
let right = (left + width) * scaleX + translateX
|
||||
let bottom = (top + height) * scaleY + translateY
|
||||
left = left * scaleX + translateX
|
||||
top = top * scaleY + translateY
|
||||
if (left >= minx && right <= maxx && top >= miny && bottom <= maxy) {
|
||||
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
|
||||
if (node.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.setNodeActive(node, true)
|
||||
this.mindMap.renderer.addActiveNode(node)
|
||||
})
|
||||
} else if (node.nodeData.data.isActive) {
|
||||
this.mindMap.batchExecution.push('activeNode' + node.uid, () => {
|
||||
if (!node.nodeData.data.isActive) {
|
||||
return
|
||||
}
|
||||
this.mindMap.renderer.setNodeActive(node, false)
|
||||
this.mindMap.renderer.removeActiveNode(node)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Select
|
||||
275
simple-mind-map/src/Shape.js
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-22 21:32:50
|
||||
* @Desc: 节点形状类
|
||||
*/
|
||||
export default class Shape {
|
||||
constructor(node) {
|
||||
this.node = node
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-17 22:32:32
|
||||
* @Desc: 形状需要的padding
|
||||
*/
|
||||
getShapePadding(width, height, paddingX, paddingY) {
|
||||
const shape = this.node.getShape()
|
||||
const defaultPaddingX = 15
|
||||
const defaultPaddingY = 5
|
||||
const actWidth = width + paddingX * 2
|
||||
const actHeight = height + paddingY * 2
|
||||
const actOffset = Math.abs(actWidth - actHeight)
|
||||
switch (shape) {
|
||||
case 'roundedRectangle':
|
||||
return {
|
||||
paddingX: height > width ? (height - width) / 2 : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'diamond':
|
||||
return {
|
||||
paddingX: width / 2,
|
||||
paddingY: height / 2
|
||||
}
|
||||
case 'parallelogram':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'outerTriangularRectangle':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'innerTriangularRectangle':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: 0
|
||||
}
|
||||
case 'ellipse':
|
||||
return {
|
||||
paddingX: paddingX <= 0 ? defaultPaddingX : 0,
|
||||
paddingY: paddingY <= 0 ? defaultPaddingY : 0
|
||||
}
|
||||
case 'circle':
|
||||
return {
|
||||
paddingX: actHeight > actWidth ? actOffset / 2 : 0,
|
||||
paddingY: actHeight < actWidth ? actOffset / 2 : 0
|
||||
}
|
||||
default:
|
||||
return {
|
||||
paddingX: 0,
|
||||
paddingY: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-08-17 22:22:53
|
||||
* @Desc: 创建形状节点
|
||||
*/
|
||||
createShape() {
|
||||
const shape = this.node.getShape()
|
||||
let { width, height } = this.node
|
||||
let node = null
|
||||
// 矩形
|
||||
if (shape === 'rectangle') {
|
||||
node = this.node.group.rect(width, height)
|
||||
} else if (shape === 'diamond') {
|
||||
// 菱形
|
||||
node = this.createDiamond()
|
||||
} else if (shape === 'parallelogram') {
|
||||
// 平行四边形
|
||||
node = this.createParallelogram()
|
||||
} else if (shape === 'roundedRectangle') {
|
||||
// 圆角矩形
|
||||
node = this.createRoundedRectangle()
|
||||
} else if (shape === 'octagonalRectangle') {
|
||||
// 八角矩形
|
||||
node = this.createOctagonalRectangle()
|
||||
} else if (shape === 'outerTriangularRectangle') {
|
||||
// 外三角矩形
|
||||
node = this.createOuterTriangularRectangle()
|
||||
} else if (shape === 'innerTriangularRectangle') {
|
||||
// 内三角矩形
|
||||
node = this.createInnerTriangularRectangle()
|
||||
} else if (shape === 'ellipse') {
|
||||
// 椭圆
|
||||
node = this.createEllipse()
|
||||
} else if (shape === 'circle') {
|
||||
// 圆
|
||||
node = this.createCircle()
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-04 09:08:54
|
||||
* @Desc: 创建菱形
|
||||
*/
|
||||
createDiamond() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
let topX = halfWidth
|
||||
let topY = 0
|
||||
let rightX = width
|
||||
let rightY = halfHeight
|
||||
let bottomX = halfWidth
|
||||
let bottomY = height
|
||||
let leftX = 0
|
||||
let leftY = halfHeight
|
||||
return this.node.group.polygon(`
|
||||
${topX}, ${topY}
|
||||
${rightX}, ${rightY}
|
||||
${bottomX}, ${bottomY}
|
||||
${leftX}, ${leftY}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-03 16:14:12
|
||||
* @Desc: 创建平行四边形
|
||||
*/
|
||||
createParallelogram() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX}, ${height}
|
||||
${0}, ${height}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-03 16:50:23
|
||||
* @Desc: 创建圆角矩形
|
||||
*/
|
||||
createRoundedRectangle() {
|
||||
let { width, height } = this.node
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfHeight},0
|
||||
L${width - halfHeight},0
|
||||
A${height / 2},${height / 2} 0 0,1 ${width - halfHeight},${height}
|
||||
L${halfHeight},${height}
|
||||
A${height / 2},${height / 2} 0 0,1 ${halfHeight},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 16:14:08
|
||||
* @Desc: 创建八角矩形
|
||||
*/
|
||||
createOctagonalRectangle() {
|
||||
let w = 5
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${w}
|
||||
${w}, ${0}
|
||||
${width - w}, ${0}
|
||||
${width}, ${w}
|
||||
${width}, ${height - w}
|
||||
${width - w}, ${height}
|
||||
${w}, ${height}
|
||||
${0}, ${height - w}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 20:55:50
|
||||
* @Desc: 创建外三角矩形
|
||||
*/
|
||||
createOuterTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${paddingX}, ${0}
|
||||
${width - paddingX}, ${0}
|
||||
${width}, ${height / 2}
|
||||
${width - paddingX}, ${height}
|
||||
${paddingX}, ${height}
|
||||
${0}, ${height / 2}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 20:59:37
|
||||
* @Desc: 创建内三角矩形
|
||||
*/
|
||||
createInnerTriangularRectangle() {
|
||||
let { paddingX } = this.node.getPaddingVale()
|
||||
paddingX = paddingX || this.node.shapePadding.paddingX
|
||||
let { width, height } = this.node
|
||||
return this.node.group.polygon(`
|
||||
${0}, ${0}
|
||||
${width}, ${0}
|
||||
${width - paddingX / 2}, ${height / 2}
|
||||
${width}, ${height}
|
||||
${0}, ${height}
|
||||
${paddingX / 2}, ${height / 2}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:06:31
|
||||
* @Desc: 创建椭圆
|
||||
*/
|
||||
createEllipse() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:14:04
|
||||
* @Desc: 创建圆
|
||||
*/
|
||||
createCircle() {
|
||||
let { width, height } = this.node
|
||||
let halfWidth = width / 2
|
||||
let halfHeight = height / 2
|
||||
return this.node.group.path(`
|
||||
M${halfWidth},0
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${height}
|
||||
M${halfWidth},${height}
|
||||
A${halfWidth},${halfHeight} 0 0,1 ${halfWidth},${0}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
'rectangle',
|
||||
'diamond',
|
||||
'parallelogram',
|
||||
'roundedRectangle',
|
||||
'octagonalRectangle',
|
||||
'outerTriangularRectangle',
|
||||
'innerTriangularRectangle',
|
||||
'ellipse',
|
||||
'circle'
|
||||
]
|
||||
230
simple-mind-map/src/Style.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import { tagColorList } from './utils/constant'
|
||||
const rootProp = ['paddingX', 'paddingY']
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:09:08
|
||||
* @Desc: 样式类
|
||||
*/
|
||||
class Style {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 16:01:53
|
||||
* @Desc: 设置背景样式
|
||||
*/
|
||||
static setBackgroundStyle(el, themeConfig) {
|
||||
let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig
|
||||
el.style.backgroundColor = backgroundColor
|
||||
if (backgroundImage) {
|
||||
el.style.backgroundImage = `url(${backgroundImage})`
|
||||
el.style.backgroundRepeat = backgroundRepeat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:10:11
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(ctx, themeConfig) {
|
||||
this.ctx = ctx
|
||||
this.themeConfig = themeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-12 07:40:14
|
||||
* @Desc: 更新主题配置
|
||||
*/
|
||||
updateThemeConfig(themeConfig) {
|
||||
this.themeConfig = themeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 12:02:55
|
||||
* @Desc: 合并样式
|
||||
*/
|
||||
merge(prop, root, isActive) {
|
||||
// 三级及以下节点
|
||||
let defaultConfig = this.themeConfig.node
|
||||
if (root || rootProp.includes(prop)) {
|
||||
// 直接使用最外层样式
|
||||
defaultConfig = this.themeConfig
|
||||
} else if (this.ctx.isGeneralization) {
|
||||
// 概要节点
|
||||
defaultConfig = this.themeConfig.generalization
|
||||
} else if (this.ctx.layerIndex === 0) {
|
||||
// 根节点
|
||||
defaultConfig = this.themeConfig.root
|
||||
} else if (this.ctx.layerIndex === 1) {
|
||||
// 二级节点
|
||||
defaultConfig = this.themeConfig.second
|
||||
}
|
||||
// 激活状态
|
||||
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
|
||||
if (
|
||||
this.ctx.nodeData.data.activeStyle &&
|
||||
this.ctx.nodeData.data.activeStyle[prop] !== undefined
|
||||
) {
|
||||
return this.ctx.nodeData.data.activeStyle[prop]
|
||||
} else if (defaultConfig.active && defaultConfig.active[prop]) {
|
||||
return defaultConfig.active[prop]
|
||||
}
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.getSelfStyle(prop) !== undefined
|
||||
? this.getSelfStyle(prop)
|
||||
: defaultConfig[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 21:55:57
|
||||
* @Desc: 获取某个样式值
|
||||
*/
|
||||
getStyle(prop, root, isActive) {
|
||||
return this.merge(prop, root, isActive)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:09:39
|
||||
* @Desc: 获取自身自定义样式
|
||||
*/
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:12:56
|
||||
* @Desc: 矩形
|
||||
*/
|
||||
rect(node) {
|
||||
this.shape(node)
|
||||
node.radius(this.merge('borderRadius'))
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林
|
||||
* @Date: 2022-09-12 15:04:28
|
||||
* @Desc: 矩形外的其他形状
|
||||
*/
|
||||
shape(node) {
|
||||
node
|
||||
.fill({
|
||||
color: this.merge('fillColor')
|
||||
})
|
||||
.stroke({
|
||||
color: this.merge('borderColor'),
|
||||
width: this.merge('borderWidth'),
|
||||
dasharray: this.merge('borderDasharray')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 12:07:59
|
||||
* @Desc: 文字
|
||||
*/
|
||||
text(node) {
|
||||
node
|
||||
.fill({
|
||||
color: this.merge('color')
|
||||
})
|
||||
.css({
|
||||
'font-family': this.merge('fontFamily'),
|
||||
'font-size': this.merge('fontSize'),
|
||||
'font-weight': this.merge('fontWeight'),
|
||||
'font-style': this.merge('fontStyle'),
|
||||
'text-decoration': this.merge('textDecoration')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-13 08:14:34
|
||||
* @Desc: html文字节点
|
||||
*/
|
||||
domText(node, fontSizeScale = 1) {
|
||||
node.style.fontFamily = this.merge('fontFamily')
|
||||
node.style.fontSize = this.merge('fontSize') * fontSizeScale + 'px'
|
||||
node.style.fontWeight = this.merge('fontWeight') || 'normal'
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-20 20:02:18
|
||||
* @Desc: 标签文字
|
||||
*/
|
||||
tagText(node, index) {
|
||||
node
|
||||
.fill({
|
||||
color: tagColorList[index].color
|
||||
})
|
||||
.css({
|
||||
'font-size': '12px'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-20 21:04:11
|
||||
* @Desc: 标签矩形
|
||||
*/
|
||||
tagRect(node, index) {
|
||||
node.fill({
|
||||
color: tagColorList[index].background
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-03 22:37:19
|
||||
* @Desc: 内置图标
|
||||
*/
|
||||
iconNode(node) {
|
||||
node.attr({
|
||||
fill: this.merge('color')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 14:50:49
|
||||
* @Desc: 连线
|
||||
*/
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2022-07-30 16:19:03
|
||||
* @Desc: 概要连线
|
||||
*/
|
||||
generalizationLine(node) {
|
||||
node
|
||||
.stroke({
|
||||
width: this.merge('generalizationLineWidth', true),
|
||||
color: this.merge('generalizationLineColor', true)
|
||||
})
|
||||
.fill({ color: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 20:03:59
|
||||
* @Desc: 按钮
|
||||
*/
|
||||
iconBtn(node, fillNode) {
|
||||
node.fill({ color: '#808080' })
|
||||
fillNode.fill({ color: '#fff' })
|
||||
}
|
||||
}
|
||||
|
||||
export default Style
|
||||
153
simple-mind-map/src/TextEdit.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import { getStrWithBrFromHtml } from './utils'
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-06-19 11:11:28
|
||||
* @Desc: 节点文字编辑类
|
||||
*/
|
||||
export default class TextEdit {
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-06-19 11:22:57
|
||||
* @Desc: 构造函数
|
||||
*/
|
||||
constructor(renderer) {
|
||||
this.renderer = renderer
|
||||
this.mindMap = renderer.mindMap
|
||||
// 文本编辑框
|
||||
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()
|
||||
})
|
||||
// 展开收缩按钮点击事件
|
||||
this.mindMap.on('expand_btn_click', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 节点激活前事件
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
return
|
||||
}
|
||||
this.show(this.renderer.activeNodeList[0])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-08-16 16:27:02
|
||||
* @Desc: 注册临时快捷键
|
||||
*/
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-13 22:15:56
|
||||
* @Desc: 显示文本编辑框
|
||||
*/
|
||||
show(node) {
|
||||
this.showEditTextBox(node, node._textData.node.node.getBoundingClientRect())
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-13 22:13:02
|
||||
* @Desc: 显示文本编辑框
|
||||
*/
|
||||
showEditTextBox(node, rect) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.registerTmpShortcut()
|
||||
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)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
node.style.domText(this.textEditNode, this.mindMap.view.scale)
|
||||
this.textEditNode.innerHTML = node.nodeData.data.text
|
||||
.split(/\n/gim)
|
||||
.join('<br>')
|
||||
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
|
||||
// 选中文本
|
||||
this.selectNodeText()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 23:13:50
|
||||
* @Desc: 选中文本
|
||||
*/
|
||||
selectNodeText() {
|
||||
let selection = window.getSelection()
|
||||
let range = document.createRange()
|
||||
range.selectNodeContents(this.textEditNode)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-24 13:48:16
|
||||
* @Desc: 隐藏文本编辑框
|
||||
*/
|
||||
hideEditTextBox() {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.renderer.activeNodeList.forEach(node => {
|
||||
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', node, str)
|
||||
if (node.isGeneralization) {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
})
|
||||
this.mindMap.emit(
|
||||
'hide_text_edit',
|
||||
this.textEditNode,
|
||||
this.renderer.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
|
||||
}
|
||||
}
|
||||
230
simple-mind-map/src/View.js
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 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.scale = 1
|
||||
this.sx = 0
|
||||
this.sy = 0
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.firstDrag = true
|
||||
this.setTransformData(this.mindMap.opt.viewData)
|
||||
this.bind()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-04-07 15:38:51
|
||||
* @Desc: 绑定
|
||||
*/
|
||||
bind() {
|
||||
// 快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Control+=', () => {
|
||||
this.enlarge()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+-', () => {
|
||||
this.narrow()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Control+Enter', () => {
|
||||
this.reset()
|
||||
})
|
||||
this.mindMap.svg.on('dblclick', () => {
|
||||
this.reset()
|
||||
})
|
||||
// 拖动视图
|
||||
this.mindMap.event.on('mousedown', () => {
|
||||
this.sx = this.x
|
||||
this.sy = this.y
|
||||
})
|
||||
this.mindMap.event.on('drag', (e, event) => {
|
||||
if (e.ctrlKey) {
|
||||
// 按住ctrl键拖动为多选
|
||||
return
|
||||
}
|
||||
if (this.firstDrag) {
|
||||
this.firstDrag = false
|
||||
// 清除激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
}
|
||||
this.x = this.sx + event.mousemoveOffset.x
|
||||
this.y = this.sy + event.mousemoveOffset.y
|
||||
this.transform()
|
||||
})
|
||||
this.mindMap.event.on('mouseup', () => {
|
||||
this.firstDrag = true
|
||||
})
|
||||
// 放大缩小视图
|
||||
this.mindMap.event.on('mousewheel', (e, dir) => {
|
||||
// // 放大
|
||||
if (dir === 'down') {
|
||||
this.enlarge()
|
||||
} else {
|
||||
// 缩小
|
||||
this.narrow()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-22 18:30:24
|
||||
* @Desc: 获取当前变换状态数据
|
||||
*/
|
||||
getTransformData() {
|
||||
return {
|
||||
transform: this.mindMap.draw.transform(),
|
||||
state: {
|
||||
scale: this.scale,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
sx: this.sx,
|
||||
sy: this.sy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-22 19:54:17
|
||||
* @Desc: 动态设置变换状态数据
|
||||
*/
|
||||
setTransformData(viewData) {
|
||||
if (viewData) {
|
||||
Object.keys(viewData.state).forEach(prop => {
|
||||
this[prop] = viewData.state[prop]
|
||||
})
|
||||
this.mindMap.draw.transform({
|
||||
...viewData.transform
|
||||
})
|
||||
this.mindMap.emit('view_data_change', this.getTransformData())
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 15:49:06
|
||||
* @Desc: 平移x方向
|
||||
*/
|
||||
translateX(step) {
|
||||
this.x += step
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:03:53
|
||||
* @Desc: 平移x方式到
|
||||
*/
|
||||
translateXTo(x) {
|
||||
this.x = x
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-07-13 15:48:52
|
||||
* @Desc: 平移y方向
|
||||
*/
|
||||
translateY(step) {
|
||||
this.y += step
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-10-10 14:04:10
|
||||
* @Desc: 平移y方向到
|
||||
*/
|
||||
translateYTo(y) {
|
||||
this.y = y
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:13:14
|
||||
* @Desc: 应用变换
|
||||
*/
|
||||
transform() {
|
||||
this.mindMap.draw.transform({
|
||||
scale: this.scale,
|
||||
// origin: 'center center',
|
||||
translate: [this.x, this.y]
|
||||
})
|
||||
this.mindMap.emit('view_data_change', this.getTransformData())
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-11 17:41:35
|
||||
* @Desc: 恢复
|
||||
*/
|
||||
reset() {
|
||||
this.scale = 1
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.transform()
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:10:34
|
||||
* @Desc: 缩小
|
||||
*/
|
||||
narrow() {
|
||||
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
|
||||
this.scale -= this.mindMap.opt.scaleRatio
|
||||
} else {
|
||||
this.scale = 0.1
|
||||
}
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-04 17:10:41
|
||||
* @Desc: 放大
|
||||
*/
|
||||
enlarge() {
|
||||
this.scale += this.mindMap.opt.scaleRatio
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-12-09 16:31:59
|
||||
* @Desc: 设置缩放
|
||||
*/
|
||||
setScale(scale) {
|
||||
this.scale = scale
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
}
|
||||
|
||||
export default View
|
||||
BIN
simple-mind-map/src/assets/blueSky.jpg
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
simple-mind-map/src/assets/brainImpairedPink.jpg
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
simple-mind-map/src/assets/catalogOrganization.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
simple-mind-map/src/assets/classic.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
simple-mind-map/src/assets/classic2.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
simple-mind-map/src/assets/classic3.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
simple-mind-map/src/assets/classic4.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
simple-mind-map/src/assets/classicBlue.jpg
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
simple-mind-map/src/assets/classicGreen.jpg
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
simple-mind-map/src/assets/dark.jpg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
simple-mind-map/src/assets/dark2.jpg
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
simple-mind-map/src/assets/default.jpg
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
simple-mind-map/src/assets/earthYellow.jpg
Normal file
|
After Width: | Height: | Size: 9.1 KiB |