Feat:支持导出某个节点为图片
This commit is contained in:
parent
6878d92ebe
commit
e072dcb170
@ -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 // 思维导图图形的宽高比
|
||||
|
||||
@ -565,6 +565,12 @@ class Node {
|
||||
this.renderer.emitNodeActiveEvent(this)
|
||||
}
|
||||
|
||||
// 取消激活该节点
|
||||
deactivate() {
|
||||
this.mindMap.renderer.removeNodeFromActiveList(this)
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
}
|
||||
|
||||
// 更新节点
|
||||
update() {
|
||||
if (!this.group) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user