From ba97fa7ef10fd039d48c02a7468fba1036e2ea87 Mon Sep 17 00:00:00 2001 From: Yanyan-Wang Date: Wed, 10 Jun 2020 14:43:42 +0800 Subject: [PATCH] feat: generate the image url of the full graph by graph.toFullDataUrl. docs: update the api about toFullDataUrl and other download related api. closes: #1657. --- CHANGELOG.md | 1 + docs/api/Graph.en.md | 54 ++++++++++++++++++++-- docs/api/Graph.zh.md | 50 +++++++++++++++++++- gatsby-browser.js | 4 +- src/graph/graph.ts | 106 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 199 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a18a1e8de8f..329d04280b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### 3.5.3 - feat: focusItem with animation; +- feat: generate the image url of the full graph by graph.toFullDataUrl; - fix: graph dispears after being dragged out of the canvas and back; - fix: the graph cannot be dragged back if it is already out of the view; - fix: size and radius of the linkPoints problem; diff --git a/docs/api/Graph.en.md b/docs/api/Graph.en.md index c13a31dba89..b65f71018a1 100644 --- a/docs/api/Graph.en.md +++ b/docs/api/Graph.en.md @@ -1748,7 +1748,7 @@ graph.set('nodeIdList', [1, 3, 5]); -### downloadFullImage(name, imageConfig) +### downloadFullImage(name, type, imageConfig) Export the whole graph as an image, whatever (a part of) the graph is out of the screen. @@ -1757,6 +1757,7 @@ Export the whole graph as an image, whatever (a part of) the graph is out of the | Name | Type | Required | Description | | ---- | ------ | -------- | ---------- | | name | String | false | The name of the image. 'graph' by default. | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | | imageConfig | Object | false | The configuration for the exported image, detials are shown below | where the `imageConfig` is the configuration for exported image: @@ -1792,15 +1793,15 @@ Export the canvas as an image. graph.downloadImage(); ``` -### toDataURL() +### toDataURL(type, backgroundColor) -Generate url of a image of the canvas. +Generate url of the image of the graph inside the view port. **Parameters** | Name | Type | Required | Description | | ---- | ------ | -------- | ---------- | -| type | String | false | The type of the image, options: `'image/png'`, `'image/jpeg'` | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | | backgroundColor | String | false | The background color of the image. If it is not assigned, the background will be transparent. | **Return** @@ -1813,3 +1814,48 @@ Generate url of a image of the canvas. ```javascript const dataURL = graph.toDataURL(); ``` + + + +### toFullDataURL(callback, type, backgroundColor) + +Generate url of the image of the whole graph including the part out of the view port. + +**Parameters** + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ---------- | +| callback | Function | true | The callback function after finish generating the dataUrl of the full graph +Asynchronously | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | The type of the image. When the `renderer` of the graph is `'canvas'`(default), `type` takes effect. When the `renderer` is `'svg'`, `toFullDataURL` will export a svg file | +| imageConfig | Object | false | The configuration for the exported image, detials are shown below | + +where the `imageConfig` is the configuration for exported image: + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ---------- | +| backgroundColor | String | false | The background color of the image. If it is not assigned, the background will be transparent. | +| padding | Number / Number[] | false | The top, right, bottom, right paddings of the exported image. When its type is number, the paddings around the graph are the same | + + +No return value, you can process the result in the callback function as shown below: + + +**Usage** + +```javascript +graph.toFullDataUrl( + // The first parameter: callback, required + (res) => { + // ... something + console.log(res); // e.g. print the result + }, + // The second and third parameter is not required + 'image/jpeg', + imageConfig: { + backgroundColor: '#fff', + padding: 10 + } + +) +``` \ No newline at end of file diff --git a/docs/api/Graph.zh.md b/docs/api/Graph.zh.md index 13e04b12706..812d21a0967 100644 --- a/docs/api/Graph.zh.md +++ b/docs/api/Graph.zh.md @@ -1733,7 +1733,7 @@ graph.set('nodeIdList', [1, 3, 5]); ``` -### downloadFullImage(name, imageConfig) +### downloadFullImage(name, type, imageConfig) 将画布上的元素导出为图片。 @@ -1742,6 +1742,7 @@ graph.set('nodeIdList', [1, 3, 5]); | 名称 | 类型 | 是否必选 | 描述 | | ---- | ------ | -------- | ---------- | | name | String | false | 图片的名称,不指定则为 'graph' | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | | imageConfig | Object | false | 图片的配置项,可选,具体字段见下方 | 其中,imageConfig 为导出图片的配置参数: @@ -1770,6 +1771,7 @@ graph.downloadFullImage('tree-graph', { | 名称 | 类型 | 是否必选 | 描述 | | ---- | ------ | -------- | ---------- | | name | String | false | 图片的名称,不指定则为 'graph' | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | | backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | **用法** @@ -1778,6 +1780,50 @@ graph.downloadFullImage('tree-graph', { graph.downloadImage(); ``` +### toFullDataURL(callback, type, imageConfig) + +将画布上元素生成为图片的 URL。 + +**参数** + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------- | +| callback | Function | true | 异步生成 dataUrl 完成后的回调函数,在这里处理生成的 dataUrl 字符串 | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | +| imageConfig | Object | false | 图片的配置项,可选,具体字段见下方 | + + +其中,imageConfig 为导出图片的配置参数: + +| 名称 | 类型 | 是否必选 | 描述 | +| ---- | ------ | -------- | ---------- | +| backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | +| padding | Number / Number[] | false | 导出图片的上左下右 padding 值。当 `padding` 为 number 类型时,四周 `padding` 相等 | + + +无返回值,生成的结果请在 callback 中处理。如下示例: + + +**用法** + +```javascript +graph.toFullDataUrl( + // 第一个参数为 callback,必须 + (res) => { + // ... something + console.log(res); // 打印出结果 + }, + // 后两个参数不是必须 + 'image/jpeg', + imageConfig: { + backgroundColor: '#fff', + padding: 10 + } + +) +``` + + ### toDataURL(type, backgroundColor) 将画布上元素生成为图片的 URL。 @@ -1786,7 +1832,7 @@ graph.downloadImage(); | 名称 | 类型 | 是否必选 | 描述 | | ---- | ------ | -------- | ---------- | -| type | String | false | 图片类型,可选值:`'image/png'`,`'image/jpeg'` | +| type | `'image/png'` / `'image/jpeg'` / `'image/webp'` / `'image/bmp'` | false | 图片的类型。图的 `renderer` 为默认的 `'canvas'` 时生效,图的 `renderer` 为 `'svg'` 时将导出 svg 文件 | | backgroundColor | String | false | 图片的背景色,可选,不传值时将导出透明背景的图片 | **返回值** diff --git a/gatsby-browser.js b/gatsby-browser.js index d1b4d76a74c..98906e728fe 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -1,3 +1,3 @@ -window.g6 = require('./src/index.ts'); // import the source for debugging -// window.g6 = require('./dist/g6.min.js'); // import the package for webworker +// window.g6 = require('./src/index.ts'); // import the source for debugging +window.g6 = require('./dist/g6.min.js'); // import the package for webworker window.insertCss = require('insert-css'); diff --git a/src/graph/graph.ts b/src/graph/graph.ts index 7041f6dfd6d..cc536e28821 100644 --- a/src/graph/graph.ts +++ b/src/graph/graph.ts @@ -59,6 +59,8 @@ interface IGroupBBox { [key: string]: BBox; } +type dataUrlType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/bmp'; + export interface PrivateGraphOption extends GraphOptions { data: GraphData; @@ -1872,10 +1874,12 @@ export default class Graph extends EventEmitter implements IGraph { } /** - * 返回图表的 dataUrl 用于生成图片 + * 返回可见区域的图的 dataUrl,用于生成图片 + * @param {String} type 图片类型,可选值:"image/png" | "image/jpeg" | "image/webp" | "image/bmp" + * @param {string} backgroundColor 图片背景色 * @return {string} 图片 dataURL */ - public toDataURL(type?: string, backgroundColor?: string): string { + public toDataURL(type?: dataUrlType, backgroundColor?: string): string { const canvas: GCanvas = this.get('canvas'); const renderer = canvas.getRenderer(); const canvasDom = canvas.get('el'); @@ -1916,11 +1920,93 @@ export default class Graph extends EventEmitter implements IGraph { return dataURL; } + /** + * 返回整个图(包括超出可见区域的部分)的 dataUrl,用于生成图片 + * @param {Function} callback 异步生成 dataUrl 完成后的回调函数,在这里处理生成的 dataUrl 字符串 + * @param {String} type 图片类型,可选值:"image/png" | "image/jpeg" | "image/webp" | "image/bmp" + * @param {Object} imageConfig 图片配置项,包括背景色和上下左右的 padding + */ + public toFullDataURL(callback: (res: string) => any, type?: dataUrlType, imageConfig?: { backgroundColor?: string, padding?: number | number[] }) { + const bbox = this.get('group').getCanvasBBox(); + const height = bbox.height; + const width = bbox.width; + const renderer = this.get('renderer'); + const vContainerDOM: HTMLDivElement = createDom(''); + + let backgroundColor = imageConfig ? imageConfig.backgroundColor : undefined; + let padding = imageConfig ? imageConfig.padding : undefined; + if (!padding) padding = [0, 0, 0, 0]; + else if (isNumber(padding)) padding = [padding, padding, padding, padding]; + + const vHeight = height + padding[0] + padding[2]; + const vWidth = width + padding[1] + padding[3]; + const canvasOptions = { + container: vContainerDOM, + height: vHeight, + width: vWidth + }; + const vCanvas = renderer === 'svg' ? new GSVGCanvas(canvasOptions) : new GCanvas(canvasOptions); + + const group = this.get('group'); + const vGroup = group.clone(); + + let matrix = clone(vGroup.getMatrix()); + if (!matrix) matrix = mat3.create(); + const centerX = (bbox.maxX + bbox.minX) / 2; + const centerY = (bbox.maxY + bbox.minY) / 2; + mat3.translate(matrix, matrix, [-centerX, -centerY]); + mat3.translate(matrix, matrix, [width / 2 + padding[3], height / 2 + padding[0]]); + + vGroup.resetMatrix(); + vGroup.setMatrix(matrix); + vCanvas.add(vGroup); + + const vCanvasEl = vCanvas.get('el'); + + let dataURL = ''; + if (!type) type = 'image/png'; + + setTimeout(() => { + if (renderer === 'svg') { + const clone = vCanvasEl.cloneNode(true); + const svgDocType = document.implementation.createDocumentType( + 'svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' + ); + const svgDoc = document.implementation.createDocument('http://www.w3.org/2000/svg', 'svg', svgDocType); + svgDoc.replaceChild(clone, svgDoc.documentElement); + const svgData = (new XMLSerializer()).serializeToString(svgDoc); + dataURL = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(svgData); + } else { + let imageData; + const context = vCanvasEl.getContext('2d'); + let compositeOperation; + if (backgroundColor) { + const pixelRatio = window.devicePixelRatio; + imageData = context.getImageData(0, 0, vWidth * pixelRatio, vHeight * pixelRatio); + compositeOperation = context.globalCompositeOperation; + context.globalCompositeOperation = "destination-over"; + context.fillStyle = backgroundColor; + context.fillRect(0, 0, vWidth, vHeight); + } + dataURL = vCanvasEl.toDataURL(type); + if (backgroundColor) { + context.clearRect(0, 0, vWidth, vHeight); + context.putImageData(imageData, 0, 0); + context.globalCompositeOperation = compositeOperation; + } + } + callback && callback(dataURL); + }, 16); + } + /** * 导出包含全图的图片 * @param {String} name 图片的名称 + * @param {String} type 图片类型,可选值:"image/png" | "image/jpeg" | "image/webp" | "image/bmp" + * @param {Object} imageConfig 图片配置项,包括背景色和上下左右的 padding */ - public downloadFullImage(name?: string, imageConfig?: { backgroundColor?: string, padding?: number | number[] }): void { + public downloadFullImage(name?: string, type?: dataUrlType, imageConfig?: { backgroundColor?: string, padding?: number | number[] }): void { + const bbox = this.get('group').getCanvasBBox(); const height = bbox.height; const width = bbox.width; @@ -1957,8 +2043,8 @@ export default class Graph extends EventEmitter implements IGraph { const vCanvasEl = vCanvas.get('el'); + if (!type) type = 'image/png'; setTimeout(() => { - const type = 'image/png'; let dataURL = ''; if (renderer === 'svg') { const clone = vCanvasEl.cloneNode(true); @@ -1989,8 +2075,9 @@ export default class Graph extends EventEmitter implements IGraph { } } + const link: HTMLAnchorElement = document.createElement('a'); - const fileName: string = (name || 'graph') + (renderer === 'svg' ? '.svg' : '.png'); + const fileName: string = (name || 'graph') + (renderer === 'svg' ? '.svg' : `.${type.split('/')[1]}`); this.dataURLToImage(dataURL, renderer, link, fileName); @@ -2003,8 +2090,10 @@ export default class Graph extends EventEmitter implements IGraph { /** * 画布导出图片,图片仅包含画布可见区域部分内容 * @param {String} name 图片的名称 + * @param {String} type 图片类型,可选值:"image/png" | "image/jpeg" | "image/webp" | "image/bmp" + * @param {string} backgroundColor 图片背景色 */ - public downloadImage(name?: string, backgroundColor?: string): void { + public downloadImage(name?: string, type?: dataUrlType, backgroundColor?: string): void { const self = this; if (self.isAnimating()) { @@ -2013,10 +2102,11 @@ export default class Graph extends EventEmitter implements IGraph { const canvas = self.get('canvas'); const renderer = canvas.getRenderer(); - const fileName: string = (name || 'graph') + (renderer === 'svg' ? '.svg' : '.png'); + if (!type) type = 'image/png'; + const fileName: string = (name || 'graph') + (renderer === 'svg' ? '.svg' : type.split('/')[1]); const link: HTMLAnchorElement = document.createElement('a'); setTimeout(() => { - const dataURL = self.toDataURL('image/png', backgroundColor); + const dataURL = self.toDataURL(type, backgroundColor); this.dataURLToImage(dataURL, renderer, link, fileName); const e = document.createEvent('MouseEvents');