UNPKG

@zxr3680166/simple-mind-map

Version:

一个简单的web在线思维导图

406 lines (391 loc) 11.7 kB
import Base from './Base' import { walk, asyncRun, degToRad, getNodeIndexInNodeList } from '../utils' import { CONSTANTS } from '../constants/constant' import utils from './fishboneUtils' // 鱼骨图 class Fishbone extends Base { // 构造函数 constructor(opt = {}) { super(opt) this.indent = 0.3 this.childIndent = 0.5 } // 布局 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.LAYOUT_GROW_DIR.TOP : CONSTANTS.LAYOUT_GROW_DIR.BOTTOM } // 计算二级节点的top值 if (parent._node.isRoot) { let marginY = this.getMarginY(layerIndex) if (this.checkIsTop(newNode)) { newNode.top = parent._node.top - newNode.height - marginY } else { newNode.top = parent._node.top + parent._node.height + marginY } } } if (!node.data.expand) { return true } }, null, true, 0 ) } // 遍历节点树计算节点的left、top computedLeftTopValue() { walk( this.root, null, (node, parent, isRoot, layerIndex) => { if (node.isRoot) { let marginX = this.getMarginX(layerIndex + 1) let topTotalLeft = node.left + node.width + node.height + marginX let bottomTotalLeft = node.left + node.width + node.height + marginX node.children.forEach(item => { if (this.checkIsTop(item)) { item.left = topTotalLeft topTotalLeft += item.width + marginX } else { item.left = bottomTotalLeft + 20 bottomTotalLeft += item.width + marginX } }) } let params = { layerIndex, node, ctx: this } if (this.checkIsTop(node)) { utils.top.computedLeftTopValue(params) } else { utils.bottom.computedLeftTopValue(params) } }, null, true ) } // 调整节点left、top adjustLeftTopValue() { walk( this.root, null, (node, parent, isRoot, layerIndex) => { if (!node.getData('expand')) { return } let params = { node, parent, layerIndex, ctx: this } if (this.checkIsTop(node)) { utils.top.adjustLeftTopValueBefore(params) } else { utils.bottom.adjustLeftTopValueBefore(params) } }, (node, parent) => { let params = { parent, node, ctx: this } if (this.checkIsTop(node)) { utils.top.adjustLeftTopValueAfter(params) } else { utils.bottom.adjustLeftTopValueAfter(params) } // 调整二级节点的子节点的left值 if (node.isRoot) { let topTotalLeft = 0 let bottomTotalLeft = 0 node.children.forEach(item => { if (this.checkIsTop(item)) { 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 } }) } }, true ) } // 递归计算节点的宽度 getNodeAreaHeight(node) { let totalHeight = 0 let loop = node => { let marginY = this.getMarginY(node.layerIndex) totalHeight += node.height + (this.getNodeActChildrenLength(node) > 0 ? node.expandBtnSize : 0) + marginY 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 { 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 = getNodeIndexInNodeList(node, childrenList) 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) } }) // 更新父节点的位置 if (this.checkIsTop(node)) { this.updateBrothersTop(node.parent, addHeight) } else { this.updateBrothersTop( node.parent, node.layerIndex === 3 ? 0 : addHeight ) } } } // 检查节点是否是上方节点 checkIsTop(node) { return node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP } // 绘制连线,连接该节点到其子节点 renderLine(node, lines, style) { if (node.layerIndex !== 1 && node.children.length <= 0) { return [] } let { top, height, expandBtnSize } = node if (!this.mindMap.opt.alwaysShowExpandBtn) { expandBtnSize = 0 } let len = node.children.length if (node.isRoot) { // 当前节点是根节点 // 根节点的子节点是和根节点同一水平线排列 let maxx = -Infinity node.children.forEach(item => { if (item.left > maxx) { maxx = item.left } // 水平线段到二级节点的连线 let marginY = this.getMarginY(item.layerIndex) let nodeLineX = item.left let offset = node.height / 2 + marginY let offsetX = offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) let line = this.lineDraw.path() if (this.checkIsTop(item)) { line.plot( `M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${ item.left },${item.top + item.height}` ) } else { line.plot( `M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${ item.top }` ) } node.style.line(line) node._lines.push(line) style && style(line, node) }) // 从根节点出发的水平线 let nodeHalfTop = node.top + node.height / 2 let offset = node.height / 2 + this.getMarginY(node.layerIndex + 1) let line = this.lineDraw.path() line.plot( `M ${node.left + node.width},${nodeHalfTop} L ${ maxx - offset / Math.tan(degToRad(this.mindMap.opt.fishboneDeg)) },${nodeHalfTop}` ) node.style.line(line) node._lines.push(line) style && style(line, node) } else { // 当前节点为非根节点 let maxy = -Infinity let miny = Infinity let maxx = -Infinity let x = node.left + node.width * this.indent 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.lineDraw.path() expandBtnSize = len > 0 ? expandBtnSize : 0 let lineLength = maxx - node.left - node.width * this.indent lineLength = Math.max(lineLength, 0) let params = { node, line, top, x, lineLength, height, expandBtnSize, maxy, miny, ctx: this } if (this.checkIsTop(node)) { utils.top.renderLine(params) } else { utils.bottom.renderLine(params) } 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() let params = { node, btn, expandBtnSize, translateX, translateY, width, height } if (this.checkIsTop(node)) { utils.top.renderExpandBtn(params) } else { utils.bottom.renderExpandBtn(params) } } } // 创建概要节点 renderGeneralization(list) { list.forEach(item => { let { top, bottom, right, generalizationLineMargin, generalizationNodeMargin } = this.getNodeGeneralizationRenderBoundaries(item, '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}` item.generalizationLine.plot(path) item.generalizationNode.left = right + generalizationNodeMargin item.generalizationNode.top = top + (bottom - top - item.generalizationNode.height) / 2 }) } // 渲染展开收起按钮的隐藏占位元素 renderExpandBtnRect(rect, expandBtnSize, width, height, node) { let dir = '' if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) { dir = node.layerIndex === 1 ? CONSTANTS.LAYOUT_GROW_DIR.TOP : CONSTANTS.LAYOUT_GROW_DIR.BOTTOM } else { dir = node.layerIndex === 1 ? CONSTANTS.LAYOUT_GROW_DIR.BOTTOM : CONSTANTS.LAYOUT_GROW_DIR.TOP } if (dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) { rect.size(width, expandBtnSize).x(0).y(-expandBtnSize) } else { rect.size(width, expandBtnSize).x(0).y(height) } } } export default Fishbone