diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index b1a75161..f7b26e74 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -16,10 +16,10 @@ import { import { SVG } from '@svgdotjs/svg.js' import { simpleDeepClone, - getType, getObjectChangedProps, isUndef, - handleGetSvgDataExtraContent + handleGetSvgDataExtraContent, + getNodeTreeBoundingRect } from './src/utils' import defaultTheme, { checkIsNodeSizeIndependenceConfig @@ -410,7 +410,8 @@ class MindMap { paddingY = 0, ignoreWatermark = false, addContentToHeader, - addContentToFooter + addContentToFooter, + node } = {}) { const { cssTextList, header, headerHeight, footer, footerHeight } = handleGetSvgDataExtraContent({ @@ -428,6 +429,11 @@ class MindMap { draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY) // 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法 const rect = draw.rbox() + // 需要裁减的区域 + let clipData = null + if (node) { + clipData = getNodeTreeBoundingRect(node, rect.x, rect.y, paddingX, paddingY) + } // 内边距 const fixHeight = 0 rect.width += paddingX * 2 @@ -507,6 +513,7 @@ class MindMap { return { svg: clone, // 思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组) svgHTML: clone.svg(), // svg字符串 + clipData, rect: { ...rect, // 思维导图图形未缩放时的位置尺寸等信息 ratio: rect.width / rect.height // 思维导图图形的宽高比 diff --git a/simple-mind-map/src/core/render/node/Node.js b/simple-mind-map/src/core/render/node/Node.js index ffbc12b4..e233f3bb 100644 --- a/simple-mind-map/src/core/render/node/Node.js +++ b/simple-mind-map/src/core/render/node/Node.js @@ -565,6 +565,12 @@ class Node { this.renderer.emitNodeActiveEvent(this) } + // 取消激活该节点 + deactivate() { + this.mindMap.renderer.removeNodeFromActiveList(this) + this.mindMap.renderer.emitNodeActiveEvent() + } + // 更新节点 update() { if (!this.group) { diff --git a/simple-mind-map/src/plugins/Export.js b/simple-mind-map/src/plugins/Export.js index 589ff89a..64f7faef 100644 --- a/simple-mind-map/src/plugins/Export.js +++ b/simple-mind-map/src/plugins/Export.js @@ -47,15 +47,26 @@ class Export { } // 获取svg数据 - async getSvgData() { - let { exportPaddingX, exportPaddingY, errorHandler, resetCss, addContentToHeader, addContentToFooter } = - this.mindMap.opt - let { svg, svgHTML } = this.mindMap.getSvgData({ + async getSvgData(node) { + let { + exportPaddingX, + exportPaddingY, + errorHandler, + resetCss, + addContentToHeader, + addContentToFooter + } = this.mindMap.opt + let { svg, svgHTML, clipData } = this.mindMap.getSvgData({ paddingX: exportPaddingX, paddingY: exportPaddingY, addContentToHeader, - addContentToFooter + addContentToFooter, + node }) + if (clipData) { + clipData.paddingX = exportPaddingX + clipData.paddingY = exportPaddingY + } // svg的image标签,把图片的url转换成data:url类型,否则导出会丢失图片 const task1 = this.createTransformImgTaskList( svg, @@ -90,12 +101,13 @@ class Export { } return { node: svg, - str: svgHTML + str: svgHTML, + clipData } } // svg转png - svgToPng(svgSrc, transparent) { + svgToPng(svgSrc, transparent, clipData = null) { return new Promise((resolve, reject) => { const img = new Image() // 跨域图片需要添加这个属性,否则画布被污染了无法导出图片 @@ -109,6 +121,15 @@ class Export { ) let imgWidth = img.width let imgHeight = img.height + // 如果是裁减操作的话,那么需要手动添加内边距,及调整图片大小为实际的裁减区域的大小,不要忘了内边距哦 + let paddingX = 0 + let paddingY = 0 + if (clipData) { + paddingX = clipData.paddingX + paddingY = clipData.paddingY + imgWidth = clipData.width + paddingX * 2 + imgHeight = clipData.height + paddingY * 2 + } // 检查是否超出canvas支持的像素上限 const maxSize = 16384 / dpr const maxArea = maxSize * maxSize @@ -135,7 +156,22 @@ class Export { await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight) } // 图片绘制到canvas里 - ctx.drawImage(img, 0, 0, imgWidth, imgHeight) + // 如果有裁减数据,那么需要进行裁减 + if (clipData) { + ctx.drawImage( + img, + clipData.left, + clipData.top, + clipData.width, + clipData.height, + paddingX, + paddingY, + clipData.width, + clipData.height + ) + } else { + ctx.drawImage(img, 0, 0, imgWidth, imgHeight) + } resolve(canvas.toDataURL()) } catch (error) { reject(error) @@ -219,13 +255,24 @@ class Export { * 方法1.把svg的图片都转化成data:url格式,再转换 * 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换 */ - async png(name, transparent = false) { - const { str } = await this.getSvgData() + async png(name, transparent = false, node = null) { + this.handleNodeExport(node) + const { str, clipData } = await this.getSvgData(node) const svgUrl = await this.fixSvgStrAndToBlob(str) - const res = await this.svgToPng(svgUrl, transparent) + const res = await this.svgToPng(svgUrl, transparent, clipData) return res } + // 导出指定节点,如果该节点是激活状态,那么取消激活和隐藏展开收起按钮 + handleNodeExport(node) { + if (node && node.getData('isActive')) { + node.deactivate() + if (!this.mindMap.opt.alwaysShowExpandBtn && node.getData('expand')) { + node.removeExpandBtn() + } + } + } + // 导出为pdf async pdf(name, transparent = false) { if (!this.mindMap.doExportPDF) { diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js index a8014d84..0b9e2d47 100644 --- a/simple-mind-map/src/utils/index.js +++ b/simple-mind-map/src/utils/index.js @@ -1368,3 +1368,49 @@ export const handleGetSvgDataExtraContent = ({ footerHeight } } + +// 获取指定节点的包围框信息 +export const getNodeTreeBoundingRect = (node, x, y, paddingX, paddingY) => { + let minX = Infinity + let maxX = -Infinity + let minY = Infinity + let maxY = -Infinity + const walk = root => { + const { x, y, width, height } = root.group.findOne('.smm-node-shape').rbox() + if (x < minX) { + minX = x + } + if (x + width > maxX) { + maxX = x + width + } + if (y < minY) { + minY = y + } + if (y + height > maxY) { + maxY = y + height + } + if (root._generalizationList.length > 0) { + root._generalizationList.forEach(item => { + walk(item.generalizationNode) + }) + } + if (root.children) { + root.children.forEach(item => { + walk(item) + }) + } + } + walk(node) + + minX = minX - x + paddingX + minY = minY - y + paddingY + maxX = maxX - x + paddingX + maxY = maxY - y + paddingY + + return { + left: minX, + top: minY, + width: maxX - minX, + height: maxY - minY + } +}