diff --git a/index.html b/index.html
index a2c3115a..4bdd84c3 100644
--- a/index.html
+++ b/index.html
@@ -1 +1 @@
-
一个简单的web思维导图实现
\ No newline at end of file
+一个简单的web思维导图实现
\ No newline at end of file
diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js
index b60c3417..1b283f4c 100644
--- a/simple-mind-map/index.js
+++ b/simple-mind-map/index.js
@@ -10,7 +10,7 @@ import BatchExecution from './src/BatchExecution'
import { layoutValueList, CONSTANTS } from './src/utils/constant'
import { SVG } from '@svgdotjs/svg.js'
import { simpleDeepClone } from './src/utils'
-import defaultTheme from './src/themes/default'
+import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
// 默认选项配置
const defaultOpt = {
@@ -120,7 +120,9 @@ const defaultOpt = {
// }
],
// 节点最大缓存数量
- maxNodeCacheCount: 1000
+ maxNodeCacheCount: 1000,
+ // 关联线默认文字
+ defaultAssociativeLineText: '关联'
}
// 思维导图
@@ -265,7 +267,9 @@ class MindMap {
// 设置主题配置
setThemeConfig(config) {
this.opt.themeConfig = config
- this.render(null, CONSTANTS.CHANGE_THEME)
+ // 检查改变的是否是节点大小无关的主题属性
+ let res = checkIsNodeSizeIndependenceConfig(config)
+ this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
}
// 获取自定义主题配置
diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json
index 0bc466e8..a5a08080 100644
--- a/simple-mind-map/package.json
+++ b/simple-mind-map/package.json
@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
- "version": "0.5.10-fix.2",
+ "version": "0.5.11",
"description": "一个简单的web在线思维导图",
"authors": [
{
diff --git a/simple-mind-map/src/AssociativeLine.js b/simple-mind-map/src/AssociativeLine.js
index d915084c..c365a8de 100644
--- a/simple-mind-map/src/AssociativeLine.js
+++ b/simple-mind-map/src/AssociativeLine.js
@@ -3,13 +3,13 @@ import { v4 as uuid } from 'uuid'
import {
getAssociativeLineTargetIndex,
computeCubicBezierPathPoints,
- joinCubicBezierPath,
cubicBezierPath,
getNodePoint,
computeNodePoints,
- getNodeLinePath,
- getDefaultControlPointOffsets
+ getNodeLinePath
} from './utils/associativeLineUtils'
+import associativeLineControlsMethods from './utils/associativeLineControls'
+import associativeLineTextMethods from './utils/associativeLineText'
// 关联线类
class AssociativeLine {
@@ -46,6 +46,14 @@ class AssociativeLine {
}
// 节流一下,不然很卡
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
+ // 控制点相关方法
+ Object.keys(associativeLineControlsMethods).forEach(item => {
+ this[item] = associativeLineControlsMethods[item].bind(this)
+ })
+ // 关联线文字相关方法
+ Object.keys(associativeLineTextMethods).forEach(item => {
+ this[item] = associativeLineTextMethods[item].bind(this)
+ })
this.bindEvent()
}
@@ -89,6 +97,8 @@ class AssociativeLine {
window.addEventListener('mouseup', e => {
this.onControlPointMouseup(e)
})
+ // 缩放事件
+ this.mindMap.on('scale', this.onScale)
}
// 创建箭头
@@ -177,36 +187,46 @@ class AssociativeLine {
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
.fill({ color: 'none' })
clickPath.plot(pathStr)
+ // 文字
+ let text = this.createText({ path, clickPath, node, toNode, startPoint, endPoint, controlPoints })
// 点击事件
clickPath.click(e => {
e.stopPropagation()
- // 如果当前存在激活节点,那么取消激活节点
- if (this.mindMap.renderer.activeNodeList.length > 0) {
- this.clearActiveNodes()
- } else {
- // 否则清除当前的关联线的激活状态,如果有的话
- this.clearActiveLine()
- // 保存当前激活的关联线信息
- this.activeLine = [path, clickPath, node, toNode]
- // 让不可见的点击线显示
- clickPath.stroke({ color: associativeLineActiveColor })
- // 渲染控制点和连线
- this.renderControls(
- startPoint,
- endPoint,
- controlPoints[0],
- controlPoints[1]
- )
- this.mindMap.emit(
- 'associative_line_click',
- path,
- clickPath,
- node,
- toNode
- )
- }
+ this.setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints })
})
- this.lineList.push([path, clickPath, node, toNode])
+ // 渲染关联线文字
+ this.renderText(this.getText(node, toNode), path, text)
+ this.lineList.push([path, clickPath, text, node, toNode])
+ }
+
+ // 激活某根关联线
+ setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints }) {
+ let {
+ associativeLineActiveColor
+ } = this.mindMap.themeConfig
+ // 如果当前存在激活节点,那么取消激活节点
+ if (this.mindMap.renderer.activeNodeList.length > 0) {
+ this.clearActiveNodes()
+ } else {
+ // 否则清除当前的关联线的激活状态,如果有的话
+ this.clearActiveLine()
+ // 保存当前激活的关联线信息
+ this.activeLine = [path, clickPath, text, node, toNode]
+ // 让不可见的点击线显示
+ clickPath.stroke({ color: associativeLineActiveColor })
+ // 如果没有输入过关联线文字,那么显示默认文字
+ if (!this.getText(node, toNode)) {
+ this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text)
+ }
+ // 渲染控制点和连线
+ this.renderControls(
+ startPoint,
+ endPoint,
+ controlPoints[0],
+ controlPoints[1]
+ )
+ this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
+ }
}
// 移除所有连接线
@@ -214,6 +234,7 @@ class AssociativeLine {
this.lineList.forEach(line => {
line[0].remove()
line[1].remove()
+ line[2].remove()
})
this.lineList = []
}
@@ -253,7 +274,8 @@ class AssociativeLine {
updateCreatingLine(e) {
let { x, y } = this.getTransformedEventPos(e)
let startPoint = getNodePoint(this.creatingStartNode)
- let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x, y)
+ let offsetX = x > startPoint.x ? -10 : 10
+ let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x + offsetX, y)
this.creatingLine.plot(pathStr)
this.checkOverlapNode(x, y)
}
@@ -349,20 +371,33 @@ class AssociativeLine {
// 删除连接线
removeLine() {
if (!this.activeLine) return
- let [, , node, toNode] = this.activeLine
+ let [, , , node, toNode] = this.activeLine
this.removeControls()
- let { associativeLineTargets, associativeLineTargetControlOffsets } =
+ let { associativeLineTargets, associativeLineTargetControlOffsets, associativeLineText } =
node.nodeData.data
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
+ // 更新关联线文本数据
+ let newAssociativeLineText = {}
+ if (associativeLineText) {
+ Object.keys(associativeLineText).forEach((item) => {
+ if (item !== toNode.nodeData.data.id) {
+ newAssociativeLineText[item] = associativeLineText[item]
+ }
+ })
+ }
this.mindMap.execCommand('SET_NODE_DATA', node, {
+ // 目标
associativeLineTargets: associativeLineTargets.filter((_, index) => {
return index !== targetIndex
}),
+ // 偏移量
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
? associativeLineTargetControlOffsets.filter((_, index) => {
return index !== targetIndex
})
- : []
+ : [],
+ // 文本
+ associativeLineText: newAssociativeLineText
})
}
@@ -376,9 +411,16 @@ class AssociativeLine {
// 清除激活的线
clearActiveLine() {
if (this.activeLine) {
- this.activeLine[1].stroke({
+ let [, clickPath, text, node, toNode] = this.activeLine
+ clickPath.stroke({
color: 'transparent'
})
+ // 隐藏关联线文本编辑框
+ this.hideEditTextBox()
+ // 如果当前关联线没有文字,则清空文字节点
+ if (!this.getText(node, toNode)) {
+ text.clear()
+ }
this.activeLine = null
this.removeControls()
}
@@ -391,6 +433,7 @@ class AssociativeLine {
this.lineList.forEach(line => {
line[0].hide()
line[1].hide()
+ line[2].hide()
})
this.hideControls()
}
@@ -401,225 +444,11 @@ class AssociativeLine {
this.lineList.forEach(line => {
line[0].show()
line[1].show()
+ line[2].show()
})
this.showControls()
this.isNodeDragging = false
}
-
- // 创建控制点、连线节点
- createControlNodes() {
- let { associativeLineActiveColor } = this.mindMap.themeConfig
- // 连线
- this.controlLine1 = this.draw
- .line()
- .stroke({ color: associativeLineActiveColor, width: 2 })
- this.controlLine2 = this.draw
- .line()
- .stroke({ color: associativeLineActiveColor, width: 2 })
- // 控制点
- this.controlPoint1 = this.createOneControlNode('controlPoint1')
- this.controlPoint2 = this.createOneControlNode('controlPoint2')
- }
-
- // 创建控制点
- createOneControlNode(pointKey) {
- let { associativeLineActiveColor } = this.mindMap.themeConfig
- return this.draw
- .circle(this.controlPointDiameter)
- .stroke({ color: associativeLineActiveColor })
- .fill({ color: '#fff' })
- .click(e => {
- e.stopPropagation()
- })
- .mousedown(e => {
- this.onControlPointMousedown(e, pointKey)
- })
- }
-
- // 控制点的鼠标按下事件
- onControlPointMousedown(e, pointKey) {
- e.stopPropagation()
- this.isControlPointMousedown = true
- this.mousedownControlPointKey = pointKey
- }
-
- // 控制点的鼠标移动事件
- onControlPointMousemove(e) {
- if (
- !this.isControlPointMousedown ||
- !this.mousedownControlPointKey ||
- !this[this.mousedownControlPointKey]
- )
- return
- e.stopPropagation()
- e.preventDefault()
- let radius = this.controlPointDiameter / 2
- // 转换鼠标当前的位置
- let { x, y } = this.getTransformedEventPos(e)
- this.controlPointMousemoveState.pos = {
- x,
- y
- }
- // 更新当前拖拽的控制点的位置
- this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
- let [path, clickPath, node, toNode] = this.activeLine
- let [startPoint, endPoint] = computeNodePoints(node, toNode)
- this.controlPointMousemoveState.startPoint = startPoint
- this.controlPointMousemoveState.endPoint = endPoint
- let targetIndex = getAssociativeLineTargetIndex(node, toNode)
- this.controlPointMousemoveState.targetIndex = targetIndex
- let offsets = []
- let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
- if (!associativeLineTargetControlOffsets) {
- // 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
- offsets = getDefaultControlPointOffsets(startPoint, endPoint)
- } else {
- offsets = associativeLineTargetControlOffsets[targetIndex]
- }
- let point1 = null
- let point2 = null
- // 拖拽的是控制点1
- if (this.mousedownControlPointKey === 'controlPoint1') {
- point1 = {
- x,
- y
- }
- point2 = {
- x: endPoint.x + offsets[1].x,
- y: endPoint.y + offsets[1].y
- }
- // 更新控制点1的连线
- this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
- } else {
- // 拖拽的是控制点2
- point1 = {
- x: startPoint.x + offsets[0].x,
- y: startPoint.y + offsets[0].y
- }
- point2 = {
- x,
- y
- }
- // 更新控制点2的连线
- this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
- }
- // 更新关联线
- let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
- path.plot(pathStr)
- clickPath.plot(pathStr)
- }
-
- // 控制点的鼠标移动事件
- onControlPointMouseup(e) {
- if (!this.isControlPointMousedown) return
- e.stopPropagation()
- e.preventDefault()
- let { pos, startPoint, endPoint, targetIndex } =
- this.controlPointMousemoveState
- let [, , node] = this.activeLine
- let offsetList = []
- let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
- if (!associativeLineTargetControlOffsets) {
- // 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
- offsetList[targetIndex] = getDefaultControlPointOffsets(startPoint, endPoint)
- } else {
- offsetList = associativeLineTargetControlOffsets
- }
- let offset1 = null
- let offset2 = null
- if (this.mousedownControlPointKey === 'controlPoint1') {
- // 更新控制点1数据
- offset1 = {
- x: pos.x - startPoint.x,
- y: pos.y - startPoint.y
- }
- offset2 = offsetList[targetIndex][1]
- } else {
- // 更新控制点2数据
- offset1 = offsetList[targetIndex][0]
- offset2 = {
- x: pos.x - endPoint.x,
- y: pos.y - endPoint.y
- }
- }
- offsetList[targetIndex] = [offset1, offset2]
- this.mindMap.execCommand('SET_NODE_DATA', node, {
- associativeLineTargetControlOffsets: offsetList
- })
- // 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
- setTimeout(() => {
- this.resetControlPoint()
- }, 0)
- }
-
- // 复位控制点移动
- resetControlPoint() {
- this.isControlPointMousedown = false
- this.mousedownControlPointKey = ''
- this.controlPointMousemoveState = {
- pos: null,
- startPoint: null,
- endPoint: null,
- targetIndex: ''
- }
- }
-
- // 渲染控制点
- renderControls(startPoint, endPoint, point1, point2) {
- if (!this.controlLine1) {
- this.createControlNodes()
- }
- let radius = this.controlPointDiameter / 2
- // 控制点和起终点的连线
- this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
- this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
- // 控制点
- this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
- this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
- }
-
- // 删除控制点
- removeControls() {
- if (!this.controlLine1) return
- ;[
- this.controlLine1,
- this.controlLine2,
- this.controlPoint1,
- this.controlPoint2
- ].forEach(item => {
- item.remove()
- })
- this.controlLine1 = null
- this.controlLine2 = null
- this.controlPoint1 = null
- this.controlPoint2 = null
- }
-
- // 隐藏控制点
- hideControls() {
- if (!this.controlLine1) return
- ;[
- this.controlLine1,
- this.controlLine2,
- this.controlPoint1,
- this.controlPoint2
- ].forEach(item => {
- item.hide()
- })
- }
-
- // 显示控制点
- showControls() {
- if (!this.controlLine1) return
- ;[
- this.controlLine1,
- this.controlLine2,
- this.controlPoint1,
- this.controlPoint2
- ].forEach(item => {
- item.show()
- })
- }
}
AssociativeLine.instanceName = 'associativeLine'
diff --git a/simple-mind-map/src/KeyCommand.js b/simple-mind-map/src/KeyCommand.js
index 9130e73e..579e219a 100644
--- a/simple-mind-map/src/KeyCommand.js
+++ b/simple-mind-map/src/KeyCommand.js
@@ -46,7 +46,7 @@ export default class KeyCommand {
if (this.mindMap.richText && this.mindMap.richText.showTextEdit) {
return
}
- if (this.mindMap.renderer.textEdit.showTextEdit) {
+ if (this.mindMap.renderer.textEdit.showTextEdit || (this.mindMap.associativeLine && this.mindMap.associativeLine.showTextEdit)) {
return
}
this.isInSvg = false
diff --git a/simple-mind-map/src/themes/default.js b/simple-mind-map/src/themes/default.js
index 0f2ad5ad..c9d9bfa6 100644
--- a/simple-mind-map/src/themes/default.js
+++ b/simple-mind-map/src/themes/default.js
@@ -34,6 +34,14 @@ export default {
associativeLineActiveWidth: 8,
// 关联线激活状态的颜色
associativeLineActiveColor: 'rgba(2, 167, 240, 1)',
+ // 关联线文字颜色
+ associativeLineTextColor: 'rgb(51, 51, 51)',
+ // 关联线文字大小
+ associativeLineTextFontSize: 14,
+ // 关联线文字行高
+ associativeLineTextLineHeight: 1.2,
+ // 关联线文字字体
+ associativeLineTextFontFamily: '微软雅黑, Microsoft YaHei',
// 背景颜色
backgroundColor: '#fafafa',
// 背景图片
@@ -148,4 +156,38 @@ export const supportActiveStyle = [
'borderRadius'
]
+// 检测主题配置是否是节点大小无关的
+const nodeSizeIndependenceList = [
+ 'lineWidth',
+ 'lineColor',
+ 'lineDasharray',
+ 'lineStyle',
+ 'generalizationLineWidth',
+ 'generalizationLineColor',
+ 'associativeLineWidth',
+ 'associativeLineColor',
+ 'associativeLineActiveWidth',
+ 'associativeLineActiveColor',
+ 'associativeLineTextColor',
+ 'associativeLineTextFontSize',
+ 'associativeLineTextLineHeight',
+ 'associativeLineTextFontFamily',
+ 'backgroundColor',
+ 'backgroundImage',
+ 'backgroundRepeat',
+ 'backgroundPosition',
+ 'backgroundSize'
+]
+export const checkIsNodeSizeIndependenceConfig = (config) => {
+ let keys = Object.keys(config)
+ for(let i = 0; i < keys.length; i++) {
+ if (!nodeSizeIndependenceList.find((item) => {
+ return item === keys[i]
+ })) {
+ return false
+ }
+ }
+ return true
+}
+
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
diff --git a/simple-mind-map/src/utils/associativeLineControls.js b/simple-mind-map/src/utils/associativeLineControls.js
new file mode 100644
index 00000000..105a69ef
--- /dev/null
+++ b/simple-mind-map/src/utils/associativeLineControls.js
@@ -0,0 +1,241 @@
+import {
+ getAssociativeLineTargetIndex,
+ joinCubicBezierPath,
+ computeNodePoints,
+ getDefaultControlPointOffsets
+} from './associativeLineUtils'
+
+// 创建控制点、连线节点
+function createControlNodes() {
+ let { associativeLineActiveColor } = this.mindMap.themeConfig
+ // 连线
+ this.controlLine1 = this.draw
+ .line()
+ .stroke({ color: associativeLineActiveColor, width: 2 })
+ this.controlLine2 = this.draw
+ .line()
+ .stroke({ color: associativeLineActiveColor, width: 2 })
+ // 控制点
+ this.controlPoint1 = this.createOneControlNode('controlPoint1')
+ this.controlPoint2 = this.createOneControlNode('controlPoint2')
+}
+
+// 创建控制点
+function createOneControlNode(pointKey) {
+ let { associativeLineActiveColor } = this.mindMap.themeConfig
+ return this.draw
+ .circle(this.controlPointDiameter)
+ .stroke({ color: associativeLineActiveColor })
+ .fill({ color: '#fff' })
+ .click(e => {
+ e.stopPropagation()
+ })
+ .mousedown(e => {
+ this.onControlPointMousedown(e, pointKey)
+ })
+}
+
+// 控制点的鼠标按下事件
+function onControlPointMousedown(e, pointKey) {
+ e.stopPropagation()
+ this.isControlPointMousedown = true
+ this.mousedownControlPointKey = pointKey
+}
+
+// 控制点的鼠标移动事件
+function onControlPointMousemove(e) {
+ if (
+ !this.isControlPointMousedown ||
+ !this.mousedownControlPointKey ||
+ !this[this.mousedownControlPointKey]
+ )
+ return
+ e.stopPropagation()
+ e.preventDefault()
+ let radius = this.controlPointDiameter / 2
+ // 转换鼠标当前的位置
+ let { x, y } = this.getTransformedEventPos(e)
+ this.controlPointMousemoveState.pos = {
+ x,
+ y
+ }
+ // 更新当前拖拽的控制点的位置
+ this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
+ let [path, clickPath, text, node, toNode] = this.activeLine
+ let [startPoint, endPoint] = computeNodePoints(node, toNode)
+ this.controlPointMousemoveState.startPoint = startPoint
+ this.controlPointMousemoveState.endPoint = endPoint
+ let targetIndex = getAssociativeLineTargetIndex(node, toNode)
+ this.controlPointMousemoveState.targetIndex = targetIndex
+ let offsets = []
+ let associativeLineTargetControlOffsets =
+ node.nodeData.data.associativeLineTargetControlOffsets
+ if (!associativeLineTargetControlOffsets) {
+ // 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
+ offsets = getDefaultControlPointOffsets(startPoint, endPoint)
+ } else {
+ offsets = associativeLineTargetControlOffsets[targetIndex]
+ }
+ let point1 = null
+ let point2 = null
+ // 拖拽的是控制点1
+ if (this.mousedownControlPointKey === 'controlPoint1') {
+ point1 = {
+ x,
+ y
+ }
+ point2 = {
+ x: endPoint.x + offsets[1].x,
+ y: endPoint.y + offsets[1].y
+ }
+ // 更新控制点1的连线
+ this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
+ } else {
+ // 拖拽的是控制点2
+ point1 = {
+ x: startPoint.x + offsets[0].x,
+ y: startPoint.y + offsets[0].y
+ }
+ point2 = {
+ x,
+ y
+ }
+ // 更新控制点2的连线
+ this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
+ }
+ // 更新关联线
+ let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
+ path.plot(pathStr)
+ clickPath.plot(pathStr)
+ this.updateTextPos(path, text)
+ this.updateTextEditBoxPos(text)
+}
+
+// 控制点的鼠标移动事件
+function onControlPointMouseup(e) {
+ if (!this.isControlPointMousedown) return
+ e.stopPropagation()
+ e.preventDefault()
+ let { pos, startPoint, endPoint, targetIndex } =
+ this.controlPointMousemoveState
+ let [, , , node] = this.activeLine
+ let offsetList = []
+ let associativeLineTargetControlOffsets =
+ node.nodeData.data.associativeLineTargetControlOffsets
+ if (!associativeLineTargetControlOffsets) {
+ // 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
+ offsetList[targetIndex] = getDefaultControlPointOffsets(
+ startPoint,
+ endPoint
+ )
+ } else {
+ offsetList = associativeLineTargetControlOffsets
+ }
+ let offset1 = null
+ let offset2 = null
+ if (this.mousedownControlPointKey === 'controlPoint1') {
+ // 更新控制点1数据
+ offset1 = {
+ x: pos.x - startPoint.x,
+ y: pos.y - startPoint.y
+ }
+ offset2 = offsetList[targetIndex][1]
+ } else {
+ // 更新控制点2数据
+ offset1 = offsetList[targetIndex][0]
+ offset2 = {
+ x: pos.x - endPoint.x,
+ y: pos.y - endPoint.y
+ }
+ }
+ offsetList[targetIndex] = [offset1, offset2]
+ this.mindMap.execCommand('SET_NODE_DATA', node, {
+ associativeLineTargetControlOffsets: offsetList
+ })
+ // 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
+ setTimeout(() => {
+ this.resetControlPoint()
+ }, 0)
+}
+
+// 复位控制点移动
+function resetControlPoint() {
+ this.isControlPointMousedown = false
+ this.mousedownControlPointKey = ''
+ this.controlPointMousemoveState = {
+ pos: null,
+ startPoint: null,
+ endPoint: null,
+ targetIndex: ''
+ }
+}
+
+// 渲染控制点
+function renderControls(startPoint, endPoint, point1, point2) {
+ if (!this.controlLine1) {
+ this.createControlNodes()
+ }
+ let radius = this.controlPointDiameter / 2
+ // 控制点和起终点的连线
+ this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
+ this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
+ // 控制点
+ this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
+ this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
+}
+
+// 删除控制点
+function removeControls() {
+ if (!this.controlLine1) return
+ ;[
+ this.controlLine1,
+ this.controlLine2,
+ this.controlPoint1,
+ this.controlPoint2
+ ].forEach(item => {
+ item.remove()
+ })
+ this.controlLine1 = null
+ this.controlLine2 = null
+ this.controlPoint1 = null
+ this.controlPoint2 = null
+}
+
+// 隐藏控制点
+function hideControls() {
+ if (!this.controlLine1) return
+ ;[
+ this.controlLine1,
+ this.controlLine2,
+ this.controlPoint1,
+ this.controlPoint2
+ ].forEach(item => {
+ item.hide()
+ })
+}
+
+// 显示控制点
+function showControls() {
+ if (!this.controlLine1) return
+ ;[
+ this.controlLine1,
+ this.controlLine2,
+ this.controlPoint1,
+ this.controlPoint2
+ ].forEach(item => {
+ item.show()
+ })
+}
+
+export default {
+ createControlNodes,
+ createOneControlNode,
+ onControlPointMousedown,
+ onControlPointMousemove,
+ onControlPointMouseup,
+ resetControlPoint,
+ renderControls,
+ removeControls,
+ hideControls,
+ showControls
+}
diff --git a/simple-mind-map/src/utils/associativeLineText.js b/simple-mind-map/src/utils/associativeLineText.js
new file mode 100644
index 00000000..f761ab13
--- /dev/null
+++ b/simple-mind-map/src/utils/associativeLineText.js
@@ -0,0 +1,167 @@
+import { Text } from '@svgdotjs/svg.js'
+import { getStrWithBrFromHtml } from './index'
+
+// 创建文字节点
+function createText(data) {
+ let g = this.draw.group()
+ const setActive = () => {
+ if (
+ !this.activeLine ||
+ this.activeLine[3] !== data.node ||
+ this.activeLine[4] !== data.toNode
+ ) {
+ this.setActiveLine({
+ ...data,
+ text: g
+ })
+ }
+ }
+ g.click(e => {
+ e.stopPropagation()
+ setActive()
+ })
+ g.on('dblclick', e => {
+ e.stopPropagation()
+ setActive()
+ if (!this.activeLine) return
+ this.showEditTextBox(g)
+ })
+ return g
+}
+
+// 显示文本编辑框
+function showEditTextBox(g) {
+ this.mindMap.emit('before_show_text_edit')
+ // 注册回车快捷键
+ this.mindMap.keyCommand.addShortcut('Enter', () => {
+ this.hideEditTextBox()
+ })
+
+ 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; word-break: break-all;`
+ this.textEditNode.setAttribute('contenteditable', true)
+ this.textEditNode.addEventListener('keyup', e => {
+ e.stopPropagation()
+ })
+ this.textEditNode.addEventListener('click', e => {
+ e.stopPropagation()
+ })
+ document.body.appendChild(this.textEditNode)
+ }
+ let {
+ associativeLineTextFontSize,
+ associativeLineTextFontFamily,
+ associativeLineTextLineHeight
+ } = this.mindMap.themeConfig
+ let scale = this.mindMap.view.scale
+ let [, , , node, toNode] = this.activeLine
+ let textLines = (
+ this.getText(node, toNode) || this.mindMap.opt.defaultAssociativeLineText
+ ).split(/\n/gim)
+ this.textEditNode.style.fontFamily = associativeLineTextFontFamily
+ this.textEditNode.style.fontSize = associativeLineTextFontSize * scale + 'px'
+ this.textEditNode.style.lineHeight = textLines.length > 1 ? associativeLineTextLineHeight : 'normal'
+ this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
+ this.textEditNode.innerHTML = textLines.join('
')
+ this.textEditNode.style.display = 'block'
+ this.updateTextEditBoxPos(g)
+ this.showTextEdit = true
+}
+
+// 处理画布缩放
+function onScale() {
+ this.hideEditTextBox()
+}
+
+// 更新文本编辑框位置
+function updateTextEditBoxPos(g) {
+ let rect = g.node.getBoundingClientRect()
+ 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'
+}
+
+// 隐藏文本编辑框
+function hideEditTextBox() {
+ if (!this.showTextEdit) {
+ return
+ }
+ let [path, , text, node, toNode] = this.activeLine
+ let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
+ this.mindMap.execCommand('SET_NODE_DATA', node, {
+ associativeLineText: {
+ ...(node.nodeData.data.associativeLineText || {}),
+ [toNode.nodeData.data.id]: str
+ }
+ })
+ this.textEditNode.style.display = 'none'
+ this.textEditNode.innerHTML = ''
+ this.showTextEdit = false
+ this.renderText(str, path, text)
+ this.mindMap.emit('hide_text_edit')
+}
+
+// 获取某根关联线的文字
+function getText(node, toNode) {
+ let obj = node.nodeData.data.associativeLineText
+ if (!obj) {
+ return ''
+ }
+ return obj[toNode.nodeData.data.id] || ''
+}
+
+// 渲染关联线文字
+function renderText(str, path, text) {
+ if (!str) return
+ let { associativeLineTextFontSize, associativeLineTextLineHeight } =
+ this.mindMap.themeConfig
+ text.clear()
+ let textArr = str.split(/\n/gim)
+ textArr.forEach((item, index) => {
+ let node = new Text().text(item)
+ node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index)
+ this.styleText(node)
+ text.add(node)
+ })
+ updateTextPos(path, text)
+}
+
+// 给文本设置样式
+function styleText(node) {
+ let {
+ associativeLineTextColor,
+ associativeLineTextFontSize,
+ associativeLineTextFontFamily
+ } = this.mindMap.themeConfig
+ node
+ .fill({
+ color: associativeLineTextColor
+ })
+ .css({
+ 'font-family': associativeLineTextFontFamily,
+ 'font-size': associativeLineTextFontSize
+ })
+}
+
+// 更新关联线文字位置
+function updateTextPos(path, text) {
+ let pathLength = path.length()
+ let centerPoint = path.pointAt(pathLength / 2)
+ let { width: textWidth, height: textHeight } = text.bbox()
+ text.x(centerPoint.x - textWidth / 2)
+ text.y(centerPoint.y - textHeight / 2)
+}
+
+export default {
+ getText,
+ createText,
+ styleText,
+ onScale,
+ showEditTextBox,
+ hideEditTextBox,
+ updateTextEditBoxPos,
+ renderText,
+ updateTextPos
+}
diff --git a/web/src/pages/Doc/en/associativeLine/index.md b/web/src/pages/Doc/en/associativeLine/index.md
index 12e7005e..9a726858 100644
--- a/web/src/pages/Doc/en/associativeLine/index.md
+++ b/web/src/pages/Doc/en/associativeLine/index.md
@@ -4,9 +4,9 @@
> The function of adjusting associated line control points is supported from v0.4.6+
-This plugin is used to support the addition of associative lines.
+> Relevance support for text editing starting from v0.5.11+
-The plugin is currently not fully functional, and does not support adding text to association lines.
+This plugin is used to support the addition of associative lines.
## Register
diff --git a/web/src/pages/Doc/en/associativeLine/index.vue b/web/src/pages/Doc/en/associativeLine/index.vue
index 168ce87a..4981edcc 100644
--- a/web/src/pages/Doc/en/associativeLine/index.vue
+++ b/web/src/pages/Doc/en/associativeLine/index.vue
@@ -7,8 +7,10 @@
The function of adjusting associated line control points is supported from v0.4.6+
+
+Relevance support for text editing starting from v0.5.11+
+
This plugin is used to support the addition of associative lines.
-The plugin is currently not fully functional, and does not support adding text to association lines.
Register
import MindMap from 'simple-mind-map'
import AssociativeLine from 'simple-mind-map/src/AssociativeLine.js'
diff --git a/web/src/pages/Doc/en/changelog/index.md b/web/src/pages/Doc/en/changelog/index.md
index f16b47c9..42236e0b 100644
--- a/web/src/pages/Doc/en/changelog/index.md
+++ b/web/src/pages/Doc/en/changelog/index.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.5.11
+
+New: Supports associative text editing.
+
+optimization: Optimizing theme configuration updates, changing configurations that do not involve node size does not trigger node recalculation.
+
## 0.5.10
New: Optimize node reuse logic using LRU caching algorithm.
diff --git a/web/src/pages/Doc/en/changelog/index.vue b/web/src/pages/Doc/en/changelog/index.vue
index 6218400f..6ae87866 100644
--- a/web/src/pages/Doc/en/changelog/index.vue
+++ b/web/src/pages/Doc/en/changelog/index.vue
@@ -1,6 +1,9 @@
Changelog
+
0.5.11
+
New: Supports associative text editing.
+
optimization: Optimizing theme configuration updates, changing configurations that do not involve node size does not trigger node recalculation.
0.5.10
New: Optimize node reuse logic using LRU caching algorithm.
0.5.10-fix.1
diff --git a/web/src/pages/Doc/zh/associativeLine/index.md b/web/src/pages/Doc/zh/associativeLine/index.md
index 1c3e937c..72f692d3 100644
--- a/web/src/pages/Doc/zh/associativeLine/index.md
+++ b/web/src/pages/Doc/zh/associativeLine/index.md
@@ -4,9 +4,9 @@
> 调整关联线控制点的功能从v0.4.6+开始支持
-该插件用于支持添加关联线。
+> 关联性支持文本编辑从v0.5.11+开始支持
-该插件目前功能还不完善,不支持在关联线上添加文字。
+该插件用于支持添加关联线。
## 注册
diff --git a/web/src/pages/Doc/zh/associativeLine/index.vue b/web/src/pages/Doc/zh/associativeLine/index.vue
index 2633650b..4e5cda2b 100644
--- a/web/src/pages/Doc/zh/associativeLine/index.vue
+++ b/web/src/pages/Doc/zh/associativeLine/index.vue
@@ -7,8 +7,10 @@
调整关联线控制点的功能从v0.4.6+开始支持
+
+关联性支持文本编辑从v0.5.11+开始支持
+
该插件用于支持添加关联线。
-
该插件目前功能还不完善,不支持在关联线上添加文字。
注册
import MindMap from 'simple-mind-map'
import AssociativeLine from 'simple-mind-map/src/AssociativeLine.js'
diff --git a/web/src/pages/Doc/zh/changelog/index.md b/web/src/pages/Doc/zh/changelog/index.md
index 00178f08..3fc6fc2d 100644
--- a/web/src/pages/Doc/zh/changelog/index.md
+++ b/web/src/pages/Doc/zh/changelog/index.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.5.11
+
+新增:支持关联性文本编辑。
+
+优化:优化主题配置更新,改变不涉及节点大小的配置不触发节点重新计算。
+
## 0.5.10
新增:使用LRU缓存算法优化节点复用逻辑。
diff --git a/web/src/pages/Doc/zh/changelog/index.vue b/web/src/pages/Doc/zh/changelog/index.vue
index aa84cf06..e4e59f09 100644
--- a/web/src/pages/Doc/zh/changelog/index.vue
+++ b/web/src/pages/Doc/zh/changelog/index.vue
@@ -1,6 +1,9 @@
Changelog
+
0.5.11
+
新增:支持关联性文本编辑。
+
优化:优化主题配置更新,改变不涉及节点大小的配置不触发节点重新计算。
0.5.10
新增:使用LRU缓存算法优化节点复用逻辑。
0.5.10-fix.1
diff --git a/web/src/pages/Doc/zh/course1/index.md b/web/src/pages/Doc/zh/course1/index.md
index 1a9123a0..5595cdfe 100644
--- a/web/src/pages/Doc/zh/course1/index.md
+++ b/web/src/pages/Doc/zh/course1/index.md
@@ -55,7 +55,7 @@
}
```
-`icon`目前只能使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。
+`icon`可以使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。也可以扩展图标,参考[扩展图标](https://wanglin2.github.io/mind-map/#/doc/zh/course19/%E6%89%A9%E5%B1%95%E5%9B%BE%E6%A0%87)。
创建实例时还支持传递其他很多选项参数,完整选项列表可以在[实例化选项](https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9)查看。
diff --git a/web/src/pages/Doc/zh/course1/index.vue b/web/src/pages/Doc/zh/course1/index.vue
index 20acb226..a2c288cf 100644
--- a/web/src/pages/Doc/zh/course1/index.vue
+++ b/web/src/pages/Doc/zh/course1/index.vue
@@ -46,7 +46,7 @@
children: []
}
-
icon目前只能使用内置的图标,完整图标可以在icons.js文件中查看。
+
icon可以使用内置的图标,完整图标可以在icons.js文件中查看。也可以扩展图标,参考扩展图标。
创建实例时还支持传递其他很多选项参数,完整选项列表可以在实例化选项查看。
这样得到的思维导图可以通过鼠标和快捷键进行操作,比如单击某个节点可以激活它,双击某个节点可以编辑节点文本,按下Tab键会给当前激活的节点添加一个子节点,按下Enter键会给当前激活的节点添加一个兄弟节点等等,完整的快捷键列表可以参考快捷键列表。
当然有些功能还是需要UI界面的,比如图标列表、编辑超链接等等,需要注意的是simple-mind-map库并不包含UI界面,所以需要你自己开发,然后通过simple-mind-map提供的相关API来操作,本教程的其他章节会向你介绍如何使用。
diff --git a/web/src/pages/Edit/components/BaseStyle.vue b/web/src/pages/Edit/components/BaseStyle.vue
index a901d4c0..78cada26 100644
--- a/web/src/pages/Edit/components/BaseStyle.vue
+++ b/web/src/pages/Edit/components/BaseStyle.vue
@@ -295,6 +295,67 @@
+
+ 关联线文字
+
+
+
+ 颜色
+
+
+ {
+ update('associativeLineTextColor', color)
+ }
+ "
+ >
+
+
+
+ 字号
+
+
+
+
+
+
{{ $t('baseStyle.nodeBorderType') }}
@@ -546,7 +607,7 @@