Feat:1.支持定义标签样式;2.新增标签显示位置的实例化选项;

This commit is contained in:
街角小林 2024-07-09 14:11:18 +08:00
parent ba9a6e501a
commit f79918ec6f
5 changed files with 147 additions and 49 deletions

View File

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

View File

@ -23,6 +23,8 @@ export const defaultOpt = {
mouseScaleCenterUseMousePosition: true, mouseScaleCenterUseMousePosition: true,
// 最多显示几个标签 // 最多显示几个标签
maxTag: 5, maxTag: 5,
// 标签显示的位置相对于节点文本bottom下方、right右侧
tagPosition: CONSTANTS.TAG_POSITION.RIGHT,
// 展开收缩按钮尺寸 // 展开收缩按钮尺寸
expandBtnSize: 20, expandBtnSize: 20,
// 节点里图片和文字的间距 // 节点里图片和文字的间距

View File

@ -253,11 +253,15 @@ class Node {
height: rect.height height: rect.height
} }
} }
const { tagPosition } = this.mindMap.opt
const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM
// 宽高 // 宽高
let imgContentWidth = 0 let imgContentWidth = 0
let imgContentHeight = 0 let imgContentHeight = 0
let textContentWidth = 0 let textContentWidth = 0
let textContentHeight = 0 let textContentHeight = 0
let tagContentWidth = 0
let tagContentHeight = 0
// 存在图片 // 存在图片
if (this._imgData) { if (this._imgData) {
this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width
@ -290,10 +294,20 @@ class Node {
} }
// 标签 // 标签
if (this._tagData.length > 0) { if (this._tagData.length > 0) {
textContentWidth += this._tagData.reduce((sum, cur) => { let maxTagHeight = 0
textContentHeight = Math.max(textContentHeight, cur.height) const totalTagWidth = this._tagData.reduce((sum, cur) => {
maxTagHeight = Math.max(maxTagHeight, cur.height)
return (sum += cur.width + this.textContentItemMargin) return (sum += cur.width + this.textContentItemMargin)
}, 0) }, 0)
if (tagIsBottom) {
// 文字下方
tagContentWidth = totalTagWidth
tagContentHeight = maxTagHeight
} else {
// 否则在右侧
textContentWidth += totalTagWidth
textContentHeight = Math.max(textContentHeight, maxTagHeight)
}
} }
// 备注 // 备注
if (this._noteData) { if (this._noteData) {
@ -325,6 +339,15 @@ class Node {
// 纯内容宽高 // 纯内容宽高
let _width = Math.max(imgContentWidth, textContentWidth) let _width = Math.max(imgContentWidth, textContentWidth)
let _height = imgContentHeight + textContentHeight let _height = imgContentHeight + textContentHeight
// 如果标签在文字下方
if (tagIsBottom && tagContentHeight > 0 && textContentHeight > 0) {
// 那么文字和标签之间也需要间距
margin += this.blockContentMargin
// 整体高度要考虑标签宽度
_width = Math.max(_width, tagContentWidth)
// 整体高度要加上标签的高度
_height += tagContentHeight
}
// 计算节点形状需要的附加内边距 // 计算节点形状需要的附加内边距
let { paddingX: shapePaddingX, paddingY: shapePaddingY } = let { paddingX: shapePaddingX, paddingY: shapePaddingY } =
this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY) this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY)
@ -342,7 +365,7 @@ class Node {
layout() { layout() {
// 清除之前的内容 // 清除之前的内容
this.group.clear() this.group.clear()
const { hoverRectPadding } = this.mindMap.opt const { hoverRectPadding, tagPosition } = this.mindMap.opt
let { width, height, textContentItemMargin } = this let { width, height, textContentItemMargin } = this
let { paddingY } = this.getPaddingVale() let { paddingY } = this.getPaddingVale()
const halfBorderWidth = this.getBorderWidth() / 2 const halfBorderWidth = this.getBorderWidth() / 2
@ -382,6 +405,8 @@ class Node {
addHoverNode() addHoverNode()
return return
} }
const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM
const { textContentHeight } = this._rectInfo
// 图片节点 // 图片节点
let imgHeight = 0 let imgHeight = 0
if (this._imgData) { if (this._imgData) {
@ -401,7 +426,7 @@ class Node {
}) })
foreignObject foreignObject
.x(textContentOffsetX) .x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._prefixData.height) / 2) .y((textContentHeight - this._prefixData.height) / 2)
textContentNested.add(foreignObject) textContentNested.add(foreignObject)
textContentOffsetX += this._prefixData.width + textContentItemMargin textContentOffsetX += this._prefixData.width + textContentItemMargin
} }
@ -412,7 +437,7 @@ class Node {
this._iconData.forEach(item => { this._iconData.forEach(item => {
item.node item.node
.x(textContentOffsetX + iconLeft) .x(textContentOffsetX + iconLeft)
.y((this._rectInfo.textContentHeight - item.height) / 2) .y((textContentHeight - item.height) / 2)
iconNested.add(item.node) iconNested.add(item.node)
iconLeft += item.width + textContentItemMargin iconLeft += item.width + textContentItemMargin
}) })
@ -427,7 +452,7 @@ class Node {
;(this._textData.nodeContent || this._textData.node) ;(this._textData.nodeContent || this._textData.node)
.x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题 .x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题
.x(textContentOffsetX) .x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._textData.height) / 2) .y((textContentHeight - this._textData.height) / 2)
textContentNested.add(this._textData.node) textContentNested.add(this._textData.node)
textContentOffsetX += this._textData.width + textContentItemMargin textContentOffsetX += this._textData.width + textContentItemMargin
} }
@ -435,29 +460,50 @@ class Node {
if (this._hyperlinkData) { if (this._hyperlinkData) {
this._hyperlinkData.node this._hyperlinkData.node
.x(textContentOffsetX) .x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2) .y((textContentHeight - this._hyperlinkData.height) / 2)
textContentNested.add(this._hyperlinkData.node) textContentNested.add(this._hyperlinkData.node)
textContentOffsetX += this._hyperlinkData.width + textContentItemMargin textContentOffsetX += this._hyperlinkData.width + textContentItemMargin
} }
// 标签 // 标签
let tagNested = new G() let tagNested = new G()
if (this._tagData && this._tagData.length > 0) { if (this._tagData && this._tagData.length > 0) {
let tagLeft = 0 if (tagIsBottom) {
this._tagData.forEach(item => { // 标签显示在文字下方
item.node let tagLeft = 0
.x(textContentOffsetX + tagLeft) this._tagData.forEach(item => {
.y((this._rectInfo.textContentHeight - item.height) / 2) item.node.x(tagLeft).y(0)
tagNested.add(item.node) tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin tagLeft += item.width + textContentItemMargin
}) })
textContentNested.add(tagNested) tagNested.cx(width / 2).y(
textContentOffsetX += tagLeft paddingY + // 内边距
imgHeight + // 图片高度
textContentHeight + // 文本区域高度
(imgHeight > 0 && textContentHeight > 0
? this.blockContentMargin
: 0) + // 图片和文本之间的间距
this.blockContentMargin // 标签和文本之间的间距
)
this.group.add(tagNested)
} else {
// 标签显示在文字右侧
let tagLeft = 0
this._tagData.forEach(item => {
item.node
.x(textContentOffsetX + tagLeft)
.y((textContentHeight - item.height) / 2)
tagNested.add(item.node)
tagLeft += item.width + textContentItemMargin
})
textContentNested.add(tagNested)
textContentOffsetX += tagLeft
}
} }
// 备注 // 备注
if (this._noteData) { if (this._noteData) {
this._noteData.node this._noteData.node
.x(textContentOffsetX) .x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._noteData.height) / 2) .y((textContentHeight - this._noteData.height) / 2)
textContentNested.add(this._noteData.node) textContentNested.add(this._noteData.node)
textContentOffsetX += this._noteData.width textContentOffsetX += this._noteData.width
} }
@ -465,7 +511,7 @@ class Node {
if (this._attachmentData) { if (this._attachmentData) {
this._attachmentData.node this._attachmentData.node
.x(textContentOffsetX) .x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2) .y((textContentHeight - this._attachmentData.height) / 2)
textContentNested.add(this._attachmentData.node) textContentNested.add(this._attachmentData.node)
textContentOffsetX += this._attachmentData.width textContentOffsetX += this._attachmentData.width
} }
@ -478,18 +524,16 @@ class Node {
}) })
foreignObject foreignObject
.x(textContentOffsetX) .x(textContentOffsetX)
.y((this._rectInfo.textContentHeight - this._postfixData.height) / 2) .y((textContentHeight - this._postfixData.height) / 2)
textContentNested.add(foreignObject) textContentNested.add(foreignObject)
textContentOffsetX += this._postfixData.width textContentOffsetX += this._postfixData.width
} }
// 文字内容整体 // 文字内容整体
textContentNested.translate( textContentNested.translate(
width / 2 - textContentNested.bbox().width / 2, width / 2 - textContentNested.bbox().width / 2,
imgHeight + paddingY + // 内边距
paddingY + imgHeight + // 图片高度
(imgHeight > 0 && this._rectInfo.textContentHeight > 0 (imgHeight > 0 && textContentHeight > 0 ? this.blockContentMargin : 0) // 和图片的间距
? this.blockContentMargin
: 0)
) )
this.group.add(textContentNested) this.group.add(textContentNested)
addHoverNode() addHoverNode()

View File

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

View File

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