Merge pull request #294 from eSeaSky-WuHan/release
Feat: 1、折叠节点添加配置项; 2、关联线效果优化。
This commit is contained in:
commit
f2642c9d63
@ -68,13 +68,21 @@ export const defaultOpt = {
|
|||||||
// 展开收起按钮的颜色
|
// 展开收起按钮的颜色
|
||||||
expandBtnStyle: {
|
expandBtnStyle: {
|
||||||
color: '#808080',
|
color: '#808080',
|
||||||
fill: '#fff'
|
fill: '#fff',
|
||||||
|
fontSize: 13,
|
||||||
|
strokeColor: '#333333'
|
||||||
},
|
},
|
||||||
// 自定义展开收起按钮的图标
|
// 自定义展开收起按钮的图标
|
||||||
expandBtnIcon: {
|
expandBtnIcon: {
|
||||||
open: '', // svg字符串
|
open: '', // svg字符串
|
||||||
close: ''
|
close: ''
|
||||||
},
|
},
|
||||||
|
// 处理收起节点数量
|
||||||
|
expandBtnNumHandler: num => {
|
||||||
|
return num
|
||||||
|
},
|
||||||
|
// 是否显示带数量的收起按钮
|
||||||
|
isShowExpandNum: true,
|
||||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||||
enableShortcutOnlyWhenMouseInSvg: true,
|
enableShortcutOnlyWhenMouseInSvg: true,
|
||||||
// 初始根节点的位置
|
// 初始根节点的位置
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
import { tagColorList, nodeDataNoStylePropList } from '../../../constants/constant'
|
import {
|
||||||
|
tagColorList,
|
||||||
|
nodeDataNoStylePropList
|
||||||
|
} from '../../../constants/constant'
|
||||||
const rootProp = ['paddingX', 'paddingY']
|
const rootProp = ['paddingX', 'paddingY']
|
||||||
const backgroundStyleProps = ['backgroundColor', 'backgroundImage', 'backgroundRepeat', 'backgroundPosition', 'backgroundSize']
|
const backgroundStyleProps = [
|
||||||
|
'backgroundColor',
|
||||||
|
'backgroundImage',
|
||||||
|
'backgroundRepeat',
|
||||||
|
'backgroundPosition',
|
||||||
|
'backgroundSize'
|
||||||
|
]
|
||||||
|
|
||||||
// 样式类
|
// 样式类
|
||||||
class Style {
|
class Style {
|
||||||
@ -10,12 +19,18 @@ class Style {
|
|||||||
if (!Style.cacheStyle) {
|
if (!Style.cacheStyle) {
|
||||||
Style.cacheStyle = {}
|
Style.cacheStyle = {}
|
||||||
let style = window.getComputedStyle(el)
|
let style = window.getComputedStyle(el)
|
||||||
backgroundStyleProps.forEach((prop) => {
|
backgroundStyleProps.forEach(prop => {
|
||||||
Style.cacheStyle[prop] = style[prop]
|
Style.cacheStyle[prop] = style[prop]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 设置新样式
|
// 设置新样式
|
||||||
let { backgroundColor, backgroundImage, backgroundRepeat, backgroundPosition, backgroundSize } = themeConfig
|
let {
|
||||||
|
backgroundColor,
|
||||||
|
backgroundImage,
|
||||||
|
backgroundRepeat,
|
||||||
|
backgroundPosition,
|
||||||
|
backgroundSize
|
||||||
|
} = themeConfig
|
||||||
el.style.backgroundColor = backgroundColor
|
el.style.backgroundColor = backgroundColor
|
||||||
if (backgroundImage && backgroundImage !== 'none') {
|
if (backgroundImage && backgroundImage !== 'none') {
|
||||||
el.style.backgroundImage = `url(${backgroundImage})`
|
el.style.backgroundImage = `url(${backgroundImage})`
|
||||||
@ -30,7 +45,7 @@ class Style {
|
|||||||
// 移除背景样式
|
// 移除背景样式
|
||||||
static removeBackgroundStyle(el) {
|
static removeBackgroundStyle(el) {
|
||||||
if (!Style.cacheStyle) return
|
if (!Style.cacheStyle) return
|
||||||
backgroundStyleProps.forEach((prop) => {
|
backgroundStyleProps.forEach(prop => {
|
||||||
el.style[prop] = Style.cacheStyle[prop]
|
el.style[prop] = Style.cacheStyle[prop]
|
||||||
})
|
})
|
||||||
Style.cacheStyle = null
|
Style.cacheStyle = null
|
||||||
@ -131,10 +146,10 @@ class Style {
|
|||||||
|
|
||||||
// 获取文本样式
|
// 获取文本样式
|
||||||
getTextFontStyle() {
|
getTextFontStyle() {
|
||||||
return {
|
return {
|
||||||
italic: this.merge('fontStyle') === 'italic',
|
italic: this.merge('fontStyle') === 'italic',
|
||||||
bold: this.merge('fontWeight'),
|
bold: this.merge('fontWeight'),
|
||||||
fontSize: this.merge('fontSize'),
|
fontSize: this.merge('fontSize'),
|
||||||
fontFamily: this.merge('fontFamily')
|
fontFamily: this.merge('fontFamily')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,19 +205,26 @@ class Style {
|
|||||||
|
|
||||||
// 展开收起按钮
|
// 展开收起按钮
|
||||||
iconBtn(node, node2, fillNode) {
|
iconBtn(node, node2, fillNode) {
|
||||||
let { color, fill } = this.ctx.mindMap.opt.expandBtnStyle || {
|
let { color, fill, fontSize, fontColor } = this.ctx.mindMap.opt
|
||||||
|
.expandBtnStyle || {
|
||||||
color: '#808080',
|
color: '#808080',
|
||||||
fill: '#fff'
|
fill: '#fff',
|
||||||
|
fontSize: 12,
|
||||||
|
strokeColor: '#333333',
|
||||||
|
fontColor: '#333333'
|
||||||
}
|
}
|
||||||
node.fill({ color: color })
|
node.fill({ color: color })
|
||||||
node2.fill({ color: color })
|
node2.fill({ color: color })
|
||||||
fillNode.fill({ color: fill })
|
fillNode.fill({ color: fill })
|
||||||
|
if (this.ctx.mindMap.opt.isShowExpandNum) {
|
||||||
|
node.attr({ 'font-size': fontSize, 'font-color': fontColor })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否设置了自定义的样式
|
// 是否设置了自定义的样式
|
||||||
hasCustomStyle() {
|
hasCustomStyle() {
|
||||||
let res = false
|
let res = false
|
||||||
Object.keys(this.ctx.nodeData.data).forEach((item) => {
|
Object.keys(this.ctx.nodeData.data).forEach(item => {
|
||||||
if (!nodeDataNoStylePropList.includes(item)) {
|
if (!nodeDataNoStylePropList.includes(item)) {
|
||||||
res = true
|
res = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,16 +3,33 @@ import { SVG, Circle, G } from '@svgdotjs/svg.js'
|
|||||||
|
|
||||||
// 创建展开收起按钮的内容节点
|
// 创建展开收起按钮的内容节点
|
||||||
function createExpandNodeContent() {
|
function createExpandNodeContent() {
|
||||||
if (this._openExpandNode) {
|
if (this._openExpandNode && !this.mindMap.opt.isShowExpandNum) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let { open, close } = this.mindMap.opt.expandBtnIcon || {}
|
let { close, open } = this.mindMap.opt.expandBtnIcon || {}
|
||||||
// 展开的节点
|
// 根据配置判断是否显示数量按钮
|
||||||
this._openExpandNode = SVG(open || btnsSvg.open).size(
|
if (this.mindMap.opt.isShowExpandNum) {
|
||||||
this.expandBtnSize,
|
// 计算子节点数量
|
||||||
this.expandBtnSize
|
let count = this.sumNode(this.nodeData.children)
|
||||||
)
|
count = this.mindMap.opt.expandBtnNumHandler(count)
|
||||||
this._openExpandNode.x(0).y(-this.expandBtnSize / 2)
|
// 展开的节点
|
||||||
|
this._openExpandNode = SVG()
|
||||||
|
.text(count)
|
||||||
|
.size(this.expandBtnSize, this.expandBtnSize)
|
||||||
|
// 文本垂直居中
|
||||||
|
this._openExpandNode.attr({
|
||||||
|
'text-anchor': 'middle',
|
||||||
|
'dominant-baseline': 'middle',
|
||||||
|
x: this.expandBtnSize / 2,
|
||||||
|
y: 2
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this._openExpandNode = SVG(open || btnsSvg.open).size(
|
||||||
|
this.expandBtnSize,
|
||||||
|
this.expandBtnSize
|
||||||
|
)
|
||||||
|
this._openExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||||
|
}
|
||||||
// 收起的节点
|
// 收起的节点
|
||||||
this._closeExpandNode = SVG(close || btnsSvg.close).size(
|
this._closeExpandNode = SVG(close || btnsSvg.close).size(
|
||||||
this.expandBtnSize,
|
this.expandBtnSize,
|
||||||
@ -22,6 +39,7 @@ function createExpandNodeContent() {
|
|||||||
// 填充节点
|
// 填充节点
|
||||||
this._fillExpandNode = new Circle().size(this.expandBtnSize)
|
this._fillExpandNode = new Circle().size(this.expandBtnSize)
|
||||||
this._fillExpandNode.x(0).y(-this.expandBtnSize / 2)
|
this._fillExpandNode.x(0).y(-this.expandBtnSize / 2)
|
||||||
|
|
||||||
// 设置样式
|
// 设置样式
|
||||||
this.style.iconBtn(
|
this.style.iconBtn(
|
||||||
this._openExpandNode,
|
this._openExpandNode,
|
||||||
@ -29,7 +47,12 @@ function createExpandNodeContent() {
|
|||||||
this._fillExpandNode
|
this._fillExpandNode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
function sumNode(data = []) {
|
||||||
|
return data.reduce(
|
||||||
|
(total, cur) => total + this.sumNode(cur.children || []),
|
||||||
|
data.length
|
||||||
|
)
|
||||||
|
}
|
||||||
// 创建或更新展开收缩按钮内容
|
// 创建或更新展开收缩按钮内容
|
||||||
function updateExpandBtnNode() {
|
function updateExpandBtnNode() {
|
||||||
let { expand } = this.nodeData.data
|
let { expand } = this.nodeData.data
|
||||||
@ -47,7 +70,18 @@ function updateExpandBtnNode() {
|
|||||||
node = this._closeExpandNode
|
node = this._closeExpandNode
|
||||||
this._lastExpandBtnType = true
|
this._lastExpandBtnType = true
|
||||||
}
|
}
|
||||||
if (this._expandBtn) this._expandBtn.add(this._fillExpandNode).add(node)
|
|
||||||
|
if (this._expandBtn) {
|
||||||
|
// 如果是收起按钮加上边框
|
||||||
|
let opt = this.mindMap.opt
|
||||||
|
if (!expand && opt.isShowExpandNum) {
|
||||||
|
// 数字按钮添加边框
|
||||||
|
this._fillExpandNode.stroke({
|
||||||
|
color: opt.expandBtnStyle.strokeColor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this._expandBtn.add(this._fillExpandNode).add(node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新展开收缩按钮位置
|
// 更新展开收缩按钮位置
|
||||||
@ -138,5 +172,6 @@ export default {
|
|||||||
renderExpandBtn,
|
renderExpandBtn,
|
||||||
removeExpandBtn,
|
removeExpandBtn,
|
||||||
showExpandBtn,
|
showExpandBtn,
|
||||||
hideExpandBtn
|
hideExpandBtn,
|
||||||
|
sumNode
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import {
|
|||||||
cubicBezierPath,
|
cubicBezierPath,
|
||||||
getNodePoint,
|
getNodePoint,
|
||||||
computeNodePoints,
|
computeNodePoints,
|
||||||
getNodeLinePath
|
getNodeLinePath,
|
||||||
|
getDefaultControlPointOffsets
|
||||||
} from './associativeLine/associativeLineUtils'
|
} from './associativeLine/associativeLineUtils'
|
||||||
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
|
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
|
||||||
import associativeLineTextMethods from './associativeLine/associativeLineText'
|
import associativeLineTextMethods from './associativeLine/associativeLineText'
|
||||||
@ -99,12 +100,31 @@ class AssociativeLine {
|
|||||||
// 创建箭头
|
// 创建箭头
|
||||||
createMarker() {
|
createMarker() {
|
||||||
return this.draw.marker(20, 20, add => {
|
return this.draw.marker(20, 20, add => {
|
||||||
add.ref(2, 5)
|
add.ref(12, 5)
|
||||||
add.size(10, 10)
|
add.size(10, 10)
|
||||||
add.attr('orient', 'auto-start-reverse')
|
add.attr('orient', 'auto-start-reverse')
|
||||||
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
|
this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// 判断关联线坐标是否变更,有变更则使用变化后的坐标,无则默认坐标
|
||||||
|
updateAllLinesPos(node, toNode, associativeLinePoint) {
|
||||||
|
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||||
|
let nodeRange = 0
|
||||||
|
let nodeDir = 'right'
|
||||||
|
let toNodeRange = 0
|
||||||
|
let toNodeDir = 'right'
|
||||||
|
if (associativeLinePoint.startPoint) {
|
||||||
|
nodeRange = associativeLinePoint.startPoint.range || 0
|
||||||
|
nodeDir = associativeLinePoint.startPoint.dir || 'right'
|
||||||
|
startPoint = getNodePoint(node, nodeDir, nodeRange)
|
||||||
|
}
|
||||||
|
if (associativeLinePoint.endPoint) {
|
||||||
|
toNodeRange = associativeLinePoint.endPoint.range || 0
|
||||||
|
toNodeDir = associativeLinePoint.endPoint.dir || 'right'
|
||||||
|
endPoint = getNodePoint(toNode, toNodeDir, toNodeRange)
|
||||||
|
}
|
||||||
|
return [startPoint, endPoint]
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染所有连线
|
// 渲染所有连线
|
||||||
renderAllLines() {
|
renderAllLines() {
|
||||||
@ -137,10 +157,17 @@ class AssociativeLine {
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
nodeToIds.forEach((ids, node) => {
|
nodeToIds.forEach((ids, node) => {
|
||||||
ids.forEach(id => {
|
ids.forEach((id, index) => {
|
||||||
let toNode = idToNode.get(id)
|
let toNode = idToNode.get(id)
|
||||||
if (!node || !toNode) return
|
if (!node || !toNode) return
|
||||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
const associativeLinePoint =
|
||||||
|
node.nodeData.data.associativeLinePoint[index] || {}
|
||||||
|
// 切换结构和布局,都会更新坐标
|
||||||
|
const [startPoint, endPoint] = this.updateAllLinesPos(
|
||||||
|
node,
|
||||||
|
toNode,
|
||||||
|
associativeLinePoint
|
||||||
|
)
|
||||||
this.drawLine(startPoint, endPoint, node, toNode)
|
this.drawLine(startPoint, endPoint, node, toNode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -183,11 +210,28 @@ class AssociativeLine {
|
|||||||
.fill({ color: 'none' })
|
.fill({ color: 'none' })
|
||||||
clickPath.plot(pathStr)
|
clickPath.plot(pathStr)
|
||||||
// 文字
|
// 文字
|
||||||
let text = this.createText({ path, clickPath, node, toNode, startPoint, endPoint, controlPoints })
|
let text = this.createText({
|
||||||
|
path,
|
||||||
|
clickPath,
|
||||||
|
node,
|
||||||
|
toNode,
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
controlPoints
|
||||||
|
})
|
||||||
// 点击事件
|
// 点击事件
|
||||||
clickPath.click(e => {
|
clickPath.click(e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints })
|
this.setActiveLine({
|
||||||
|
path,
|
||||||
|
clickPath,
|
||||||
|
text,
|
||||||
|
node,
|
||||||
|
toNode,
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
controlPoints
|
||||||
|
})
|
||||||
})
|
})
|
||||||
// 渲染关联线文字
|
// 渲染关联线文字
|
||||||
this.renderText(this.getText(node, toNode), path, text)
|
this.renderText(this.getText(node, toNode), path, text)
|
||||||
@ -195,10 +239,17 @@ class AssociativeLine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 激活某根关联线
|
// 激活某根关联线
|
||||||
setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints }) {
|
setActiveLine({
|
||||||
let {
|
path,
|
||||||
associativeLineActiveColor
|
clickPath,
|
||||||
} = this.mindMap.themeConfig
|
text,
|
||||||
|
node,
|
||||||
|
toNode,
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
controlPoints
|
||||||
|
}) {
|
||||||
|
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||||
// 如果当前存在激活节点,那么取消激活节点
|
// 如果当前存在激活节点,那么取消激活节点
|
||||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||||
this.clearActiveNodes()
|
this.clearActiveNodes()
|
||||||
@ -243,8 +294,10 @@ class AssociativeLine {
|
|||||||
|
|
||||||
// 创建连接线
|
// 创建连接线
|
||||||
createLine(fromNode) {
|
createLine(fromNode) {
|
||||||
let { associativeLineWidth, associativeLineColor } =
|
let {
|
||||||
this.mindMap.themeConfig
|
associativeLineWidth,
|
||||||
|
associativeLineColor
|
||||||
|
} = this.mindMap.themeConfig
|
||||||
if (this.isCreatingLine || !fromNode) return
|
if (this.isCreatingLine || !fromNode) return
|
||||||
this.isCreatingLine = true
|
this.isCreatingLine = true
|
||||||
this.creatingStartNode = fromNode
|
this.creatingStartNode = fromNode
|
||||||
@ -279,14 +332,38 @@ class AssociativeLine {
|
|||||||
// 获取转换后的鼠标事件对象的坐标
|
// 获取转换后的鼠标事件对象的坐标
|
||||||
getTransformedEventPos(e) {
|
getTransformedEventPos(e) {
|
||||||
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
let { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||||
let { scaleX, scaleY, translateX, translateY } =
|
let {
|
||||||
this.mindMap.draw.transform()
|
scaleX,
|
||||||
|
scaleY,
|
||||||
|
translateX,
|
||||||
|
translateY
|
||||||
|
} = this.mindMap.draw.transform()
|
||||||
return {
|
return {
|
||||||
x: (x - translateX) / scaleX,
|
x: (x - translateX) / scaleX,
|
||||||
y: (y - translateY) / scaleY
|
y: (y - translateY) / scaleY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算节点偏移位置
|
||||||
|
getNodePos(node) {
|
||||||
|
const {
|
||||||
|
scaleX,
|
||||||
|
scaleY,
|
||||||
|
translateX,
|
||||||
|
translateY
|
||||||
|
} = this.mindMap.draw.transform()
|
||||||
|
const { left, top, width, height } = node
|
||||||
|
let translateLeft = left * scaleX + translateX
|
||||||
|
let translateTop = top * scaleY + translateY
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
translateLeft,
|
||||||
|
translateTop,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
// 检测当前移动到的目标节点
|
// 检测当前移动到的目标节点
|
||||||
checkOverlapNode(x, y) {
|
checkOverlapNode(x, y) {
|
||||||
this.overlapNode = null
|
this.overlapNode = null
|
||||||
@ -336,6 +413,11 @@ class AssociativeLine {
|
|||||||
}
|
}
|
||||||
// 将目标节点id保存起来
|
// 将目标节点id保存起来
|
||||||
let list = fromNode.nodeData.data.associativeLineTargets || []
|
let list = fromNode.nodeData.data.associativeLineTargets || []
|
||||||
|
// 连线节点是否存在相同的id,存在则阻止添加关联线
|
||||||
|
const sameLine = list.some(item => item === id)
|
||||||
|
if (sameLine) {
|
||||||
|
return
|
||||||
|
}
|
||||||
list.push(id)
|
list.push(id)
|
||||||
// 保存控制点
|
// 保存控制点
|
||||||
let [startPoint, endPoint] = computeNodePoints(fromNode, toNode)
|
let [startPoint, endPoint] = computeNodePoints(fromNode, toNode)
|
||||||
@ -358,9 +440,13 @@ class AssociativeLine {
|
|||||||
y: controlPoints[1].y - endPoint.y
|
y: controlPoints[1].y - endPoint.y
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
let associativeLinePoint = fromNode.nodeData.data.associativeLinePoint || []
|
||||||
|
// 记录关联的起始|结束坐标
|
||||||
|
associativeLinePoint[list.length - 1] = [{ startPoint, endPoint }]
|
||||||
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
|
this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
|
||||||
associativeLineTargets: list,
|
associativeLineTargets: list,
|
||||||
associativeLineTargetControlOffsets: offsetList
|
associativeLineTargetControlOffsets: offsetList,
|
||||||
|
associativeLinePoint
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,13 +455,17 @@ class AssociativeLine {
|
|||||||
if (!this.activeLine) return
|
if (!this.activeLine) return
|
||||||
let [, , , node, toNode] = this.activeLine
|
let [, , , node, toNode] = this.activeLine
|
||||||
this.removeControls()
|
this.removeControls()
|
||||||
let { associativeLineTargets, associativeLineTargetControlOffsets, associativeLineText } =
|
let {
|
||||||
node.nodeData.data
|
associativeLineTargets,
|
||||||
|
associativeLinePoint,
|
||||||
|
associativeLineTargetControlOffsets,
|
||||||
|
associativeLineText
|
||||||
|
} = node.nodeData.data
|
||||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||||
// 更新关联线文本数据
|
// 更新关联线文本数据
|
||||||
let newAssociativeLineText = {}
|
let newAssociativeLineText = {}
|
||||||
if (associativeLineText) {
|
if (associativeLineText) {
|
||||||
Object.keys(associativeLineText).forEach((item) => {
|
Object.keys(associativeLineText).forEach(item => {
|
||||||
if (item !== toNode.nodeData.data.id) {
|
if (item !== toNode.nodeData.data.id) {
|
||||||
newAssociativeLineText[item] = associativeLineText[item]
|
newAssociativeLineText[item] = associativeLineText[item]
|
||||||
}
|
}
|
||||||
@ -386,6 +476,10 @@ class AssociativeLine {
|
|||||||
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||||
return index !== targetIndex
|
return index !== targetIndex
|
||||||
}),
|
}),
|
||||||
|
// 连接线坐标
|
||||||
|
associativeLinePoint: associativeLinePoint.filter((_, index) => {
|
||||||
|
return index !== targetIndex
|
||||||
|
}),
|
||||||
// 偏移量
|
// 偏移量
|
||||||
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
|
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
|
||||||
? associativeLineTargetControlOffsets.filter((_, index) => {
|
? associativeLineTargetControlOffsets.filter((_, index) => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
getAssociativeLineTargetIndex,
|
getAssociativeLineTargetIndex,
|
||||||
joinCubicBezierPath,
|
joinCubicBezierPath,
|
||||||
computeNodePoints,
|
getNodePoint,
|
||||||
getDefaultControlPointOffsets
|
getDefaultControlPointOffsets
|
||||||
} from './associativeLineUtils'
|
} from './associativeLineUtils'
|
||||||
|
|
||||||
@ -61,15 +61,23 @@ function onControlPointMousemove(e) {
|
|||||||
}
|
}
|
||||||
// 更新当前拖拽的控制点的位置
|
// 更新当前拖拽的控制点的位置
|
||||||
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||||
let [path, clickPath, text, node, toNode] = this.activeLine
|
let [, , , node, toNode] = this.activeLine
|
||||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||||
|
const {
|
||||||
|
associativeLinePoint,
|
||||||
|
associativeLineTargetControlOffsets
|
||||||
|
} = node.nodeData.data
|
||||||
|
const nodePos = this.getNodePos(node)
|
||||||
|
const toNodePos = this.getNodePos(toNode)
|
||||||
|
let [startPoint, endPoint] = this.updateAllLinesPos(
|
||||||
|
node,
|
||||||
|
toNode,
|
||||||
|
associativeLinePoint[targetIndex]
|
||||||
|
)
|
||||||
this.controlPointMousemoveState.startPoint = startPoint
|
this.controlPointMousemoveState.startPoint = startPoint
|
||||||
this.controlPointMousemoveState.endPoint = endPoint
|
this.controlPointMousemoveState.endPoint = endPoint
|
||||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
|
||||||
this.controlPointMousemoveState.targetIndex = targetIndex
|
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||||
let offsets = []
|
let offsets = []
|
||||||
let associativeLineTargetControlOffsets =
|
|
||||||
node.nodeData.data.associativeLineTargetControlOffsets
|
|
||||||
if (!associativeLineTargetControlOffsets) {
|
if (!associativeLineTargetControlOffsets) {
|
||||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||||
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||||
@ -80,6 +88,7 @@ function onControlPointMousemove(e) {
|
|||||||
let point2 = null
|
let point2 = null
|
||||||
// 拖拽的是控制点1
|
// 拖拽的是控制点1
|
||||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||||
|
startPoint = getNodePoint(nodePos, '', 0, e)
|
||||||
point1 = {
|
point1 = {
|
||||||
x,
|
x,
|
||||||
y
|
y
|
||||||
@ -88,10 +97,16 @@ function onControlPointMousemove(e) {
|
|||||||
x: endPoint.x + offsets[1].x,
|
x: endPoint.x + offsets[1].x,
|
||||||
y: endPoint.y + offsets[1].y
|
y: endPoint.y + offsets[1].y
|
||||||
}
|
}
|
||||||
// 更新控制点1的连线
|
if (startPoint) {
|
||||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
// 保存更新后的坐标
|
||||||
|
associativeLinePoint[targetIndex].startPoint = startPoint
|
||||||
|
this.controlPointMousemoveState.startPoint = startPoint
|
||||||
|
// 更新控制点1的连线
|
||||||
|
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 拖拽的是控制点2
|
// 拖拽的是控制点2
|
||||||
|
endPoint = getNodePoint(toNodePos, '', 0, e)
|
||||||
point1 = {
|
point1 = {
|
||||||
x: startPoint.x + offsets[0].x,
|
x: startPoint.x + offsets[0].x,
|
||||||
y: startPoint.y + offsets[0].y
|
y: startPoint.y + offsets[0].y
|
||||||
@ -100,11 +115,33 @@ function onControlPointMousemove(e) {
|
|||||||
x,
|
x,
|
||||||
y
|
y
|
||||||
}
|
}
|
||||||
// 更新控制点2的连线
|
if (endPoint) {
|
||||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
// 保存更新后结束节点的坐标
|
||||||
|
associativeLinePoint[targetIndex].endPoint = endPoint
|
||||||
|
this.controlPointMousemoveState.endPoint = endPoint
|
||||||
|
// 更新控制点2的连线
|
||||||
|
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this.updataAassociativeLine(
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
point1,
|
||||||
|
point2,
|
||||||
|
this.activeLine
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updataAassociativeLine(
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
point1,
|
||||||
|
point2,
|
||||||
|
activeLine
|
||||||
|
) {
|
||||||
|
const [path, clickPath, text] = activeLine
|
||||||
// 更新关联线
|
// 更新关联线
|
||||||
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
const pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||||
path.plot(pathStr)
|
path.plot(pathStr)
|
||||||
clickPath.plot(pathStr)
|
clickPath.plot(pathStr)
|
||||||
this.updateTextPos(path, text)
|
this.updateTextPos(path, text)
|
||||||
@ -116,12 +153,18 @@ function onControlPointMouseup(e) {
|
|||||||
if (!this.isControlPointMousedown) return
|
if (!this.isControlPointMousedown) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let { pos, startPoint, endPoint, targetIndex } =
|
let {
|
||||||
this.controlPointMousemoveState
|
pos,
|
||||||
|
startPoint,
|
||||||
|
endPoint,
|
||||||
|
targetIndex
|
||||||
|
} = this.controlPointMousemoveState
|
||||||
let [, , , node] = this.activeLine
|
let [, , , node] = this.activeLine
|
||||||
let offsetList = []
|
let offsetList = []
|
||||||
let associativeLineTargetControlOffsets =
|
const {
|
||||||
node.nodeData.data.associativeLineTargetControlOffsets
|
associativeLinePoint,
|
||||||
|
associativeLineTargetControlOffsets
|
||||||
|
} = node.nodeData.data
|
||||||
if (!associativeLineTargetControlOffsets) {
|
if (!associativeLineTargetControlOffsets) {
|
||||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||||
offsetList[targetIndex] = getDefaultControlPointOffsets(
|
offsetList[targetIndex] = getDefaultControlPointOffsets(
|
||||||
@ -150,7 +193,8 @@ function onControlPointMouseup(e) {
|
|||||||
}
|
}
|
||||||
offsetList[targetIndex] = [offset1, offset2]
|
offsetList[targetIndex] = [offset1, offset2]
|
||||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||||
associativeLineTargetControlOffsets: offsetList
|
associativeLineTargetControlOffsets: offsetList,
|
||||||
|
associativeLinePoint
|
||||||
})
|
})
|
||||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -237,5 +281,6 @@ export default {
|
|||||||
renderControls,
|
renderControls,
|
||||||
removeControls,
|
removeControls,
|
||||||
hideControls,
|
hideControls,
|
||||||
showControls
|
showControls,
|
||||||
|
updataAassociativeLine
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ function showEditTextBox(g) {
|
|||||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||||
this.hideEditTextBox()
|
this.hideEditTextBox()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!this.textEditNode) {
|
if (!this.textEditNode) {
|
||||||
this.textEditNode = document.createElement('div')
|
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.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;`
|
||||||
@ -62,7 +62,8 @@ function showEditTextBox(g) {
|
|||||||
).split(/\n/gim)
|
).split(/\n/gim)
|
||||||
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
|
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
|
||||||
this.textEditNode.style.fontSize = associativeLineTextFontSize * scale + 'px'
|
this.textEditNode.style.fontSize = associativeLineTextFontSize * scale + 'px'
|
||||||
this.textEditNode.style.lineHeight = textLines.length > 1 ? associativeLineTextLineHeight : 'normal'
|
this.textEditNode.style.lineHeight =
|
||||||
|
textLines.length > 1 ? associativeLineTextLineHeight : 'normal'
|
||||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||||
this.textEditNode.style.display = 'block'
|
this.textEditNode.style.display = 'block'
|
||||||
@ -78,10 +79,12 @@ function onScale() {
|
|||||||
// 更新文本编辑框位置
|
// 更新文本编辑框位置
|
||||||
function updateTextEditBoxPos(g) {
|
function updateTextEditBoxPos(g) {
|
||||||
let rect = g.node.getBoundingClientRect()
|
let rect = g.node.getBoundingClientRect()
|
||||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
if (this.textEditNode) {
|
||||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
this.textEditNode.style.minWidth = `${rect.width + 10}px`
|
||||||
this.textEditNode.style.left = rect.left + 'px'
|
this.textEditNode.style.minHeight = `${rect.height + 6}px`
|
||||||
this.textEditNode.style.top = rect.top + 'px'
|
this.textEditNode.style.left = `${rect.left}px`
|
||||||
|
this.textEditNode.style.top = `${rect.top}px`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏文本编辑框
|
// 隐藏文本编辑框
|
||||||
@ -116,8 +119,10 @@ function getText(node, toNode) {
|
|||||||
// 渲染关联线文字
|
// 渲染关联线文字
|
||||||
function renderText(str, path, text) {
|
function renderText(str, path, text) {
|
||||||
if (!str) return
|
if (!str) return
|
||||||
let { associativeLineTextFontSize, associativeLineTextLineHeight } =
|
let {
|
||||||
this.mindMap.themeConfig
|
associativeLineTextFontSize,
|
||||||
|
associativeLineTextLineHeight
|
||||||
|
} = this.mindMap.themeConfig
|
||||||
text.clear()
|
text.clear()
|
||||||
let textArr = str.split(/\n/gim)
|
let textArr = str.split(/\n/gim)
|
||||||
textArr.forEach((item, index) => {
|
textArr.forEach((item, index) => {
|
||||||
|
|||||||
@ -54,28 +54,136 @@ export const cubicBezierPath = (x1, y1, x2, y2) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const calcPoint = (node, e) => {
|
||||||
|
const { left, top, translateLeft, translateTop, width, height } = node
|
||||||
|
const clientX = e.clientX
|
||||||
|
const clientY = e.clientY
|
||||||
|
// 中心点的坐标
|
||||||
|
const centerX = translateLeft + width / 2
|
||||||
|
const centerY = translateTop + height / 2
|
||||||
|
const translateCenterX = left + width / 2
|
||||||
|
const translateCenterY = top + height / 2
|
||||||
|
const theta = Math.atan(height / width)
|
||||||
|
// 矩形左上角坐标
|
||||||
|
const deltaX = clientX - centerX
|
||||||
|
const deltaY = centerY - clientY
|
||||||
|
// 方向值
|
||||||
|
const direction = Math.atan2(deltaY, deltaX)
|
||||||
|
// 默认坐标
|
||||||
|
let x = left + width
|
||||||
|
let y = top + height
|
||||||
|
if (direction < theta && direction >= -theta) {
|
||||||
|
// 右边
|
||||||
|
// 正切值 = 对边/邻边,对边 = 正切值*邻边
|
||||||
|
const range = direction * (width / 2)
|
||||||
|
if (direction < theta && direction >= 0) {
|
||||||
|
// 中心点上边
|
||||||
|
y = translateCenterY - range
|
||||||
|
} else if (direction >= -theta && direction < 0) {
|
||||||
|
// 中心点下方
|
||||||
|
y = translateCenterY - range
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
dir: 'right',
|
||||||
|
range
|
||||||
|
}
|
||||||
|
} else if (direction >= theta && direction < Math.PI - theta) {
|
||||||
|
// 上边
|
||||||
|
y = top
|
||||||
|
let range = 0
|
||||||
|
if (direction < Math.PI / 2 - theta && direction >= theta) {
|
||||||
|
// 正切值 = 对边/邻边,邻边 = 对边/正切值
|
||||||
|
const side = height / 2 / direction
|
||||||
|
range = -side
|
||||||
|
// 中心点右侧
|
||||||
|
x = translateCenterX + side
|
||||||
|
} else if (
|
||||||
|
direction >= Math.PI / 2 - theta &&
|
||||||
|
direction < Math.PI - theta
|
||||||
|
) {
|
||||||
|
// 中心点左侧
|
||||||
|
const tanValue = (centerX - clientX) / (centerY - clientY)
|
||||||
|
const side = (height / 2) * tanValue
|
||||||
|
range = side
|
||||||
|
x = translateCenterX - side
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
dir: 'top',
|
||||||
|
range
|
||||||
|
}
|
||||||
|
} else if (direction < -theta && direction >= theta - Math.PI) {
|
||||||
|
// 下边
|
||||||
|
let range = 0
|
||||||
|
if (direction >= theta - Math.PI / 2 && direction < -theta) {
|
||||||
|
// 中心点右侧
|
||||||
|
// 正切值 = 对边/邻边,邻边 = 对边/正切值
|
||||||
|
const side = height / 2 / direction
|
||||||
|
range = side
|
||||||
|
x = translateCenterX - side
|
||||||
|
} else if (
|
||||||
|
direction < theta - Math.PI / 2 &&
|
||||||
|
direction >= theta - Math.PI
|
||||||
|
) {
|
||||||
|
// 中心点左侧
|
||||||
|
const tanValue = (centerX - clientX) / (centerY - clientY)
|
||||||
|
const side = (height / 2) * tanValue
|
||||||
|
range = -side
|
||||||
|
x = translateCenterX + side
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
dir: 'bottom',
|
||||||
|
range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 左边
|
||||||
|
x = left
|
||||||
|
const tanValue = (centerY - clientY) / (centerX - clientX)
|
||||||
|
const range = tanValue * (width / 2)
|
||||||
|
if (direction >= -Math.PI && direction < theta - Math.PI) {
|
||||||
|
// 中心点右侧
|
||||||
|
y = translateCenterY - range
|
||||||
|
} else if (direction < Math.PI && direction >= Math.PI - theta) {
|
||||||
|
// 中心点左侧
|
||||||
|
y = translateCenterY - range
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
dir: 'left',
|
||||||
|
range
|
||||||
|
}
|
||||||
|
}
|
||||||
// 获取节点的连接点
|
// 获取节点的连接点
|
||||||
export const getNodePoint = (node, dir = 'right') => {
|
export const getNodePoint = (node, dir = 'right', range = 0, e = null) => {
|
||||||
let { left, top, width, height } = node
|
let { left, top, width, height } = node
|
||||||
|
if (e) {
|
||||||
|
return calcPoint(node, e)
|
||||||
|
}
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
case 'left':
|
case 'left':
|
||||||
return {
|
return {
|
||||||
x: left,
|
x: left,
|
||||||
y: top + height / 2
|
y: top + height / 2 - range
|
||||||
}
|
}
|
||||||
case 'right':
|
case 'right':
|
||||||
return {
|
return {
|
||||||
x: left + width,
|
x: left + width,
|
||||||
y: top + height / 2
|
y: top + height / 2 - range
|
||||||
}
|
}
|
||||||
case 'top':
|
case 'top':
|
||||||
return {
|
return {
|
||||||
x: left + width / 2,
|
x: left + width / 2 - range,
|
||||||
y: top
|
y: top
|
||||||
}
|
}
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
return {
|
return {
|
||||||
x: left + width / 2,
|
x: left + width / 2 - range,
|
||||||
y: top + height
|
y: top + height
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -111,8 +219,8 @@ export const computeNodePoints = (fromNode, toNode) => {
|
|||||||
toDir = 'bottom'
|
toDir = 'bottom'
|
||||||
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
|
} else if (offsetY > 0 && -offsetY < offsetX && offsetY > offsetX) {
|
||||||
// down
|
// down
|
||||||
fromDir = 'bottom'
|
fromDir = 'right'
|
||||||
toDir = 'top'
|
toDir = 'right'
|
||||||
}
|
}
|
||||||
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
|
return [getNodePoint(fromNode, fromDir), getNodePoint(toNode, toDir)]
|
||||||
}
|
}
|
||||||
@ -179,4 +287,4 @@ export const getDefaultControlPointOffsets = (startPoint, endPoint) => {
|
|||||||
y: controlPoints[1].y - endPoint.y
|
y: controlPoints[1].y - endPoint.y
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user