diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index dfd0ae4c..23f069c3 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -235,6 +235,10 @@ export const CONSTANTS = { DEFAULT: 'default', NOT_ACTIVE: 'notActive', ACTIVE_ONLY: 'activeOnly' + }, + TAG_POSITION: { + RIGHT: 'right', + BOTTOM: 'bottom' } } diff --git a/simple-mind-map/src/constants/defaultOptions.js b/simple-mind-map/src/constants/defaultOptions.js index 594b751f..83114860 100644 --- a/simple-mind-map/src/constants/defaultOptions.js +++ b/simple-mind-map/src/constants/defaultOptions.js @@ -23,6 +23,8 @@ export const defaultOpt = { mouseScaleCenterUseMousePosition: true, // 最多显示几个标签 maxTag: 5, + // 标签显示的位置,相对于节点文本,bottom(下方)、right(右侧) + tagPosition: CONSTANTS.TAG_POSITION.RIGHT, // 展开收缩按钮尺寸 expandBtnSize: 20, // 节点里图片和文字的间距 diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index 431ef6d5..b1dec1eb 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -253,11 +253,15 @@ class Node { height: rect.height } } + const { tagPosition } = this.mindMap.opt + const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM // 宽高 let imgContentWidth = 0 let imgContentHeight = 0 let textContentWidth = 0 let textContentHeight = 0 + let tagContentWidth = 0 + let tagContentHeight = 0 // 存在图片 if (this._imgData) { this._rectInfo.imgContentWidth = imgContentWidth = this._imgData.width @@ -290,10 +294,20 @@ class Node { } // 标签 if (this._tagData.length > 0) { - textContentWidth += this._tagData.reduce((sum, cur) => { - textContentHeight = Math.max(textContentHeight, cur.height) + let maxTagHeight = 0 + const totalTagWidth = this._tagData.reduce((sum, cur) => { + maxTagHeight = Math.max(maxTagHeight, cur.height) return (sum += cur.width + this.textContentItemMargin) }, 0) + if (tagIsBottom) { + // 文字下方 + tagContentWidth = totalTagWidth + tagContentHeight = maxTagHeight + } else { + // 否则在右侧 + textContentWidth += totalTagWidth + textContentHeight = Math.max(textContentHeight, maxTagHeight) + } } // 备注 if (this._noteData) { @@ -325,6 +339,15 @@ class Node { // 纯内容宽高 let _width = Math.max(imgContentWidth, textContentWidth) 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 } = this.shapeInstance.getShapePadding(_width, _height, paddingX, paddingY) @@ -342,7 +365,7 @@ class Node { layout() { // 清除之前的内容 this.group.clear() - const { hoverRectPadding } = this.mindMap.opt + const { hoverRectPadding, tagPosition } = this.mindMap.opt let { width, height, textContentItemMargin } = this let { paddingY } = this.getPaddingVale() const halfBorderWidth = this.getBorderWidth() / 2 @@ -382,6 +405,8 @@ class Node { addHoverNode() return } + const tagIsBottom = tagPosition === CONSTANTS.TAG_POSITION.BOTTOM + const { textContentHeight } = this._rectInfo // 图片节点 let imgHeight = 0 if (this._imgData) { @@ -401,7 +426,7 @@ class Node { }) foreignObject .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._prefixData.height) / 2) + .y((textContentHeight - this._prefixData.height) / 2) textContentNested.add(foreignObject) textContentOffsetX += this._prefixData.width + textContentItemMargin } @@ -412,7 +437,7 @@ class Node { this._iconData.forEach(item => { item.node .x(textContentOffsetX + iconLeft) - .y((this._rectInfo.textContentHeight - item.height) / 2) + .y((textContentHeight - item.height) / 2) iconNested.add(item.node) iconLeft += item.width + textContentItemMargin }) @@ -427,7 +452,7 @@ class Node { ;(this._textData.nodeContent || this._textData.node) .x(-oldX) // 修复非富文本模式下同时存在图标和换行的文本时,被收起和展开时图标与文字距离会逐渐拉大的问题 .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._textData.height) / 2) + .y((textContentHeight - this._textData.height) / 2) textContentNested.add(this._textData.node) textContentOffsetX += this._textData.width + textContentItemMargin } @@ -435,29 +460,50 @@ class Node { if (this._hyperlinkData) { this._hyperlinkData.node .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._hyperlinkData.height) / 2) + .y((textContentHeight - this._hyperlinkData.height) / 2) textContentNested.add(this._hyperlinkData.node) textContentOffsetX += this._hyperlinkData.width + textContentItemMargin } // 标签 let tagNested = new G() if (this._tagData && this._tagData.length > 0) { - let tagLeft = 0 - this._tagData.forEach(item => { - item.node - .x(textContentOffsetX + tagLeft) - .y((this._rectInfo.textContentHeight - item.height) / 2) - tagNested.add(item.node) - tagLeft += item.width + textContentItemMargin - }) - textContentNested.add(tagNested) - textContentOffsetX += tagLeft + if (tagIsBottom) { + // 标签显示在文字下方 + let tagLeft = 0 + this._tagData.forEach(item => { + item.node.x(tagLeft).y(0) + tagNested.add(item.node) + tagLeft += item.width + textContentItemMargin + }) + tagNested.cx(width / 2).y( + 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) { this._noteData.node .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._noteData.height) / 2) + .y((textContentHeight - this._noteData.height) / 2) textContentNested.add(this._noteData.node) textContentOffsetX += this._noteData.width } @@ -465,7 +511,7 @@ class Node { if (this._attachmentData) { this._attachmentData.node .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._attachmentData.height) / 2) + .y((textContentHeight - this._attachmentData.height) / 2) textContentNested.add(this._attachmentData.node) textContentOffsetX += this._attachmentData.width } @@ -478,18 +524,16 @@ class Node { }) foreignObject .x(textContentOffsetX) - .y((this._rectInfo.textContentHeight - this._postfixData.height) / 2) + .y((textContentHeight - this._postfixData.height) / 2) textContentNested.add(foreignObject) textContentOffsetX += this._postfixData.width } // 文字内容整体 textContentNested.translate( width / 2 - textContentNested.bbox().width / 2, - imgHeight + - paddingY + - (imgHeight > 0 && this._rectInfo.textContentHeight > 0 - ? this.blockContentMargin - : 0) + paddingY + // 内边距 + imgHeight + // 图片高度 + (imgHeight > 0 && textContentHeight > 0 ? this.blockContentMargin : 0) // 和图片的间距 ) this.group.add(textContentNested) addHoverNode() diff --git a/simple-mind-map/src/core/render/node/Style.js b/simple-mind-map/src/core/render/node/Style.js index 2f033692..1248fbb7 100644 --- a/simple-mind-map/src/core/render/node/Style.js +++ b/simple-mind-map/src/core/render/node/Style.js @@ -1,8 +1,4 @@ -import { - checkIsNodeStyleDataKey, - generateColorByContent -} from '../../../utils/index' -import { Gradient } from '@svgdotjs/svg.js' +import { checkIsNodeStyleDataKey } from '../../../utils/index' const rootProp = ['paddingX', 'paddingY'] const backgroundStyleProps = [ @@ -182,21 +178,24 @@ class Style { } // 标签文字 - tagText(node) { + tagText(node, style) { node .fill({ color: '#fff' }) .css({ - 'font-size': '12px' + 'font-size': style.fontSize + 'px' }) } // 标签矩形 - tagRect(node, text, color) { + tagRect(node, style) { node.fill({ - color: color || generateColorByContent(text.node.textContent) + color: style.fill }) + if (style.radius) { + node.radius(style.radius) + } } // 内置图标 diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js index 343e5181..04fea5ef 100644 --- a/simple-mind-map/src/core/render/node/nodeCreateContents.js +++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js @@ -6,12 +6,23 @@ import { checkIsRichText, isUndef, createForeignObjectNode, - addXmlns + addXmlns, + generateColorByContent } from '../../../utils' import { Image as SVGImage, SVG, A, G, Rect, Text } from '@svgdotjs/svg.js' import iconsSvg from '../../../svg/icons' import { CONSTANTS } from '../../../constants/constant' +// 标签默认的样式 +const defaultTagStyle = { + radius: 3, // 标签矩形的圆角大小 + fontSize: 12, // 字号,建议文字高度不要大于height + fill: '', // 标签矩形的背景颜色 + height: 20, // 标签矩形的高度 + paddingX: 8 // 水平内边距,如果设置了width,将忽略该配置 + //width: 30 // 标签矩形的宽度,如果不设置,默认以文字的宽度+paddingX*2为宽度 +} + // 创建图片节点 function createImgNode() { const img = this.getData('image') @@ -284,31 +295,69 @@ function createHyperlinkNode() { // 创建标签节点 function createTagNode() { - let tagData = this.getData('tag') + const tagData = this.getData('tag') if (!tagData || tagData.length <= 0) { return [] } - let nodes = [] - tagData.slice(0, this.mindMap.opt.maxTag).forEach((item, index) => { - let tag = new G() + let { maxTag, tagsColorMap } = this.mindMap.opt + tagsColorMap = tagsColorMap || {} + 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', () => { this.mindMap.emit('node_tag_click', this, item) }) // 标签文本 - let text = new Text().text(item).x(8).cy(8) - this.style.tagText(text, index) - let { width } = text.bbox() + const text = new Text().text(str) + this.style.tagText(text, style) + // 获取文本宽高 + 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 tagsColorList = this.mindMap.opt.tagsColorMap || {} - const color = tagsColorList[text.node.textContent] - this.style.tagRect(rect, text, color) + const rect = new Rect().size(rectWidth, style.height).cy(-maxHeight / 2) + if (hasCustomWidth) { + rect.x((maxWidth - rectWidth) / 2) + } + this.style.tagRect(rect, { + ...style, + fill: + style.fill || // 优先节点自身配置 + tagsColorMap[text.node.textContent] || // 否则尝试从实例化选项tagsColorMap映射中获取颜色 + generateColorByContent(text.node.textContent) // 否则按照标签内容生成 + }) tag.add(rect).add(text) nodes.push({ node: tag, - width: width + 16, - height: 20 + width: maxWidth, + height: maxHeight }) }) return nodes