mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-21 18:37:43 +08:00
440 lines
15 KiB
JavaScript
440 lines
15 KiB
JavaScript
import { CONSTANTS } from '../../../constants/constant'
|
||
import { G, Rect } from '@svgdotjs/svg.js'
|
||
import { createForeignObjectNode } from '../../../utils/index'
|
||
|
||
// 根据图片放置位置返回图片和文本的间距值
|
||
function getImgTextMarin(dir, imgWidth, textWidth, imgHeight, textHeight) {
|
||
// 图片和文字节点的间距
|
||
const { imgTextMargin } = this.mindMap.opt
|
||
if (dir === 'v') {
|
||
// 垂直
|
||
return imgHeight > 0 && textHeight > 0 ? imgTextMargin : 0
|
||
} else {
|
||
// 水平
|
||
return imgWidth > 0 && textWidth > 0 ? imgTextMargin : 0
|
||
}
|
||
}
|
||
|
||
// 获取标签内容的大小
|
||
function getTagContentSize(space) {
|
||
let maxTagHeight = 0
|
||
let width = this._tagData.reduce((sum, cur) => {
|
||
maxTagHeight = Math.max(maxTagHeight, cur.height)
|
||
return (sum += cur.width)
|
||
}, 0)
|
||
width += (this._tagData.length - 1) * space
|
||
return {
|
||
width,
|
||
height: maxTagHeight
|
||
}
|
||
}
|
||
|
||
// 计算节点尺寸信息
|
||
function getNodeRect() {
|
||
// 自定义节点内容
|
||
if (this.isUseCustomNodeContent()) {
|
||
const rect = this.measureCustomNodeContentSize(this._customNodeContent)
|
||
return {
|
||
width: this.hasCustomWidth() ? this.customTextWidth : rect.width,
|
||
height: rect.height
|
||
}
|
||
}
|
||
const { TAG_PLACEMENT, IMG_PLACEMENT } = CONSTANTS
|
||
const { textContentMargin } = this.mindMap.opt
|
||
const tagPlacement = this.getStyle('tagPlacement') || TAG_PLACEMENT.RIGHT
|
||
const tagIsBottom = tagPlacement === TAG_PLACEMENT.BOTTOM
|
||
const imgPlacement = this.getStyle('imgPlacement') || IMG_PLACEMENT.TOP
|
||
// 宽高
|
||
let imgContentWidth = 0
|
||
let imgContentHeight = 0
|
||
let textContentWidth = 0
|
||
let textContentHeight = 0
|
||
let tagContentWidth = 0
|
||
let tagContentHeight = 0
|
||
let spaceCount = 0
|
||
// 存在图片
|
||
if (this._imgData) {
|
||
imgContentWidth = this._imgData.width
|
||
imgContentHeight = this._imgData.height
|
||
}
|
||
// 库前置内容
|
||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||
const itemData = this[`_${item.name}Data`]
|
||
if (itemData) {
|
||
textContentWidth += itemData.width
|
||
textContentHeight = Math.max(textContentHeight, itemData.height)
|
||
spaceCount++
|
||
}
|
||
})
|
||
// 自定义前置内容
|
||
if (this._prefixData) {
|
||
textContentWidth += this._prefixData.width
|
||
textContentHeight = Math.max(textContentHeight, this._prefixData.height)
|
||
spaceCount++
|
||
}
|
||
// 图标
|
||
if (this._iconData.length > 0) {
|
||
textContentWidth +=
|
||
this._iconData.reduce((sum, cur) => {
|
||
textContentHeight = Math.max(textContentHeight, cur.height)
|
||
return (sum += cur.width)
|
||
}, 0) +
|
||
(this._iconData.length - 1) * textContentMargin
|
||
spaceCount++
|
||
}
|
||
// 文字
|
||
if (this._textData) {
|
||
textContentWidth += this._textData.width
|
||
textContentHeight = Math.max(textContentHeight, this._textData.height)
|
||
spaceCount++
|
||
}
|
||
// 超链接
|
||
if (this._hyperlinkData) {
|
||
textContentWidth += this._hyperlinkData.width
|
||
textContentHeight = Math.max(textContentHeight, this._hyperlinkData.height)
|
||
spaceCount++
|
||
}
|
||
// 标签
|
||
if (this._tagData.length > 0) {
|
||
const { width: totalTagWidth, height: maxTagHeight } =
|
||
this.getTagContentSize(textContentMargin)
|
||
if (tagIsBottom) {
|
||
// 文字下方
|
||
tagContentWidth = totalTagWidth
|
||
tagContentHeight = maxTagHeight
|
||
} else {
|
||
// 否则在右侧
|
||
textContentWidth += totalTagWidth
|
||
textContentHeight = Math.max(textContentHeight, maxTagHeight)
|
||
spaceCount++
|
||
}
|
||
}
|
||
// 备注
|
||
if (this._noteData) {
|
||
textContentWidth += this._noteData.width
|
||
textContentHeight = Math.max(textContentHeight, this._noteData.height)
|
||
spaceCount++
|
||
}
|
||
// 附件
|
||
if (this._attachmentData) {
|
||
textContentWidth += this._attachmentData.width
|
||
textContentHeight = Math.max(textContentHeight, this._attachmentData.height)
|
||
spaceCount++
|
||
}
|
||
// 自定义后置内容
|
||
if (this._postfixData) {
|
||
textContentWidth += this._postfixData.width
|
||
textContentHeight = Math.max(textContentHeight, this._postfixData.height)
|
||
spaceCount++
|
||
}
|
||
textContentWidth += (spaceCount - 1) * textContentMargin
|
||
// 文字内容部分的尺寸
|
||
if (tagIsBottom && textContentWidth > 0 && tagContentHeight > 0) {
|
||
this._rectInfo.textContentWidthWithoutTag = textContentWidth
|
||
textContentWidth = Math.max(textContentWidth, tagContentWidth)
|
||
textContentHeight = textContentHeight + textContentMargin + tagContentHeight
|
||
}
|
||
this._rectInfo.textContentWidth = textContentWidth
|
||
this._rectInfo.textContentHeight = textContentHeight
|
||
|
||
// 纯内容宽高
|
||
let _width = 0
|
||
let _height = 0
|
||
if ([IMG_PLACEMENT.TOP, IMG_PLACEMENT.BOTTOM].includes(imgPlacement)) {
|
||
// 图片在上下
|
||
_width = Math.max(imgContentWidth, textContentWidth)
|
||
_height =
|
||
imgContentHeight +
|
||
textContentHeight +
|
||
this.getImgTextMarin('v', 0, 0, imgContentHeight, textContentHeight)
|
||
} else {
|
||
// 图片在左右
|
||
_width =
|
||
imgContentWidth +
|
||
textContentWidth +
|
||
this.getImgTextMarin('h', imgContentWidth, textContentWidth)
|
||
_height = Math.max(imgContentHeight, textContentHeight)
|
||
}
|
||
const { paddingX, paddingY } = this.getPaddingVale()
|
||
// 计算节点形状需要的附加内边距
|
||
const { paddingX: shapePaddingX, paddingY: shapePaddingY } =
|
||
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
|
||
this.shapePadding.paddingX = shapePaddingX
|
||
this.shapePadding.paddingY = shapePaddingY
|
||
// 边框宽度,因为边框是以中线向两端发散,所以边框会超出节点
|
||
const borderWidth = this.getBorderWidth()
|
||
return {
|
||
width: _width + paddingX * 2 + shapePaddingX * 2 + borderWidth,
|
||
height: _height + paddingY * 2 + shapePaddingY * 2 + borderWidth
|
||
}
|
||
}
|
||
|
||
// 定位节点内容
|
||
function layout() {
|
||
if (!this.group) return
|
||
// 清除之前的内容
|
||
this.group.clear()
|
||
const {
|
||
hoverRectPadding,
|
||
openRealtimeRenderOnNodeTextEdit,
|
||
textContentMargin
|
||
} = this.mindMap.opt
|
||
// 避免编辑过程中展开收起按钮闪烁的问题
|
||
if (openRealtimeRenderOnNodeTextEdit && this._expandBtn) {
|
||
this.group.add(this._expandBtn)
|
||
}
|
||
const { width, height } = this
|
||
let { paddingX, paddingY } = this.getPaddingVale()
|
||
const halfBorderWidth = this.getBorderWidth() / 2
|
||
paddingX += this.shapePadding.paddingX + halfBorderWidth
|
||
paddingY += this.shapePadding.paddingY + halfBorderWidth
|
||
// 节点形状
|
||
this.shapeNode = this.shapeInstance.createShape()
|
||
this.shapeNode.addClass('smm-node-shape')
|
||
this.shapeNode.translate(halfBorderWidth, halfBorderWidth)
|
||
this.style.shape(this.shapeNode)
|
||
this.group.add(this.shapeNode)
|
||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||
this.renderExpandBtnPlaceholderRect()
|
||
// 创建协同头像节点
|
||
if (this.createUserListNode) this.createUserListNode()
|
||
// 概要节点添加一个带所属节点id的类名
|
||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||
}
|
||
// 激活hover和激活边框
|
||
const addHoverNode = () => {
|
||
this.hoverNode = new Rect()
|
||
.size(width + hoverRectPadding * 2, height + hoverRectPadding * 2)
|
||
.x(-hoverRectPadding)
|
||
.y(-hoverRectPadding)
|
||
this.hoverNode.addClass('smm-hover-node')
|
||
this.style.hoverNode(this.hoverNode, width, height)
|
||
this.group.add(this.hoverNode)
|
||
}
|
||
// 如果存在自定义节点内容,那么使用自定义节点内容
|
||
if (this.isUseCustomNodeContent()) {
|
||
const foreignObject = createForeignObjectNode({
|
||
el: this._customNodeContent,
|
||
width,
|
||
height
|
||
})
|
||
this.group.add(foreignObject)
|
||
addHoverNode()
|
||
return
|
||
}
|
||
const { IMG_PLACEMENT, TAG_PLACEMENT } = CONSTANTS
|
||
const imgPlacement = this.getStyle('imgPlacement') || IMG_PLACEMENT.TOP
|
||
const tagPlacement = this.getStyle('tagPlacement') || TAG_PLACEMENT.RIGHT
|
||
const tagIsBottom = tagPlacement === TAG_PLACEMENT.BOTTOM
|
||
let { textContentWidth, textContentHeight, textContentWidthWithoutTag } =
|
||
this._rectInfo
|
||
const textContentHeightWithTag = textContentHeight
|
||
// 如果存在显示在文本下方的标签,那么非标签内容的整体高度需要减去标签高度
|
||
let totalTagWidth = 0
|
||
let maxTagHeight = 0
|
||
const hasTagContent = this._tagData && this._tagData.length > 0
|
||
if (hasTagContent) {
|
||
const res = this.getTagContentSize(textContentMargin)
|
||
totalTagWidth = res.width
|
||
maxTagHeight = res.height
|
||
if (tagIsBottom) {
|
||
textContentHeight -= maxTagHeight + textContentMargin
|
||
}
|
||
}
|
||
// 图片节点
|
||
let imgWidth = 0
|
||
let imgHeight = 0
|
||
if (this._imgData) {
|
||
imgWidth = this._imgData.width
|
||
imgHeight = this._imgData.height
|
||
this.group.add(this._imgData.node)
|
||
switch (imgPlacement) {
|
||
case IMG_PLACEMENT.TOP:
|
||
this._imgData.node.cx(width / 2).y(paddingY)
|
||
break
|
||
case IMG_PLACEMENT.BOTTOM:
|
||
this._imgData.node.cx(width / 2).y(height - paddingY - imgHeight)
|
||
break
|
||
case IMG_PLACEMENT.LEFT:
|
||
this._imgData.node.x(paddingX).cy(height / 2)
|
||
break
|
||
case IMG_PLACEMENT.RIGHT:
|
||
this._imgData.node.x(width - paddingX - imgWidth).cy(height / 2)
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
// 内容节点
|
||
let textContentNested = new G()
|
||
let textContentOffsetX = 0
|
||
if (hasTagContent && tagIsBottom) {
|
||
textContentOffsetX =
|
||
textContentWidthWithoutTag < textContentWidth
|
||
? (textContentWidth - textContentWidthWithoutTag) / 2
|
||
: 0
|
||
}
|
||
// 库前置内容
|
||
this.mindMap.nodeInnerPrefixList.forEach(item => {
|
||
const itemData = this[`_${item.name}Data`]
|
||
if (itemData) {
|
||
itemData.node
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - itemData.height) / 2)
|
||
textContentNested.add(itemData.node)
|
||
textContentOffsetX += itemData.width + textContentMargin
|
||
}
|
||
})
|
||
// 自定义前置内容
|
||
if (this._prefixData) {
|
||
const foreignObject = createForeignObjectNode({
|
||
el: this._prefixData.el,
|
||
width: this._prefixData.width,
|
||
height: this._prefixData.height
|
||
})
|
||
foreignObject
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - this._prefixData.height) / 2)
|
||
textContentNested.add(foreignObject)
|
||
textContentOffsetX += this._prefixData.width + textContentMargin
|
||
}
|
||
// icon
|
||
let iconNested = new G()
|
||
if (this._iconData && this._iconData.length > 0) {
|
||
let iconLeft = 0
|
||
this._iconData.forEach(item => {
|
||
item.node
|
||
.x(textContentOffsetX + iconLeft)
|
||
.y((textContentHeight - item.height) / 2)
|
||
iconNested.add(item.node)
|
||
iconLeft += item.width + textContentMargin
|
||
})
|
||
textContentNested.add(iconNested)
|
||
textContentOffsetX += iconLeft
|
||
}
|
||
// 文字
|
||
if (this._textData) {
|
||
const oldX = this._textData.node.attr('data-offsetx') || 0
|
||
this._textData.node.attr('data-offsetx', textContentOffsetX)
|
||
// 修复safari浏览器节点存在图标时文字位置不正确的问题
|
||
;(this._textData.nodeContent || this._textData.node)
|
||
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - this._textData.height) / 2)
|
||
// 如果开启了文本编辑实时渲染,需要判断当前渲染的节点是否是正在编辑的节点,是的话将透明度设置为0不显示
|
||
if (openRealtimeRenderOnNodeTextEdit) {
|
||
this._textData.node.opacity(
|
||
this.mindMap.renderer.textEdit.getCurrentEditNode() === this ? 0 : 1
|
||
)
|
||
}
|
||
textContentNested.add(this._textData.node)
|
||
textContentOffsetX += this._textData.width + textContentMargin
|
||
}
|
||
// 超链接
|
||
if (this._hyperlinkData) {
|
||
this._hyperlinkData.node
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - this._hyperlinkData.height) / 2)
|
||
textContentNested.add(this._hyperlinkData.node)
|
||
textContentOffsetX += this._hyperlinkData.width + textContentMargin
|
||
}
|
||
// 标签
|
||
let tagNested = new G()
|
||
if (hasTagContent) {
|
||
if (tagIsBottom) {
|
||
// 标签显示在文字下方
|
||
let tagLeft = 0
|
||
this._tagData.forEach(item => {
|
||
item.node.x(tagLeft).y((maxTagHeight - item.height) / 2)
|
||
tagNested.add(item.node)
|
||
tagLeft += item.width + textContentMargin
|
||
})
|
||
tagNested
|
||
.x((textContentWidth - totalTagWidth) / 2)
|
||
.y(textContentHeightWithTag - maxTagHeight)
|
||
textContentNested.add(tagNested)
|
||
} else {
|
||
// 标签显示在文字右侧
|
||
let tagLeft = 0
|
||
this._tagData.forEach(item => {
|
||
item.node
|
||
.x(textContentOffsetX + tagLeft)
|
||
.y((textContentHeight - item.height) / 2)
|
||
tagNested.add(item.node)
|
||
tagLeft += item.width + textContentMargin
|
||
})
|
||
textContentNested.add(tagNested)
|
||
textContentOffsetX += tagLeft
|
||
}
|
||
}
|
||
// 备注
|
||
if (this._noteData) {
|
||
this._noteData.node
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - this._noteData.height) / 2)
|
||
textContentNested.add(this._noteData.node)
|
||
textContentOffsetX += this._noteData.width + textContentMargin
|
||
}
|
||
// 附件
|
||
if (this._attachmentData) {
|
||
this._attachmentData.node
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - this._attachmentData.height) / 2)
|
||
textContentNested.add(this._attachmentData.node)
|
||
textContentOffsetX += this._attachmentData.width + textContentMargin
|
||
}
|
||
// 自定义后置内容
|
||
if (this._postfixData) {
|
||
const foreignObject = createForeignObjectNode({
|
||
el: this._postfixData.el,
|
||
width: this._postfixData.width,
|
||
height: this._postfixData.height
|
||
})
|
||
foreignObject
|
||
.x(textContentOffsetX)
|
||
.y((textContentHeight - this._postfixData.height) / 2)
|
||
textContentNested.add(foreignObject)
|
||
textContentOffsetX += this._postfixData.width
|
||
}
|
||
this.group.add(textContentNested)
|
||
// 文字内容整体
|
||
const { width: bboxWidth, height: bboxHeight } = textContentNested.bbox()
|
||
let translateX = 0
|
||
let translateY = 0
|
||
switch (imgPlacement) {
|
||
case IMG_PLACEMENT.TOP:
|
||
translateX = width / 2 - bboxWidth / 2
|
||
translateY =
|
||
paddingY + // 内边距
|
||
imgHeight + // 图片高度
|
||
this.getImgTextMarin('v', 0, 0, imgHeight, textContentHeightWithTag) // 和图片的间距
|
||
break
|
||
case IMG_PLACEMENT.BOTTOM:
|
||
translateX = width / 2 - bboxWidth / 2
|
||
translateY = paddingY
|
||
break
|
||
case IMG_PLACEMENT.LEFT:
|
||
translateX =
|
||
imgWidth +
|
||
paddingX +
|
||
this.getImgTextMarin('h', imgWidth, textContentWidth)
|
||
translateY = height / 2 - bboxHeight / 2
|
||
break
|
||
case IMG_PLACEMENT.RIGHT:
|
||
translateX = paddingX
|
||
translateY = height / 2 - bboxHeight / 2
|
||
break
|
||
}
|
||
textContentNested.translate(translateX, translateY)
|
||
addHoverNode()
|
||
this.mindMap.emit('node_layout_end', this)
|
||
}
|
||
|
||
export default {
|
||
getImgTextMarin,
|
||
getTagContentSize,
|
||
getNodeRect,
|
||
layout
|
||
}
|