完成节点内容布局

This commit is contained in:
wanglin 2021-06-20 23:03:23 +08:00
parent 08c7768b18
commit 947c4b2b44
21 changed files with 497 additions and 193 deletions

View File

@ -6,29 +6,27 @@
export default {
"root": {
"data": {
"text": "鱼骨头图",
"text": "根节点",
},
"children": [{
"data": {
"text": "分支主题",
"expand": true
"text": "二级节点",
"expand": true,
},
"children": [{
"data": {
"text": "分支主题",
"hyperlink": "https://naotu.baidu.com/",
"hyperlinkTitle": "百度脑图",
"image": "https://kityminder-img.gz.bcebos.com/865551aedebd1e02ac6e76d24c093231df9aafda",
"text": "子节点",
"image": "http://aliyuncdn.lxqnsys.com/whbm/enJFNMHnedQTYTESGfDkctCp2",
"imageTitle": "图片名称",
"imageSize": {
"width": 200,
"height": 112
"width": 1000,
"height": 563
},
"note": "我是备注",
"resource": ["标签1", "标签2"],
"priority": 5,
"progress": 7,
// ... 其他类型的图标
"icon": ['a'],
"tag": ["标签1", "标签2"],
"hyperlink": "http://lxqnsys.com/",
"hyperlinkTitle": "理想青年实验室",
"note": "理想青年实验室\n一个有意思的角落"
},
"children": []
}]

View File

@ -4,12 +4,11 @@ import {
} from './utils'
import {
Image,
Text,
SVG,
Circle,
Element
Circle
} from '@svgdotjs/svg.js'
import btnsSvg from './svg/btns'
import iconsSvg from './svg/icons';
/**
* javascript comment
@ -26,7 +25,7 @@ class Node {
*/
constructor(opt = {}) {
// 节点数据
this.data = opt.data || {}
this.nodeData = this.handleData(opt.data || {})
// id
this.uid = opt.uid
// 控制实例
@ -41,16 +40,14 @@ class Node {
this.style = new Style(this, this.themeConfig)
// 是否是根节点
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
// 是否激活
this.isActive = opt.isActive === undefined ? false : opt.isActive
// 是否展开
this.expand = opt.expand === undefined ? true : opt.expand
// 节点层级
this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex
// 节点宽
this.width = opt.width || 0
// 节点高
this.height = opt.height || 0
// 节点文字内容部分高
this._textContentHeight = 0
// left
this.left = opt.left || 0
// top
@ -61,6 +58,23 @@ class Node {
this.children = opt.children || []
// 文本节点
this.textNode = null
// icon间距
this._textContentItemMargin = 2
// 图片和文字节点的间距
this._blockContentMargin = 5
// 计算节点尺寸
this.refreshSize()
}
/**
* @Author: 王林
* @Date: 2021-06-20 10:12:31
* @Desc: 处理数据
*/
handleData(data) {
data.data.expand = data.data.expand === false ? false : true
data.data.isActive = data.data.isActive === true ? true : false
return data;
}
/**
@ -95,39 +109,62 @@ class Node {
* @Desc: 计算节点尺寸信息
*/
getNodeRect() {
let width = this.themeConfig.paddingX * 2
let height = this.themeConfig.paddingY * 2
let maxWidth = 0
if (this.img) {
let img = this.createImgNode()
if (img.width > maxWidth) {
maxWidth = img.width
}
height += img.height
// 宽高
let imgContentWidth = 0
let imgContentHeight = 0
let textContentWidth = 0
let textContentHeight = 0
// 存在图片
let imgObj = this.createImgNode()
if (imgObj) {
imgContentWidth = imgObj.width
imgContentHeight = imgObj.height
}
if (this.icon && this.text) {
let icon = this.createIconNode()
let text = this.createTextNode()
if (icon.width + text.width > maxWidth) {
maxWidth = icon.width + text.width
}
height += Math.max(text.height, icon.height)
} else if (this.text) {
let text = this.createTextNode()
if (text.width > maxWidth) {
maxWidth = text.width
}
height += text.height
} else if (this.icon) {
let icon = this.createIconNode()
if (icon.width > maxWidth) {
maxWidth = icon.width
}
height += icon.height
// 图标
let iconObjs = this.createIconNode()
if (iconObjs.length > 0) {
textContentWidth += iconObjs.reduce((sum, cur) => {
textContentHeight = Math.max(textContentHeight, cur.height)
return sum += cur.width + this._textContentItemMargin
}, 0)
}
// 文字
let textObj = this.createTextNode()
if (textObj) {
textContentWidth += textObj.width
textContentHeight = Math.max(textContentHeight, textObj.height)
}
// 超链接
let hyperlinkObj = this.createHyperlinkNode()
if (hyperlinkObj) {
textContentWidth += hyperlinkObj.width
textContentHeight = Math.max(textContentHeight, hyperlinkObj.height)
hyperlinkObj.node.remove()
}
// 标签
let tagObjs = this.createTagNode()
if (tagObjs.length > 0) {
textContentWidth += tagObjs.reduce((sum, cur) => {
textContentHeight = Math.max(textContentHeight, cur.height)
cur.node.remove()
return sum += cur.width + this._textContentItemMargin
}, 0)
}
// 备注
let noteObj = this.createNoteNode()
if (noteObj) {
textContentWidth += noteObj.width
textContentHeight = Math.max(textContentHeight, noteObj.height)
noteObj.node.remove()
}
// 文字内容部分的高度
this._textContentHeight = textContentHeight
// 间距
let margin = imgContentHeight > 0 && textContentHeight > 0 ? this._blockContentMargin : 0
let { paddingX, paddingY } = this.getPaddingVale()
return {
width: width + maxWidth,
height
width: Math.max(imgContentWidth, textContentWidth) + paddingX * 2,
height: imgContentHeight + textContentHeight + paddingY * 2 + margin
}
}
@ -138,17 +175,55 @@ class Node {
* @Desc: 创建图片节点
*/
createImgNode() {
if (!this.img) {
let img = this.nodeData.data.image
if (!img) {
return
}
let imgSize = this.getImgShowSize()
let node = new Image().load(img).size(...imgSize)
if (this.nodeData.data.imageTitle) {
node.attr('title', this.nodeData.data.imageTitle)
}
return {
node: new Image().load(this.img).size(...imgSize),
node,
width: imgSize[0],
height: imgSize[1]
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:12:51
* @Desc: 获取图片显示宽高
*/
getImgShowSize() {
return resizeImgSize(this.nodeData.data.imageSize.width, this.nodeData.data.imageSize.height, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:10:48
* @Desc: 创建icon节点
*/
createIconNode() {
let _data = this.nodeData.data
if (!_data.icon || _data.icon.length <= 0) {
return [];
}
let node = SVG('<svg t="1617947697619" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="999" width="200" height="200"><path d="M512 899.5c-213.668 0-387.5-173.832-387.5-387.5S298.332 124.5 512 124.5 899.5 298.332 899.5 512 725.668 899.5 512 899.5z" fill="#4472C4" p-id="1000"></path><path d="M512 137c-206.776 0-375 168.224-375 375s168.224 375 375 375 375-168.224 375-375-168.224-375-375-375m0-25c220.914 0 400 179.086 400 400S732.914 912 512 912 112 732.914 112 512s179.086-400 400-400z" fill="#4472C4" p-id="1001"></path><path d="M597.681 335.009c0-7.67-2.36-13.569-7.08-17.109a35.115 35.115 0 0 0-20.061-5.9c-3.54 0-6.49 1.77-10.029 4.13-3.54 2.95-6.49 5.31-8.26 7.08a75.758 75.758 0 0 0-11.211 13.569c-3.54 4.72-7.67 9.44-11.209 13.569-11.209 12.979-23.009 27.139-35.988 41.3-13.569 14.749-26.549 27.729-38.938 39.528-1.18 1.18-2.95 2.36-4.13 3.54l-4.72 2.36c-1.77 1.18-3.54 1.77-4.72 2.95l-5.31 3.54c-2.95 2.36-5.31 4.13-7.08 5.9-2.36 2.36-2.95 4.13-2.95 5.9 0 7.08 2.95 12.389 10.03 16.519 5.9 4.72 12.979 6.49 20.059 6.49a31.985 31.985 0 0 0 14.756-3.543c4.13-2.36 8.26-5.9 12.979-10.619 2.95-3.54 6.49-7.67 11.209-12.979l11.8-12.979c2.95-2.95 7.67-7.67 13.569-14.159s12.389-14.159 20.649-23.009c-1.77 9.44-3.54 20.649-4.72 33.628-2.36 12.979-4.13 25.959-5.9 40.118l-4.72 41.888c-1.18 14.159-2.36 27.729-2.95 39.528-1.18 22.419-2.36 44.838-2.95 67.257q-1.77 33.628-1.77 58.407c0 9.44 2.36 16.519 7.67 21.829 5.31 5.9 12.389 8.26 21.829 8.26a43.479 43.479 0 0 0 15.929-3.54c4.72-2.36 7.67-5.31 7.67-8.85 0-1.77-0.59-5.31-0.59-11.209a149.392 149.392 0 0 1-2.36-18.879 116.91 116.91 0 0 1-2.36-21.239 132.008 132.008 0 0 1-1.18-20.649c0-41.3 1.18-82.6 4.72-124.484 3.54-41.3 10.03-82.6 20.649-123.3a106.366 106.366 0 0 1 2.95-11.209l2.36-11.209 1.77-11.209c-0.002-3.547 0.588-7.086 0.588-11.216z" fill="#FFFFFF" p-id="1002"></path></svg>').size(this.themeConfig.iconSize, this.themeConfig.iconSize)
return [{
node,
width: this.themeConfig.iconSize,
height: this.themeConfig.iconSize
}, {
node: node.clone(),
width: this.themeConfig.iconSize,
height: this.themeConfig.iconSize
}]
}
/**
* javascript comment
* @Author: 王林25
@ -156,10 +231,10 @@ class Node {
* @Desc: 创建文本节点
*/
createTextNode() {
if (!this.text) {
if (!this.nodeData.data.text) {
return
}
let node = this.draw.text(this.text)
let node = this.draw.text(this.nodeData.data.text)
this.style.text(node)
let {
width,
@ -175,21 +250,101 @@ class Node {
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:10:48
* @Desc: 创建icon节点
* @Author: 王林
* @Date: 2021-06-20 15:28:54
* @Desc: 创建超链接节点
*/
createIconNode() {
if (!this.icon) {
createHyperlinkNode() {
if (!this.nodeData.data.hyperlink) {
return
}
let node = SVG('<svg t="1617947697619" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="999" width="200" height="200"><path d="M512 899.5c-213.668 0-387.5-173.832-387.5-387.5S298.332 124.5 512 124.5 899.5 298.332 899.5 512 725.668 899.5 512 899.5z" fill="#4472C4" p-id="1000"></path><path d="M512 137c-206.776 0-375 168.224-375 375s168.224 375 375 375 375-168.224 375-375-168.224-375-375-375m0-25c220.914 0 400 179.086 400 400S732.914 912 512 912 112 732.914 112 512s179.086-400 400-400z" fill="#4472C4" p-id="1001"></path><path d="M597.681 335.009c0-7.67-2.36-13.569-7.08-17.109a35.115 35.115 0 0 0-20.061-5.9c-3.54 0-6.49 1.77-10.029 4.13-3.54 2.95-6.49 5.31-8.26 7.08a75.758 75.758 0 0 0-11.211 13.569c-3.54 4.72-7.67 9.44-11.209 13.569-11.209 12.979-23.009 27.139-35.988 41.3-13.569 14.749-26.549 27.729-38.938 39.528-1.18 1.18-2.95 2.36-4.13 3.54l-4.72 2.36c-1.77 1.18-3.54 1.77-4.72 2.95l-5.31 3.54c-2.95 2.36-5.31 4.13-7.08 5.9-2.36 2.36-2.95 4.13-2.95 5.9 0 7.08 2.95 12.389 10.03 16.519 5.9 4.72 12.979 6.49 20.059 6.49a31.985 31.985 0 0 0 14.756-3.543c4.13-2.36 8.26-5.9 12.979-10.619 2.95-3.54 6.49-7.67 11.209-12.979l11.8-12.979c2.95-2.95 7.67-7.67 13.569-14.159s12.389-14.159 20.649-23.009c-1.77 9.44-3.54 20.649-4.72 33.628-2.36 12.979-4.13 25.959-5.9 40.118l-4.72 41.888c-1.18 14.159-2.36 27.729-2.95 39.528-1.18 22.419-2.36 44.838-2.95 67.257q-1.77 33.628-1.77 58.407c0 9.44 2.36 16.519 7.67 21.829 5.31 5.9 12.389 8.26 21.829 8.26a43.479 43.479 0 0 0 15.929-3.54c4.72-2.36 7.67-5.31 7.67-8.85 0-1.77-0.59-5.31-0.59-11.209a149.392 149.392 0 0 1-2.36-18.879 116.91 116.91 0 0 1-2.36-21.239 132.008 132.008 0 0 1-1.18-20.649c0-41.3 1.18-82.6 4.72-124.484 3.54-41.3 10.03-82.6 20.649-123.3a106.366 106.366 0 0 1 2.95-11.209l2.36-11.209 1.77-11.209c-0.002-3.547 0.588-7.086 0.588-11.216z" fill="#FFFFFF" p-id="1002"></path></svg>').size(this.themeConfig.iconSize, this.themeConfig.iconSize)
let iconSize = this.themeConfig.iconSize
let node = this.draw.element('a')
node.node.addEventListener('click', (e) => {
e.stopPropagation()
})
node.attr('href', this.nodeData.data.hyperlink).attr('target', '_blank')
if (this.nodeData.data.hyperlinkTitle) {
node.attr('title', this.nodeData.data.hyperlinkTitle)
}
node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' }))
node.add(SVG(iconsSvg.hyperlink).size(iconSize, iconSize))
return {
node: this.draw.nested().add(node),
width: iconSize,
height: iconSize
}
}
/**
* @Author: 王林
* @Date: 2021-06-20 19:49:15
* @Desc: 创建标签节点
*/
createTagNode() {
if (!this.nodeData.data.tag || this.nodeData.data.tag.length <= 0) {
return [];
}
let nodes = []
this.nodeData.data.tag.slice(0, 5).forEach((item, index) => {
let tag = this.draw.nested()
let text = this.draw.text(item).x(8).cy(10)
this.style.tagText(text, index)
let {
width,
height
} = text.bbox()
let cloneText = text.clone()
text.remove()
let rect = this.draw.rect(width + 16, 20)
this.style.tagRect(rect, index)
tag.add(rect).add(cloneText)
nodes.push({
node: tag,
width: width + 16,
height: 20
})
})
return nodes;
}
/**
* @Author: 王林
* @Date: 2021-06-20 21:19:36
* @Desc: 创建备注节点
*/
createNoteNode() {
if (!this.nodeData.data.note) {
return null;
}
let node = this.draw.nested().attr('cursor', 'pointer')
let iconSize = this.themeConfig.iconSize
node.add(this.draw.rect(iconSize, iconSize).fill({ color: 'transparent' }))
node.add(SVG(iconsSvg.note).size(iconSize, iconSize))
let el = document.createElement('div')
el.style.cssText = `
position: absolute;
padding: 10px;
border-radius: 5px;
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
display: none;
`
el.innerText = this.nodeData.data.note
document.body.appendChild(el)
node.on('mouseover', () => {
let { left, top } = node.node.getBoundingClientRect()
el.style.left = left + 'px'
el.style.top = top + iconSize + 'px'
el.style.display = 'block'
})
node.on('mouseout', () => {
el.style.display = 'none'
})
return {
node,
width: this.themeConfig.iconSize,
height: this.themeConfig.iconSize
}
width: iconSize,
height: iconSize
};
}
/**
@ -203,46 +358,87 @@ class Node {
left,
top,
width,
height
height,
_textContentHeight,
_textContentItemMargin
} = this
let paddingY = this.themeConfig.paddingY
let { paddingY } = this.getPaddingVale()
// 创建组
let group = this.draw.group()
// 节点矩形
let _rectNode = group.rect(width, height).x(left).y(top)
this.style.rect(_rectNode)
// 内容节点
let imgNode = this.createImgNode()
let iconNode = this.createIconNode()
let textNode = this.createTextNode()
let imgHeight = imgNode ? imgNode.height : 0
// 图片
if (imgNode) {
group.add(imgNode.node)
imgNode.node.cx(left + width / 2).y(top + paddingY)
this.style.rect(group.rect(width, height).x(left).y(top))
// 图片节点
let imgObj = this.createImgNode()
let imgHeight = 0
if (imgObj) {
imgHeight = imgObj.height
group.add(imgObj.node)
imgObj.node.cx(left + width / 2).y(top + paddingY)
}
// 内容节点
let textContentNested = this.draw.nested()
let textContentOffsetX = 0
// icon
if (iconNode) {
group.add(iconNode.node)
iconNode.node.x(left + width / 2).y(top + paddingY + imgHeight + (textNode && textNode.height > iconNode.height ? (textNode.height - iconNode.height) / 2 : 0)).dx(textNode ? -textNode.width / 2 - iconNode.width / 2 : 0)
let iconObjs = this.createIconNode()
let iconNested = this.draw.nested()
if (iconObjs && iconObjs.length > 0) {
let iconLeft = 0
iconObjs.forEach((item) => {
item.node.x(textContentOffsetX + iconLeft).y((_textContentHeight - item.height) / 2)
iconNested.add(item.node)
iconLeft += item.width + _textContentItemMargin
})
textContentNested.add(iconNested)
textContentOffsetX += iconLeft
}
// 文字
if (textNode) {
this.textNode = textNode
group.add(textNode.node)
textNode.node.cx(left + width / 2).y(top + paddingY + imgHeight).dx(iconNode ? iconNode.width / 2 : 0)
let textObj = this.createTextNode()
if (textObj) {
textObj.node.x(textContentOffsetX).y(0)
this.textNode = textObj
textContentNested.add(textObj.node)
textContentOffsetX += textObj.width + _textContentItemMargin
}
// 超链接
let hyperlinkObj = this.createHyperlinkNode()
if (hyperlinkObj) {
hyperlinkObj.node.x(textContentOffsetX).y((_textContentHeight - hyperlinkObj.height) / 2)
textContentNested.add(hyperlinkObj.node)
textContentOffsetX += hyperlinkObj.width + _textContentItemMargin
}
// 标签
let tagObjs = this.createTagNode()
let tagNested = this.draw.nested()
if (tagObjs && tagObjs.length > 0) {
let tagLeft = 0
tagObjs.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
}
// 备注
let noteObj = this.createNoteNode()
if (noteObj) {
noteObj.node.x(textContentOffsetX).y((_textContentHeight - noteObj.height) / 2)
textContentNested.add(noteObj.node)
textContentOffsetX += noteObj.width
}
// 文字内容整体
textContentNested.x(left + width / 2).dx(-textContentNested.bbox().width / 2).y(top + imgHeight + paddingY + (imgHeight > 0 && _textContentHeight > 0 ? this._blockContentMargin : 0))
group.add(textContentNested)
// 单击事件
group.click((e) => {
e.stopPropagation()
if (this.isActive) {
if (this.nodeData.data.isActive) {
return;
}
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActive()
this.isActive = true
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
isActive: this.isActive
isActive: !this.nodeData.data.isActive
})
this.renderer.activeNodeList.push(this)
this.mindMap.render()
@ -263,13 +459,13 @@ class Node {
*/
render() {
// 连线
this.drawLine()
this.renderLine()
// 按钮
this.drawBtn()
this.renderExpandBtn()
// 节点
this.draw.add(this.createNode())
// 子节点
if (this.children && this.children.length && this.expand) {
if (this.children && this.children.length && this.nodeData.data.expand !== false) {
this.children.forEach((child) => {
child.render()
})
@ -281,11 +477,11 @@ class Node {
* @Date: 2021-04-10 22:01:53
* @Desc: 连线
*/
drawLine() {
if (!this.expand) {
renderLine() {
if (this.nodeData.data.expand === false) {
return;
}
let lines = this.renderer.layout.drawLine(this)
let lines = this.renderer.layout.renderLine(this)
lines.forEach((line) => {
this.style.line(line)
})
@ -296,20 +492,20 @@ class Node {
* @Date: 2021-04-11 19:47:01
* @Desc: 展开收缩按钮
*/
drawBtn() {
renderExpandBtn() {
if (this.children.length <= 0 || this.isRoot) {
return;
}
let g = this.draw.group()
let iconSvg
if (this.expand) {
iconSvg = btnsSvg.close
} else {
if (this.nodeData.data.expand === false) {
iconSvg = btnsSvg.open
} else {
iconSvg = btnsSvg.close
}
let node = SVG(iconSvg).size(20, 20)
let fillNode = new Circle().size(20)
this.renderer.layout.drawIcon(this, [node, fillNode])
this.renderer.layout.renderExpandBtn(this, [node, fillNode])
node.dx(0).dy(-10)
fillNode.dx(0).dy(-10)
this.style.iconBtn(node, fillNode)
@ -324,10 +520,9 @@ class Node {
})
})
g.click(() => {
this.expand = !this.expand
// 需要反映到实际数据上
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
expand: this.expand
expand: !this.nodeData.data.expand
})
this.mindMap.render()
this.mindMap.emit('expand_btn_click', this)
@ -337,13 +532,15 @@ class Node {
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:12:51
* @Desc: 获取图片显示宽高
* @Author: 王林
* @Date: 2021-06-20 22:51:57
* @Desc: 获取padding值
*/
getImgShowSize() {
return resizeImgSize(this.imgWidth, this.imgHeight, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight)
getPaddingVale() {
return {
paddingX: this.getStyle('paddingX', true, this.nodeData.data.isActive),
paddingY: this.getStyle('paddingY', true, this.nodeData.data.isActive)
};
}
/**
@ -365,7 +562,7 @@ class Node {
if (isActive) {
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
activeStyle: {
...(this.data.activeStyle || {}),
...(this.nodeData.data.activeStyle || {}),
[prop]: value
}
})

View File

@ -35,11 +35,30 @@ class Render {
// 文本编辑框
this.textEdit = new TextEdit(this)
// 布局
this.layout = new(layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)(this)
this.layout = new (layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)(this)
// 绑定事件
this.bindEvent()
// 注册命令
this.registerCommands()
}
/**
* @Author: 王林
* @Date: 2021-06-20 10:34:06
* @Desc: 绑定事件
*/
bindEvent() {
// 点击事件
this.mindMap.on('draw_click', () => {
// 清除激活状态
if (this.activeNodeList.length > 0) {
this.clearActive()
this.mindMap.render()
this.mindMap.emit('node_active', null, [])
}
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:06
@ -167,7 +186,7 @@ class Render {
*/
updateNodeData(node, data) {
Object.keys(data).forEach((key) => {
node.data[key] = data[key]
node.nodeData.data[key] = data[key]
})
}
}

View File

@ -1,3 +1,26 @@
const tagColorList = [
{
color: 'rgb(77, 65, 0)',
background: 'rgb(255, 244, 179)'
},
{
color: 'rgb(0, 50, 77)',
background: 'rgb(179, 229, 255)'
},
{
color: 'rgb(77, 0, 73)',
background: 'rgb(255, 179, 251)'
},
{
color: 'rgb(57, 77, 0)',
background: 'rgb(236, 255, 179)'
},
{
color: 'rgb(0, 77, 47)',
background: 'rgb(179, 255, 226)'
}
]
const rootProp = ['paddingX', 'paddingY']
/**
* @Author: 王林
@ -37,23 +60,23 @@ class Style {
merge(prop, root, isActive) {
// 三级及以下节点
let defaultConfig = this.themeConfig.node
if (root) {// 直接使用最外层样式
if (root || rootProp.includes(prop)) {// 直接使用最外层样式
defaultConfig = this.themeConfig
} else if (this.ctx.layerIndex === 0) {// 根节点
defaultConfig = this.themeConfig.root
} else if (this.ctx.layerIndex === 1) {// 二级节点
defaultConfig = this.themeConfig.secondLevel
defaultConfig = this.themeConfig.second
}
// 激活状态
if (isActive !== undefined ? isActive : this.ctx.isActive) {
if (this.ctx.activeStyle && this.ctx.activeStyle[prop] !== undefined) {
return this.ctx.activeStyle[prop];
if (isActive !== undefined ? isActive : this.ctx.nodeData.data.isActive) {
if (this.ctx.nodeData.data.activeStyle && this.ctx.nodeData.data.activeStyle[prop] !== undefined) {
return this.ctx.nodeData.data.activeStyle[prop];
} else if (defaultConfig.active && defaultConfig.active[prop]) {
return defaultConfig.active[prop]
}
}
// 优先使用节点本身的样式
return this.ctx[prop] !== undefined ? this.ctx[prop] : defaultConfig[prop]
return this.ctx.nodeData.data[prop] !== undefined ? this.ctx.nodeData.data[prop] : defaultConfig[prop]
}
/**
@ -99,6 +122,30 @@ class Style {
node.style.fontWeight = this.merge('fontWeight') || 'normal'
}
/**
* @Author: 王林
* @Date: 2021-06-20 20:02:18
* @Desc: 标签文字
*/
tagText(node, index) {
node.fill({
color: tagColorList[index].color
}).css({
'font-size': '12px'
})
}
/**
* @Author: 王林
* @Date: 2021-06-20 21:04:11
* @Desc: 标签矩形
*/
tagRect(node, index) {
node.fill({
color: tagColorList[index].background
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:50:49

View File

@ -16,6 +16,7 @@ export default class TextEdit {
* @Desc: 构造函数
*/
constructor(renderer) {
this.renderer = renderer
this.mindMap = renderer.mindMap
// 文本编辑框
this.textEditNode = null
@ -37,12 +38,6 @@ export default class TextEdit {
this.mindMap.on('draw_click', () => {
// 隐藏文本编辑框
this.hideEditTextBox()
// 清除激活状态
if (this.activeNodeList.length > 0) {
this.clearActive()
this.mindMap.render()
this.mindMap.emit('node_active', null, [])
}
})
// 展开收缩按钮点击事件
this.mindMap.on('expand_btn_click', () => {
@ -64,10 +59,10 @@ export default class TextEdit {
* @Desc: 显示文本编辑框
*/
show(node) {
if (!node.text) {
if (!node.nodeData.data.text) {
return;
}
this.showEditTextBox(this, this.textNode.node.node.getBoundingClientRect())
this.showEditTextBox(node, node.textNode.node.node.getBoundingClientRect())
}
/**
@ -83,7 +78,7 @@ export default class TextEdit {
document.body.appendChild(this.textEditNode)
}
node.style.domText(this.textEditNode)
this.textEditNode.innerHTML = node.data.text.split(/\n/img).join('<br>')
this.textEditNode.innerHTML = node.nodeData.data.text.split(/\n/img).join('<br>')
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
this.textEditNode.style.left = rect.left + 'px'
@ -101,12 +96,12 @@ export default class TextEdit {
if (!this.showTextEdit) {
return
}
this.activeNodeList.forEach((node) => {
this.renderer.activeNodeList.forEach((node) => {
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
node.data.text = str
node.nodeData.data.text = str
this.mindMap.render()
})
this.mindMap.emit('hide_text_edit', this.textEditNode, this.activeNodeList)
this.mindMap.emit('hide_text_edit', this.textEditNode, this.renderer.activeNodeList)
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
this.textEditNode.style.fontFamily = 'inherit'

View File

@ -38,17 +38,17 @@ class Base {
* @Date: 2021-04-12 22:41:04
* @Desc: 连线
*/
drawLine() {
throw new Error('【drawLine】方法为必要方法需要子类进行重写')
renderLine() {
throw new Error('【renderLine】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:42:08
* @Desc: 定位显示展开收缩按钮
* @Desc: 定位展开收缩按钮
*/
drawIcon() {
throw new Error('【drawIcon】方法为必要方法需要子类进行重写')
renderExpandBtn() {
throw new Error('【renderExpandBtn】方法为必要方法需要子类进行重写')
}
/**

View File

@ -26,7 +26,7 @@ class LogicalStructure extends Base {
* @Desc: 布局
*/
doLayout() {
// 计算节点的left、width、height
// 遍历数据计算节点的left、width、height
this.computedBaseValue()
// 计算节点的top
this.computedTopValue()
@ -40,39 +40,41 @@ class LogicalStructure extends Base {
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 计算节点的leftwidthheight
* @Desc: 遍历数据计算节点的leftwidthheight
*/
computedBaseValue() {
walk(this.renderTree, null, (node, parent, isRoot, layerIndex) => {
// 遍历子节点前设置left、width、height
walk(this.renderTree, null, (cur, parent, isRoot, layerIndex) => {
// 创建节点
let newNode = new Node({
data: cur,
uid: this.mindMap.uid++,
data: node,
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
layerIndex
})
// 计算节点的宽高
newNode.refreshSize()
// 数据关联实际节点
cur._node = newNode
// 根节点定位在画布中心位置
if (isRoot) {
newNode.isRoot = true
newNode.left = (this.mindMap.width - newNode.width) / 2
newNode.top = (this.mindMap.height - newNode.height) / 2
this.root = newNode
} else {
let marginX = layerIndex === 1 ? this.themeConfig.secondLevel.marginX : this.themeConfig.node.marginX
newNode.left = parent._node.left + parent._node.width + marginX,
newNode.parent = parent._node
// 非根节点
let marginX = layerIndex === 1 ? this.themeConfig.second.marginX : this.themeConfig.node.marginX
// 定位到父节点右侧
newNode.left = parent._node.left + parent._node.width + marginX
// 互相收集
newNode.parent = parent._node
parent._node.addChildren(newNode)
}
node._node = newNode
}, (node, parent, isRoot, layerIndex) => {
}, (cur, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaHeight也就是子节点所占的高度之和包括外边距
let len = node.expand === false ? 0 : node._node.children.length
node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => {
return h + cur.height
let len = cur.data.expand === false ? 0 : cur._node.children.length
cur._node.childrenAreaHeight = len ? cur._node.children.reduce((h, item) => {
return h + item.height
}, 0) + (len + 1) * this.getMarginY(layerIndex) : 0
}, true, 0)
}
@ -81,7 +83,7 @@ class LogicalStructure extends Base {
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 计算节点的top
* @Desc: 遍历节点树计算节点的top
*/
computedTopValue() {
walk(this.root, null, (node, parent, isRoot, layerIndex) => {
@ -151,7 +153,7 @@ class LogicalStructure extends Base {
* @Desc: 获取节点的marginY
*/
getMarginY(layerIndex) {
return layerIndex === 1 ? this.themeConfig.secondLevel.marginY : this.themeConfig.node.marginY;
return layerIndex === 1 ? this.themeConfig.second.marginY : this.themeConfig.node.marginY;
}
/**
@ -183,7 +185,7 @@ class LogicalStructure extends Base {
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线连接该节点到其子节点
*/
drawLine(node) {
renderLine(node) {
if (node.children.length <= 0) {
return [];
}
@ -220,7 +222,7 @@ class LogicalStructure extends Base {
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
drawIcon(node, icons) {
renderExpandBtn(node, icons) {
let {
left,
top,

View File

@ -0,0 +1,10 @@
// 超链接图标
const hyperlink = '<svg t="1624174958075" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7982" width="200" height="200"><path d="M435.484444 251.733333v68.892445L295.822222 320.682667a168.504889 168.504889 0 0 0-2.844444 336.952889h142.506666v68.892444H295.822222a237.397333 237.397333 0 0 1 0-474.794667h139.662222z m248.945778 0a237.397333 237.397333 0 0 1 0 474.851556H544.654222v-69.006222l139.776 0.056889a168.504889 168.504889 0 0 0 2.844445-336.952889H544.597333V251.676444h139.776z m-25.827555 203.946667a34.474667 34.474667 0 0 1 0 68.892444H321.649778a34.474667 34.474667 0 0 1 0-68.892444h336.952889z" p-id="7983"></path></svg>'
// 备注图标
const note = '<svg t="1624195132675" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8792" width="200" height="200"><path d="M152.768 985.984 152.768 49.856l434.56 0 66.816 0 234.048 267.392 0 66.816 0 601.92L152.768 985.984 152.768 985.984zM654.144 193.088l0 124.16 108.736 0L654.144 193.088 654.144 193.088zM821.312 384.064l-167.168 0L587.328 384.064 587.328 317.312 587.328 116.736 219.584 116.736 219.584 919.04l601.728 0L821.312 384.064 821.312 384.064zM386.688 517.888 319.808 517.888 319.808 450.944l66.816 0L386.624 517.888 386.688 517.888zM386.688 651.584 319.808 651.584 319.808 584.704l66.816 0L386.624 651.584 386.688 651.584zM386.688 785.344 319.808 785.344l0-66.88 66.816 0L386.624 785.344 386.688 785.344zM721.024 517.888 453.632 517.888 453.632 450.944l267.392 0L721.024 517.888 721.024 517.888zM654.144 651.584 453.632 651.584 453.632 584.704l200.512 0L654.144 651.584 654.144 651.584zM620.672 785.344l-167.04 0 0-66.88 167.04 0L620.672 785.344 620.672 785.344z" p-id="8793"></path></svg>'
export default {
hyperlink,
note
}

View File

@ -19,7 +19,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(238, 243, 246)',
color: '#333',
borderColor: 'rgb(115, 161, 191)',

View File

@ -19,7 +19,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(246, 238, 242)',
color: '#333',
borderColor: 'rgb(191, 115, 148)',

View File

@ -29,7 +29,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(164, 197, 192)',
borderColor: 'transparent',
color: '#333',

View File

@ -24,7 +24,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(241, 242, 241)',
borderColor: 'transparent',
color: '#1a1a1a',

View File

@ -26,7 +26,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(255, 245, 214)',
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1,

View File

@ -24,7 +24,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(55, 56, 58)',
color: 'rgb(147,148,149)',
fontSize: 18,

View File

@ -8,9 +8,9 @@ export default {
paddingX: 20,
paddingY: 10,
// 图片显示的最大宽度
imgMaxWidth: 200,
imgMaxWidth: 100,
// 图片显示的最大高度
imgMaxHeight: 200,
imgMaxHeight: 100,
// icon的大小
iconSize: 20,
// 连线的粗细
@ -43,7 +43,7 @@ export default {
}
},
// 二级节点样式
secondLevel: {
second: {
marginX: 100,
marginY: 40,
fillColor: '#fff',

View File

@ -19,7 +19,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(246, 242, 238)',
color: '#333',
borderColor: 'rgb(191, 147, 115)',

View File

@ -16,7 +16,7 @@ export default merge(defaultTheme, {
fillColor: '#1fb27d'
},
// 二级节点样式
secondLevel: {
second: {
fillColor: '#fff',
color: '#565656',
borderColor: 'transparent',

View File

@ -19,7 +19,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(246, 238, 238)',
color: '#333',
borderColor: 'rgb(191, 115, 115)',

View File

@ -19,7 +19,7 @@ export default merge(defaultTheme, {
}
},
// 二级节点样式
secondLevel: {
second: {
fillColor: 'rgb(239, 238, 246)',
color: '#333',
borderColor: 'rgb(123, 115, 191)',

View File

@ -25,7 +25,6 @@ export default {
this.$bus.$on("node_active", (...args) => {
let activeNodes = args[1];
this.activeNode = activeNodes[0];
console.log(args);
});
this.$bus.$on("showNodeImage", () => {
this.dialogVisible = true;

View File

@ -6,6 +6,7 @@
<el-tab-pane label="选中状态" name="active"></el-tab-pane>
</el-tabs>
<div class="sidebarContent" v-if="activeNode">
<!-- 文字 -->
<div class="title noTop">文字</div>
<div class="row">
<el-select
@ -94,6 +95,7 @@
</el-radio-group>
</el-popover>
</div>
<!-- 边框 -->
<div class="title">边框</div>
<div class="row">
<div class="rowItem">
@ -167,6 +169,7 @@
</el-select>
</div>
</div>
<!-- 背景 -->
<div class="title">背景</div>
<div class="row">
<div class="rowItem">
@ -181,6 +184,28 @@
</el-popover>
</div>
</div>
<!-- 节点内边距 -->
<div class="title noTop">节点内边距</div>
<div class="row">
<div class="rowItem">
<span class="name">水平</span>
<el-slider
style="width: 230px"
v-model="style.paddingX"
@change="update('paddingX')"
></el-slider>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">垂直</span>
<el-slider
style="width: 230px"
v-model="style.paddingY"
@change="update('paddingY')"
></el-slider>
</div>
</div>
</div>
</div>
</Sidebar>
@ -213,6 +238,8 @@ export default {
activeNode: null,
activeTab: "normal",
style: {
paddingX: 0,
paddingY: 0,
color: "",
fontFamily: "",
fontSize: "",
@ -229,7 +256,7 @@ export default {
},
created() {
this.$bus.$on("node_active", (...args) => {
this.activeTab = 'normal'
this.activeTab = "normal";
let activeNodes = args[1];
this.activeNode = activeNodes[0];
this.$refs.sidebar.show = activeNodes.length > 0;
@ -237,14 +264,14 @@ export default {
});
},
methods: {
/**
* @Author: 王林
* @Date: 2021-05-05 11:42:32
* @Desc: tab切换
*/
handleTabClick() {
this.initNodeStyle()
},
/**
* @Author: 王林
* @Date: 2021-05-05 11:42:32
* @Desc: tab切换
*/
handleTabClick() {
this.initNodeStyle();
},
/**
* @Author: 王林
@ -253,10 +280,12 @@ export default {
*/
initNodeStyle() {
if (!this.activeNode) {
this.activeTab = 'normal'
this.activeTab = "normal";
return;
}
[
"paddingX",
"paddingY",
"color",
"fontFamily",
"fontSize",
@ -269,7 +298,11 @@ export default {
"borderDasharray",
"borderRadius",
].forEach((item) => {
this.style[item] = this.activeNode.getStyle(item, false, this.activeTab === 'active');
this.style[item] = this.activeNode.getStyle(
item,
false,
this.activeTab === "active"
);
});
},
@ -279,7 +312,11 @@ export default {
* @Desc: 修改样式
*/
update(prop) {
this.activeNode.setStyle(prop, this.style[prop], this.activeTab === 'active');
this.activeNode.setStyle(
prop,
this.style[prop],
this.activeTab === "active"
);
},
/**
@ -345,21 +382,21 @@ export default {
<style lang="less" scoped>
.styleBox {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.tab {
flex-grow: 0;
flex-shrink: 0;
padding: 0 20px;
}
.tab {
flex-grow: 0;
flex-shrink: 0;
padding: 0 20px;
}
}
.sidebarContent {
padding: 20px;
padding-top: 10px;
padding: 20px;
padding-top: 10px;
.title {
font-size: 16px;
@ -369,9 +406,9 @@ export default {
margin-bottom: 10px;
margin-top: 20px;
&.noTop {
margin-top: 0;
}
&.noTop {
margin-top: 0;
}
}
.row {