diff --git a/simple-mind-map/src/layouts/Fishbone copy 2.js b/simple-mind-map/src/layouts/Fishbone copy 2.js new file mode 100644 index 00000000..36d1afdf --- /dev/null +++ b/simple-mind-map/src/layouts/Fishbone copy 2.js @@ -0,0 +1,368 @@ +import Base from './Base' +import { walk, asyncRun } from '../utils' +import { CONSTANTS } from '../utils/constant' + +const degToRad = deg => { + return (Math.PI / 180) * deg +} + +// 鱼骨图 +class Fishbone extends Base { + // 构造函数 + constructor(opt = {}) { + super(opt) + } + + // 布局 + doLayout(callback) { + let task = [ + () => { + this.computedBaseValue() + }, + () => { + this.computedLeftTopValue() + }, + () => { + this.adjustLeftTopValue() + }, + () => { + callback(this.root) + } + ] + asyncRun(task) + } + + // 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值 + computedBaseValue() { + walk( + this.renderer.renderTree, + null, + (node, parent, isRoot, layerIndex, index) => { + // 创建节点 + let newNode = this.createNode(node, parent, isRoot, layerIndex) + // 根节点定位在画布中心位置 + if (isRoot) { + this.setNodeCenter(newNode) + } else { + // 非根节点 + // 三级及以下节点以上级方向为准 + if (parent._node.dir) { + newNode.dir = parent._node.dir + } else { + // 节点生长方向 + newNode.dir = + index % 2 === 0 + ? CONSTANTS.TIMELINE_DIR.TOP + : CONSTANTS.TIMELINE_DIR.BOTTOM + } + // 计算二级节点的top值 + if (parent._node.isRoot) { + newNode.top = parent._node.top - newNode.height + } + } + if (!node.data.expand) { + return true + } + }, + null, + true, + 0 + ) + } + + // 遍历节点树计算节点的left、top + computedLeftTopValue() { + walk( + this.root, + null, + (node, parent, isRoot, layerIndex, index) => { + if (node.isRoot) { + let totalLeft = node.left + node.width + node.children.forEach(item => { + item.left = totalLeft + totalLeft += item.width + }) + } + if (layerIndex >= 1 && node.children) { + // 遍历三级及以下节点的子节点 + let startLeft = node.left + node.width * 0.5 + let totalTop = + node.top + + node.height + + (this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) + node.children.forEach(item => { + item.left = startLeft + item.top += totalTop + totalTop += + item.height + + (this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + }) + } + }, + null, + true + ) + } + + // 调整节点left、top + adjustLeftTopValue() { + walk( + this.root, + null, + (node, parent, isRoot, layerIndex) => { + if (!node.nodeData.data.expand) { + return + } + // 调整top + let len = node.children.length + // 调整三级及以下节点的top + if (parent && !parent.isRoot && len > 0) { + let totalHeight = node.children.reduce((h, item) => { + return ( + h + + item.height + + (this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + ) + }, 0) + this.updateBrothersTop(node, totalHeight) + } + }, + (node, parent) => { + // 将二级节点的子节点移到上方 + if (parent && parent.isRoot) { + // 遍历二级节点的子节点 + let totalHeight = 0 + node.children.forEach(item => { + // 调整top + let nodeTotalHeight = this.getNodeAreaHeight(item) + let _top = item.top + item.top = + node.top - (item.top - node.top) - nodeTotalHeight + node.height + // 调整left + let offsetLeft = + (nodeTotalHeight + totalHeight) / Math.tan(degToRad(45)) + item.left += offsetLeft + totalHeight += nodeTotalHeight + // 同步更新后代节点 + this.updateChildrenPro(item.children, { + top: item.top - _top, + left: offsetLeft + }) + }) + } + // 调整二级节点的子节点的left值 + if (node.isRoot) { + let totalLeft = 0 + node.children.forEach(item => { + item.left += totalLeft + this.updateChildren(item.children, 'left', totalLeft) + let { left, right } = this.getNodeBoundaries(item, 'h') + totalLeft += right - left + }) + } + }, + true + ) + } + + // 递归计算节点的宽度 + getNodeAreaWidth(node) { + let widthArr = [] + let loop = (node, width) => { + if (node.children.length) { + width += node.width / 2 + node.children.forEach(item => { + loop(item, width) + }) + } else { + width += node.width + widthArr.push(width) + } + } + loop(node, 0) + return Math.max(...widthArr) + } + + // 递归计算节点的宽度 + getNodeAreaHeight(node) { + let totalHeight = 0 + let loop = node => { + totalHeight += + node.height + + (this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) + if (node.children.length) { + node.children.forEach(item => { + loop(item) + }) + } + } + loop(node) + return totalHeight + } + + // 调整兄弟节点的left + updateBrothersLeft(node) { + let childrenList = node.children + let totalAddWidth = 0 + childrenList.forEach(item => { + item.left += totalAddWidth + if (item.children && item.children.length) { + this.updateChildren(item.children, 'left', totalAddWidth) + } + // let areaWidth = this.getNodeAreaWidth(item) + let { left, right } = this.getNodeBoundaries(item, 'h') + let areaWidth = right - left + let difference = areaWidth - item.width + if (difference > 0) { + totalAddWidth += difference + } + }) + } + + // 调整兄弟节点的top + updateBrothersTop(node, addHeight) { + if (node.parent && !node.parent.isRoot) { + let childrenList = node.parent.children + let index = childrenList.findIndex(item => { + return item === node + }) + childrenList.forEach((item, _index) => { + if (item.hasCustomPosition()) { + // 适配自定义位置 + return + } + let _offset = 0 + // 下面的节点往下移 + if (_index > index) { + _offset = addHeight + } + item.top += _offset + // 同步更新子节点的位置 + if (item.children && item.children.length) { + this.updateChildren(item.children, 'top', _offset) + } + }) + // 更新父节点的位置 + this.updateBrothersTop(node.parent, addHeight) + } + } + + // 绘制连线,连接该节点到其子节点 + renderLine(node, lines, style) { + if (node.children.length <= 0) { + return [] + } + let { left, top, width, height, expandBtnSize } = node + let len = node.children.length + if (node.isRoot) { + // 当前节点是根节点 + let prevBother = node + // 根节点的子节点是和根节点同一水平线排列 + node.children.forEach((item, index) => { + let x1 = prevBother.left + prevBother.width + let x2 = item.left + let y = node.top + node.height / 2 + let path = `M ${x1},${y} L ${x2},${y}` + lines[index].plot(path) + style && style(lines[index], item) + prevBother = item + }) + } else { + // 当前节点为非根节点 + let maxy = -Infinity + let miny = Infinity + let maxx = -Infinity + let x = node.left + node.width * 0.3 + node.children.forEach((item, index) => { + if (item.left > maxx) { + maxx = item.left + } + let y = item.top + item.height / 2 + if (y > maxy) { + maxy = y + } + if (y < miny) { + miny = y + } + // 水平线 + if (node.layerIndex > 1) { + let path = `M ${x},${y} L ${item.left},${y}` + lines[index].plot(path) + style && style(lines[index], item) + } + }) + // 竖线 + if (len > 0) { + let line = this.draw.path() + expandBtnSize = len > 0 ? expandBtnSize : 0 + let lineLength = maxx - node.left - node.width * 0.3 + if ( + node.parent && + node.parent.isRoot && + node.dir === CONSTANTS.TIMELINE_DIR.TOP + ) { + line.plot( + `M ${x},${top} L ${x + lineLength},${ + top - Math.tan(degToRad(45)) * lineLength + }` + ) + } else { + if (node.parent && node.parent.isRoot) { + line.plot( + `M ${x},${top} L ${x + lineLength},${ + top - Math.tan(degToRad(45)) * lineLength + }` + ) + } else { + line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`) + } + } + node.style.line(line) + node._lines.push(line) + style && style(line, node) + } + } + } + + // 渲染按钮 + renderExpandBtn(node, btn) { + let { width, height, expandBtnSize, isRoot } = node + if (!isRoot) { + let { translateX, translateY } = btn.transform() + if (node.parent && node.parent.isRoot) { + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + -expandBtnSize / 2 - translateY + ) + } else { + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + height + expandBtnSize / 2 - translateY + ) + } + } + } + + // 创建概要节点 + renderGeneralization(node, gLine, gNode) { + let { + top, + bottom, + right, + generalizationLineMargin, + generalizationNodeMargin + } = this.getNodeBoundaries(node, 'h') + let x1 = right + generalizationLineMargin + let y1 = top + let x2 = right + generalizationLineMargin + let y2 = bottom + let cx = x1 + 20 + let cy = y1 + (y2 - y1) / 2 + let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}` + gLine.plot(path) + gNode.left = right + generalizationNodeMargin + gNode.top = top + (bottom - top - gNode.height) / 2 + } +} + +export default Fishbone diff --git a/simple-mind-map/src/layouts/Fishbone.js b/simple-mind-map/src/layouts/Fishbone.js index 32413010..7f63e236 100644 --- a/simple-mind-map/src/layouts/Fishbone.js +++ b/simple-mind-map/src/layouts/Fishbone.js @@ -1,10 +1,7 @@ import Base from './Base' -import { walk, asyncRun } from '../utils' +import { walk, asyncRun, degToRad } from '../utils' import { CONSTANTS } from '../utils/constant' - -const degToRad = deg => { - return (Math.PI / 180) * deg -} +import utils from './fishboneUtils' // 鱼骨图 class Fishbone extends Base { @@ -57,7 +54,11 @@ class Fishbone extends Base { } // 计算二级节点的top值 if (parent._node.isRoot) { - newNode.top = parent._node.top - newNode.height + if (newNode.dir === 'top') { + newNode.top = parent._node.top - newNode.height + } else { + newNode.top = parent._node.top + parent._node.height + } } } if (!node.data.expand) { @@ -77,26 +78,23 @@ class Fishbone extends Base { null, (node, parent, isRoot, layerIndex, index) => { if (node.isRoot) { - let totalLeft = node.left + node.width + let topTotalLeft = node.left + node.width + node.height + let bottomTotalLeft = node.left + node.width + node.height node.children.forEach(item => { - item.left = totalLeft - totalLeft += item.width + if (item.dir === 'top') { + item.left = topTotalLeft + topTotalLeft += item.width + } else { + item.left = bottomTotalLeft + 20 + bottomTotalLeft += item.width + } }) } - if (layerIndex >= 1 && node.children) { - // 遍历三级及以下节点的子节点 - let startLeft = node.left + node.width * 0.5 - let totalTop = - node.top + - node.height + - (this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) - node.children.forEach(item => { - item.left = startLeft - item.top += totalTop - totalTop += - item.height + - (this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) - }) + let params = { layerIndex, node, ctx: this } + if (node.dir === 'top') { + utils.top.computedLeftTopValue(params) + } else { + utils.bottom.computedLeftTopValue(params) } }, null, @@ -113,51 +111,36 @@ class Fishbone extends Base { if (!node.nodeData.data.expand) { return } - // 调整top - let len = node.children.length - // 调整三级及以下节点的top - if (parent && !parent.isRoot && len > 0) { - let totalHeight = node.children.reduce((h, item) => { - return ( - h + - item.height + - (this.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) - ) - }, 0) - this.updateBrothersTop(node, totalHeight) + let params = { node, parent, layerIndex, ctx: this } + if (node.dir === 'top') { + utils.top.adjustLeftTopValueBefore(params) + } else { + utils.bottom.adjustLeftTopValueBefore(params) } }, (node, parent) => { - // 将二级节点的子节点移到上方 - if (parent && parent.isRoot) { - // 遍历二级节点的子节点 - let totalHeight = 0 - node.children.forEach(item => { - // 调整top - let nodeTotalHeight = this.getNodeAreaHeight(item) - let _top = item.top - item.top = - node.top - (item.top - node.top) - nodeTotalHeight + node.height - // 调整left - let offsetLeft = - (nodeTotalHeight + totalHeight) / Math.tan(degToRad(45)) - item.left += offsetLeft - totalHeight += nodeTotalHeight - // 同步更新后代节点 - this.updateChildrenPro(item.children, { - top: item.top - _top, - left: offsetLeft - }) - }) + let params = { parent, node, ctx: this } + if (node.dir === 'top') { + utils.top.adjustLeftTopValueAfter(params) + } else { + utils.bottom.adjustLeftTopValueAfter(params) } // 调整二级节点的子节点的left值 if (node.isRoot) { - let totalLeft = 0 + let topTotalLeft = 0 + let bottomTotalLeft = 0 node.children.forEach(item => { - item.left += totalLeft - this.updateChildren(item.children, 'left', totalLeft) - let { left, right } = this.getNodeBoundaries(item, 'h') - totalLeft += right - left + if (item.dir === 'top') { + item.left += topTotalLeft + this.updateChildren(item.children, 'left', topTotalLeft) + let { left, right } = this.getNodeBoundaries(item, 'h') + topTotalLeft += right - left + } else { + item.left += bottomTotalLeft + this.updateChildren(item.children, 'left', bottomTotalLeft) + let { left, right } = this.getNodeBoundaries(item, 'h') + bottomTotalLeft += right - left + } }) } }, @@ -243,30 +226,66 @@ class Fishbone extends Base { } }) // 更新父节点的位置 - this.updateBrothersTop(node.parent, addHeight) + if (node.dir === 'top') { + this.updateBrothersTop(node.parent, addHeight) + } else { + this.updateBrothersTop( + node.parent, + node.layerIndex === 3 ? 0 : addHeight + ) + } } } // 绘制连线,连接该节点到其子节点 renderLine(node, lines, style) { - if (node.children.length <= 0) { + if (node.layerIndex !== 1 && node.children.length <= 0) { return [] } let { left, top, width, height, expandBtnSize } = node let len = node.children.length if (node.isRoot) { // 当前节点是根节点 - let prevBother = node // 根节点的子节点是和根节点同一水平线排列 - node.children.forEach((item, index) => { - let x1 = prevBother.left + prevBother.width - let x2 = item.left - let y = node.top + node.height / 2 - let path = `M ${x1},${y} L ${x2},${y}` - lines[index].plot(path) - style && style(lines[index], item) - prevBother = item + let maxx = -Infinity + node.children.forEach(item => { + if (item.left > maxx) { + maxx = item.left + } + // 水平线段到二级节点的连线 + let nodeLineX = item.left + item.width * 0.3 + let offset = item.height + node.height / 2 + let offsetX = offset / Math.tan(degToRad(45)) + let line = this.draw.path() + if (item.dir === 'top') { + line.plot( + `M ${nodeLineX - offsetX},${item.top + offset} L ${nodeLineX},${ + item.top + }` + ) + } else { + line.plot( + `M ${nodeLineX - offsetX},${ + item.top + item.height - offset + } L ${nodeLineX},${item.top + item.height}` + ) + } + node.style.line(line) + node._lines.push(line) + style && style(line, node) }) + // 从根节点出发的水平线 + let nodeHalfTop = node.top + node.height / 2 + let offset = node.height / 2 + let line = this.draw.path() + line.plot( + `M ${node.left + node.width},${nodeHalfTop} L ${ + maxx - offset / Math.tan(degToRad(45)) + },${nodeHalfTop}` + ) + node.style.line(line) + node._lines.push(line) + style && style(line, node) } else { // 当前节点为非根节点 let maxy = -Infinity @@ -291,36 +310,27 @@ class Fishbone extends Base { style && style(lines[index], item) } }) - // 竖线 - if (len > 0) { + // 斜线 + if (len >= 0) { let line = this.draw.path() expandBtnSize = len > 0 ? expandBtnSize : 0 let lineLength = maxx - node.left - node.width * 0.3 - if ( - node.parent && - node.parent.isRoot && - node.dir === CONSTANTS.TIMELINE_DIR.TOP - ) { - line.plot( - `M ${x},${top} L ${x + lineLength},${ - top - Math.tan(degToRad(45)) * lineLength - }` - ) + lineLength = Math.max(lineLength, 0) + let params = { + node, + line, + top, + x, + lineLength, + height, + expandBtnSize, + maxy, + miny + } + if (node.dir === 'top') { + utils.top.renderLine(params) } else { - if (node.parent && node.parent.isRoot) { - line.plot( - `M ${x},${top} L ${x + lineLength},${ - top - Math.tan(degToRad(45)) * lineLength - }` - ) - // line.plot( - // `M ${x},${top + height + expandBtnSize} L ${x + 1000},${ - // top + Math.tan(degToRad(45)) * 1000 - // }` - // ) - } else { - line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`) - } + utils.bottom.renderLine(params) } node.style.line(line) node._lines.push(line) @@ -334,16 +344,19 @@ class Fishbone extends Base { let { width, height, expandBtnSize, isRoot } = node if (!isRoot) { let { translateX, translateY } = btn.transform() - if (node.parent && node.parent.isRoot) { - btn.translate( - width * 0.3 - expandBtnSize / 2 - translateX, - -expandBtnSize / 2 - translateY - ) + let params = { + node, + btn, + expandBtnSize, + translateX, + translateY, + width, + height + } + if (node.dir === 'top') { + utils.top.renderExpandBtn(params) } else { - btn.translate( - width * 0.3 - expandBtnSize / 2 - translateX, - height + expandBtnSize / 2 - translateY - ) + utils.bottom.renderExpandBtn(params) } } } diff --git a/simple-mind-map/src/layouts/fishboneUtils.js b/simple-mind-map/src/layouts/fishboneUtils.js new file mode 100644 index 00000000..9b567121 --- /dev/null +++ b/simple-mind-map/src/layouts/fishboneUtils.js @@ -0,0 +1,217 @@ +import { degToRad } from '../utils/' + +export default { + top: { + renderExpandBtn({ + node, + btn, + expandBtnSize, + translateX, + translateY, + width, + height + }) { + if (node.parent && node.parent.isRoot) { + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + -expandBtnSize / 2 - translateY + ) + } else { + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + height + expandBtnSize / 2 - translateY + ) + } + }, + renderLine({ + node, + line, + top, + x, + lineLength, + height, + expandBtnSize, + maxy + }) { + if (node.parent && node.parent.isRoot) { + line.plot( + `M ${x},${top} L ${x + lineLength},${ + top - Math.tan(degToRad(45)) * lineLength + }` + ) + } else { + line.plot(`M ${x},${top + height + expandBtnSize} L ${x},${maxy}`) + } + }, + computedLeftTopValue({ layerIndex, node, ctx }) { + if (layerIndex >= 1 && node.children) { + // 遍历三级及以下节点的子节点 + let startLeft = node.left + node.width * 0.5 + let totalTop = + node.top + + node.height + + (ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) + node.children.forEach(item => { + item.left = startLeft + item.top += totalTop + totalTop += + item.height + + (ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + }) + } + }, + adjustLeftTopValueBefore({ node, parent, ctx }) { + // 调整top + let len = node.children.length + // 调整三级及以下节点的top + if (parent && !parent.isRoot && len > 0) { + let totalHeight = node.children.reduce((h, item) => { + return ( + h + + item.height + + (ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + ) + }, 0) + ctx.updateBrothersTop(node, totalHeight) + } + }, + adjustLeftTopValueAfter({ parent, node, ctx }) { + // 将二级节点的子节点移到上方 + if (parent && parent.isRoot) { + // 遍历二级节点的子节点 + let totalHeight = 0 + node.children.forEach(item => { + // 调整top + let nodeTotalHeight = ctx.getNodeAreaHeight(item) + let _top = item.top + item.top = + node.top - (item.top - node.top) - nodeTotalHeight + node.height + // 调整left + let offsetLeft = + (nodeTotalHeight + totalHeight) / Math.tan(degToRad(45)) + item.left += offsetLeft + totalHeight += nodeTotalHeight + // 同步更新后代节点 + ctx.updateChildrenPro(item.children, { + top: item.top - _top, + left: offsetLeft + }) + }) + } + } + }, + bottom: { + renderExpandBtn({ + node, + btn, + expandBtnSize, + translateX, + translateY, + width, + height + }) { + if (node.parent && node.parent.isRoot) { + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + height + expandBtnSize / 2 - translateY + ) + } else { + btn.translate( + width * 0.3 - expandBtnSize / 2 - translateX, + -expandBtnSize / 2 - translateY + ) + } + }, + renderLine({ node, line, top, x, lineLength, height, miny }) { + if (node.parent && node.parent.isRoot) { + line.plot( + `M ${x},${top + height} L ${x + lineLength},${ + top + height + Math.tan(degToRad(45)) * lineLength + }` + ) + } else { + line.plot(`M ${x},${top} L ${x},${miny}`) + } + }, + computedLeftTopValue({ layerIndex, node, ctx }) { + if (layerIndex === 1 && node.children) { + // 遍历二级节点的子节点 + let startLeft = node.left + node.width * 0.5 + let totalTop = + node.top + + node.height + + (ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) + + node.children.forEach(item => { + item.left = startLeft + item.top = + totalTop + + (ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + totalTop += + item.height + + (ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + }) + } + if (layerIndex > 1 && node.children) { + // 遍历三级及以下节点的子节点 + let startLeft = node.left + node.width * 0.5 + let totalTop = + node.top - + (ctx.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) + node.children.forEach(item => { + item.left = startLeft + item.top = totalTop - item.height + totalTop -= + item.height + + (ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + }) + } + }, + adjustLeftTopValueBefore({ node, ctx, layerIndex }) { + // 调整top + let len = node.children.length + if (layerIndex > 2 && len > 0) { + let totalHeight = node.children.reduce((h, item) => { + return ( + h + + item.height + + (ctx.getNodeActChildrenLength(item) > 0 ? item.expandBtnSize : 0) + ) + }, 0) + ctx.updateBrothersTop(node, -totalHeight) + } + }, + adjustLeftTopValueAfter({ parent, node, ctx }) { + // 将二级节点的子节点移到上方 + if (parent && parent.isRoot) { + // 遍历二级节点的子节点 + let totalHeight = 0 + let totalHeight2 = 0 + node.children.forEach(item => { + // 调整top + let hasChildren = ctx.getNodeActChildrenLength(item) > 0 + let nodeTotalHeight = ctx.getNodeAreaHeight(item) + let offset = + hasChildren > 0 + ? nodeTotalHeight - + item.height - + (hasChildren ? item.expandBtnSize : 0) + : 0 + let _top = totalHeight + offset + item.top += _top + // 调整left + let offsetLeft = + (totalHeight2 + nodeTotalHeight) / Math.tan(degToRad(45)) + item.left += offsetLeft + totalHeight += offset + totalHeight2 += nodeTotalHeight + // 同步更新后代节点 + ctx.updateChildrenPro(item.children, { + top: _top, + left: offsetLeft + }) + }) + } + } + } +}