diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js
index eea5764f..8332dba3 100644
--- a/simple-mind-map/src/constants/constant.js
+++ b/simple-mind-map/src/constants/constant.js
@@ -15,6 +15,7 @@ export const CONSTANTS = {
TIMELINE: 'timeline',
TIMELINE2: 'timeline2',
FISHBONE: 'fishbone',
+ FISHBONE2: 'fishbone2',
RIGHT_FISHBONE: 'rightFishbone',
VERTICAL_TIMELINE: 'verticalTimeline'
},
@@ -129,6 +130,10 @@ export const layoutList = [
name: '鱼骨图',
value: CONSTANTS.LAYOUT.FISHBONE
},
+ {
+ name: '鱼骨图2',
+ value: CONSTANTS.LAYOUT.FISHBONE2
+ },
{
name: '向右鱼骨图',
value: CONSTANTS.LAYOUT.RIGHT_FISHBONE
@@ -144,6 +149,7 @@ export const layoutValueList = [
CONSTANTS.LAYOUT.TIMELINE2,
CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
CONSTANTS.LAYOUT.FISHBONE,
+ CONSTANTS.LAYOUT.FISHBONE2,
CONSTANTS.LAYOUT.RIGHT_FISHBONE
]
diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js
index ea63291c..d87628f3 100644
--- a/simple-mind-map/src/core/render/Render.js
+++ b/simple-mind-map/src/core/render/Render.js
@@ -61,7 +61,9 @@ const layouts = {
// 竖向时间轴
[CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline,
// 鱼骨图
- [CONSTANTS.LAYOUT.FISHBONE]: Fishbone
+ [CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
+ // 鱼骨图2
+ [CONSTANTS.LAYOUT.FISHBONE2]: Fishbone
}
// 渲染
@@ -116,6 +118,9 @@ class Render {
// 设置布局结构
setLayout() {
+ if (this.layout && this.layout.beforeChange) {
+ this.layout.beforeChange()
+ }
const { layout } = this.mindMap.opt
let L = layouts[layout] || this.mindMap[layout]
if (!L) {
diff --git a/simple-mind-map/src/core/render/node/MindMapNode.js b/simple-mind-map/src/core/render/node/MindMapNode.js
index c0a0af12..9c085fc1 100644
--- a/simple-mind-map/src/core/render/node/MindMapNode.js
+++ b/simple-mind-map/src/core/render/node/MindMapNode.js
@@ -810,13 +810,10 @@ class MindMapNode {
}
let childrenLen = this.getChildrenLength()
// 切换为鱼骨结构时,清空根节点和二级节点的连线
- if (
- [CONSTANTS.LAYOUT.FISHBONE, CONSTANTS.LAYOUT.RIGHT_FISHBONE].includes(
- this.mindMap.opt.layout
- ) &&
- (this.isRoot || this.layerIndex === 1)
- ) {
- childrenLen = 0
+ if (this.mindMap.renderer.layout.nodeIsRemoveAllLines) {
+ if (this.mindMap.renderer.layout.nodeIsRemoveAllLines(this)) {
+ childrenLen = 0
+ }
}
if (childrenLen > this._lines.length) {
// 创建缺少的线
diff --git a/simple-mind-map/src/core/render/node/Style.js b/simple-mind-map/src/core/render/node/Style.js
index d3fa39ad..69752f8f 100644
--- a/simple-mind-map/src/core/render/node/Style.js
+++ b/simple-mind-map/src/core/render/node/Style.js
@@ -8,6 +8,18 @@ const backgroundStyleProps = [
'backgroundSize'
]
+export const shapeStyleProps = [
+ 'gradientStyle',
+ 'startColor',
+ 'endColor',
+ 'startDir',
+ 'endDir',
+ 'fillColor',
+ 'borderColor',
+ 'borderWidth',
+ 'borderDasharray'
+]
+
// 样式类
class Style {
// 设置背景样式
@@ -128,17 +140,10 @@ class Style {
// 形状
shape(node) {
- const styles = {
- gradientStyle: this.merge('gradientStyle'),
- startColor: this.merge('startColor'),
- endColor: this.merge('endColor'),
- startDir: this.merge('startDir'),
- endDir: this.merge('endDir'),
- fillColor: this.merge('fillColor'),
- borderColor: this.merge('borderColor'),
- borderWidth: this.merge('borderWidth'),
- borderDasharray: this.merge('borderDasharray')
- }
+ const styles = {}
+ shapeStyleProps.forEach(key => {
+ styles[key] = this.merge(key)
+ })
if (styles.gradientStyle) {
if (!this._gradient) {
this._gradient = this.ctx.nodeDraw.gradient('linear')
diff --git a/simple-mind-map/src/layouts/Fishbone.js b/simple-mind-map/src/layouts/Fishbone.js
index 1b986db0..8240e684 100644
--- a/simple-mind-map/src/layouts/Fishbone.js
+++ b/simple-mind-map/src/layouts/Fishbone.js
@@ -2,14 +2,75 @@ import Base from './Base'
import { walk, asyncRun, degToRad, getNodeIndexInNodeList } from '../utils'
import { CONSTANTS } from '../constants/constant'
import utils from './fishboneUtils'
+import { SVG } from '@svgdotjs/svg.js'
+import { shapeStyleProps } from '../core/render/node/Style'
// 鱼骨图
class Fishbone extends Base {
// 构造函数
- constructor(opt = {}) {
+ constructor(opt = {}, layout) {
super(opt)
+ this.layout = layout
this.indent = 0.3
this.childIndent = 0.5
+ this.fishTail = null
+ this.maxx = 0
+ this.headRatio = 1
+ this.tailRatio = 0.6
+ this.bindEvent()
+ this.extendShape()
+ this.beforeChange = this.beforeChange.bind(this)
+ }
+
+ // 重新渲染时,节点连线是否全部删除
+ // 鱼尾鱼骨图会多渲染一些连线,按需删除无法删除掉,只能全部删除重新创建
+ nodeIsRemoveAllLines(node) {
+ return node.isRoot || node.layerIndex === 1
+ }
+
+ // 是否是带鱼头鱼尾的鱼骨图
+ isFishbone2() {
+ return this.layout === CONSTANTS.LAYOUT.FISHBONE2
+ }
+
+ bindEvent() {
+ if (!this.isFishbone2()) return
+ this.onCheckUpdateFishTail = this.onCheckUpdateFishTail.bind(this)
+ this.mindMap.on('afterExecCommand', this.onCheckUpdateFishTail)
+ }
+
+ unBindEvent() {
+ this.mindMap.off('afterExecCommand', this.onCheckUpdateFishTail)
+ }
+
+ // 扩展节点形状
+ extendShape() {
+ if (!this.isFishbone2()) return
+ // 扩展鱼头形状
+ this.mindMap.addShape({
+ name: 'fishHead',
+ createShape: node => {
+ const rect = SVG(
+ ``
+ )
+ const { width, height } = node.shapeInstance.getNodeSize()
+ rect.size(width, height)
+ return rect
+ },
+ getPadding: ({ width, height, paddingX, paddingY }) => {
+ width += paddingX * 2
+ height += paddingY * 2
+ let shapePaddingX = 0.25 * width
+ let shapePaddingY = 0
+ width += shapePaddingX * 2
+ const newHeight = width / this.headRatio
+ shapePaddingY = (newHeight - height) / 2
+ return {
+ paddingX: shapePaddingX,
+ paddingY: shapePaddingY
+ }
+ }
+ })
}
// 布局
@@ -17,12 +78,14 @@ class Fishbone extends Base {
let task = [
() => {
this.computedBaseValue()
+ this.addFishTail()
},
() => {
this.computedLeftTopValue()
},
() => {
this.adjustLeftTopValue()
+ this.updateFishTailPosition()
},
() => {
callback(this.root)
@@ -31,14 +94,77 @@ class Fishbone extends Base {
asyncRun(task)
}
+ // 创建鱼尾
+ addFishTail() {
+ if (!this.isFishbone2()) return
+ const exist = this.mindMap.lineDraw.findOne('.smm-layout-fishbone-tail')
+ if (!exist) {
+ this.fishTail = SVG(
+ ''
+ )
+ this.fishTail.addClass('smm-layout-fishbone-tail')
+ } else {
+ this.fishTail = exist
+ }
+ const tailHeight = this.root.height
+ const tailWidth = tailHeight * this.tailRatio
+ this.fishTail.size(tailWidth, tailHeight)
+ this.styleFishTail()
+ this.mindMap.lineDraw.add(this.fishTail)
+ }
+
+ // 如果根节点更新了形状样式,那么鱼尾也要更新
+ onCheckUpdateFishTail(name, node, data) {
+ if (name === 'SET_NODE_DATA') {
+ let hasShapeProp = false
+ Object.keys(data).forEach(key => {
+ if (shapeStyleProps.includes(key)) {
+ hasShapeProp = true
+ }
+ })
+ if (hasShapeProp) {
+ this.styleFishTail()
+ }
+ }
+ }
+
+ styleFishTail() {
+ this.root.style.shape(this.fishTail)
+ }
+
+ // 删除鱼尾
+ removeFishTail() {
+ const exist = this.mindMap.lineDraw.findOne('.smm-layout-fishbone-tail')
+ if (exist) {
+ exist.remove()
+ }
+ }
+
+ // 更新鱼尾形状位置
+ updateFishTailPosition() {
+ if (!this.isFishbone2()) return
+ this.fishTail.x(this.maxx).cy(this.root.top + this.root.height / 2)
+ }
+
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
computedBaseValue() {
walk(
this.renderer.renderTree,
null,
(node, parent, isRoot, layerIndex, index, ancestors) => {
+ if (isRoot && this.isFishbone2()) {
+ // 将根节点形状强制修改为鱼头
+ node.data.shape = 'fishHead'
+ }
// 创建节点
- let newNode = this.createNode(node, parent, isRoot, layerIndex, index, ancestors)
+ let newNode = this.createNode(
+ node,
+ parent,
+ isRoot,
+ layerIndex,
+ index,
+ ancestors
+ )
// 根节点定位在画布中心位置
if (isRoot) {
this.setNodeCenter(newNode)
@@ -133,19 +259,27 @@ class Fishbone extends Base {
if (node.isRoot) {
let topTotalLeft = 0
let bottomTotalLeft = 0
+ let maxx = -Infinity
node.children.forEach(item => {
if (this.checkIsTop(item)) {
item.left += topTotalLeft
this.updateChildren(item.children, 'left', topTotalLeft)
let { left, right } = this.getNodeBoundaries(item, 'h')
+ if (right > maxx) {
+ maxx = right
+ }
topTotalLeft += right - left
} else {
item.left += bottomTotalLeft
this.updateChildren(item.children, 'left', bottomTotalLeft)
let { left, right } = this.getNodeBoundaries(item, 'h')
+ if (right > maxx) {
+ maxx = right
+ }
bottomTotalLeft += right - left
}
})
+ this.maxx = maxx
}
},
true
@@ -277,11 +411,14 @@ class Fishbone extends Base {
let nodeHalfTop = node.top + node.height / 2
let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1)
let line = this.lineDraw.path()
+ const lineEndX = this.isFishbone2()
+ ? this.maxx
+ : maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
line.plot(
this.transformPath(
- `M ${node.left + node.width},${nodeHalfTop} L ${
- maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg))
- },${nodeHalfTop}`
+ `M ${
+ node.left + node.width
+ },${nodeHalfTop} L ${lineEndX},${nodeHalfTop}`
)
)
node.style.line(line)
@@ -406,6 +543,16 @@ class Fishbone extends Base {
rect.size(width, expandBtnSize).x(0).y(height)
}
}
+
+ // 切换切换为其他结构时的处理
+ beforeChange() {
+ // 删除鱼尾
+ if (!this.isFishbone2()) return
+ this.root.nodeData.data.shape = CONSTANTS.SHAPE.RECTANGLE
+ this.removeFishTail()
+ this.unBindEvent()
+ this.mindMap.removeShape('fishHead')
+ }
}
export default Fishbone
diff --git a/simple-mind-map/src/plugins/Drag.js b/simple-mind-map/src/plugins/Drag.js
index d1652f09..371c7d49 100644
--- a/simple-mind-map/src/plugins/Drag.js
+++ b/simple-mind-map/src/plugins/Drag.js
@@ -408,6 +408,7 @@ class Drag extends Base {
TIMELINE2,
VERTICAL_TIMELINE,
FISHBONE,
+ FISHBONE2,
RIGHT_FISHBONE
} = CONSTANTS.LAYOUT
this.overlapNode = null
@@ -447,6 +448,7 @@ class Drag extends Base {
this.handleLogicalStructure(node)
break
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
this.handleFishbone(node)
break
@@ -472,6 +474,7 @@ class Drag extends Base {
TIMELINE2,
VERTICAL_TIMELINE,
FISHBONE,
+ FISHBONE2,
RIGHT_FISHBONE
} = CONSTANTS.LAYOUT
const { LEFT, TOP, RIGHT, BOTTOM } = CONSTANTS.LAYOUT_GROW_DIR
@@ -583,6 +586,7 @@ class Drag extends Base {
}
break
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
if (layerIndex <= 1) {
notRenderPlaceholder = true
@@ -672,6 +676,7 @@ class Drag extends Base {
halfPlaceholderHeight
break
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
if (layerIndex <= 1) {
notRenderPlaceholder = true
@@ -709,6 +714,7 @@ class Drag extends Base {
TIMELINE2,
VERTICAL_TIMELINE,
FISHBONE,
+ FISHBONE2,
RIGHT_FISHBONE
} = CONSTANTS.LAYOUT
switch (this.mindMap.opt.layout) {
@@ -720,6 +726,7 @@ class Drag extends Base {
case TIMELINE2:
case VERTICAL_TIMELINE:
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
return node.dir
default:
@@ -732,7 +739,7 @@ class Drag extends Base {
handleVerticalCheck(node, checkList, isReverse = false) {
const { layout } = this.mindMap.opt
const { LAYOUT, LAYOUT_GROW_DIR } = CONSTANTS
- const { VERTICAL_TIMELINE, FISHBONE, RIGHT_FISHBONE } = LAYOUT
+ const { VERTICAL_TIMELINE, FISHBONE, FISHBONE2, RIGHT_FISHBONE } = LAYOUT
const { LEFT } = LAYOUT_GROW_DIR
const mouseMoveX = this.mouseMoveX
const mouseMoveY = this.mouseMoveY
@@ -799,6 +806,7 @@ class Drag extends Base {
this.placeholderHeight / 2
switch (layout) {
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
if (layerIndex === 2) {
notRenderLine = true
@@ -829,6 +837,7 @@ class Drag extends Base {
this.placeholderHeight / 2
switch (layout) {
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
if (layerIndex === 2) {
notRenderLine = true
@@ -866,7 +875,7 @@ class Drag extends Base {
handleHorizontalCheck(node, checkList) {
const { layout } = this.mindMap.opt
const { LAYOUT } = CONSTANTS
- const { FISHBONE, RIGHT_FISHBONE, TIMELINE, TIMELINE2 } = LAYOUT
+ const { FISHBONE, FISHBONE2, RIGHT_FISHBONE, TIMELINE, TIMELINE2 } = LAYOUT
let mouseMoveX = this.mouseMoveX
let mouseMoveY = this.mouseMoveY
let nodeRect = this.getNodeRect(node)
@@ -906,6 +915,7 @@ class Drag extends Base {
this.placeholderWidth / 2
break
case FISHBONE:
+ case FISHBONE2:
case RIGHT_FISHBONE:
if (layerIndex === 1) {
notRenderLine = true