mirror of
https://gitee.com/antv/g6.git
synced 2024-12-03 12:18:40 +08:00
feat: downloadFullImage for full graph.
This commit is contained in:
parent
f82d5ecfd2
commit
b56c83d3eb
@ -50,7 +50,7 @@
|
|||||||
"site:deploy": "npm run site:build && gh-pages -d public",
|
"site:deploy": "npm run site:build && gh-pages -d public",
|
||||||
"start": "npm run site:develop",
|
"start": "npm run site:develop",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/plugins/minimap-spec.ts",
|
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/graph/graph-spec.ts",
|
||||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
|
||||||
"watch": "father build -w",
|
"watch": "father build -w",
|
||||||
"cdn": "antv-bin upload -n @antv/g6"
|
"cdn": "antv-bin upload -n @antv/g6"
|
||||||
|
@ -10,6 +10,7 @@ import deepMix from '@antv/util/lib/deep-mix';
|
|||||||
import each from '@antv/util/lib/each';
|
import each from '@antv/util/lib/each';
|
||||||
import isPlainObject from '@antv/util/lib/is-plain-object';
|
import isPlainObject from '@antv/util/lib/is-plain-object';
|
||||||
import isString from '@antv/util/lib/is-string';
|
import isString from '@antv/util/lib/is-string';
|
||||||
|
import isNumber from '@antv/util/lib/is-number';
|
||||||
import {
|
import {
|
||||||
GraphAnimateConfig,
|
GraphAnimateConfig,
|
||||||
GraphOptions,
|
GraphOptions,
|
||||||
@ -46,6 +47,7 @@ import {
|
|||||||
ViewController,
|
ViewController,
|
||||||
} from './controller';
|
} from './controller';
|
||||||
import PluginBase from '../plugins/base';
|
import PluginBase from '../plugins/base';
|
||||||
|
import createDom from '@antv/dom-util/lib/create-dom';
|
||||||
|
|
||||||
const NODE = 'node';
|
const NODE = 'node';
|
||||||
const SVG = 'svg';
|
const SVG = 'svg';
|
||||||
@ -1364,7 +1366,91 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 画布导出图片
|
* 导出包含全图的图片
|
||||||
|
* @param {String} name 图片的名称
|
||||||
|
*/
|
||||||
|
public downloadFullImage(name?: string, imageConfig?: { backgroundColor?: string, padding?: number | number[]}): void {
|
||||||
|
const bbox = this.get('group').getCanvasBBox();
|
||||||
|
const height = bbox.height;
|
||||||
|
const width = bbox.width;
|
||||||
|
const renderer = this.get('renderer');
|
||||||
|
const vContainerDOM = createDom('<div id="test"></div>');
|
||||||
|
|
||||||
|
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 = 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');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const type = 'image/png';
|
||||||
|
let dataURL = '';
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const link: HTMLAnchorElement = document.createElement('a');
|
||||||
|
const fileName: string = (name || 'graph') + (renderer === 'svg' ? '.svg' : '.png');
|
||||||
|
|
||||||
|
this.dataURLToImage(dataURL, renderer, link, fileName);
|
||||||
|
|
||||||
|
const e = document.createEvent('MouseEvents');
|
||||||
|
e.initEvent('click', false, false);
|
||||||
|
link.dispatchEvent(e);
|
||||||
|
}, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 画布导出图片,图片仅包含画布可见区域部分内容
|
||||||
* @param {String} name 图片的名称
|
* @param {String} name 图片的名称
|
||||||
*/
|
*/
|
||||||
public downloadImage(name?: string, backgroundColor?: string): void {
|
public downloadImage(name?: string, backgroundColor?: string): void {
|
||||||
@ -1380,41 +1466,7 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
const link: HTMLAnchorElement = document.createElement('a');
|
const link: HTMLAnchorElement = document.createElement('a');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const dataURL = self.toDataURL('image/png', backgroundColor);
|
const dataURL = self.toDataURL('image/png', backgroundColor);
|
||||||
if (typeof window !== 'undefined') {
|
this.dataURLToImage(dataURL, renderer, link, fileName);
|
||||||
if (window.Blob && window.URL && renderer !== 'svg') {
|
|
||||||
const arr = dataURL.split(',');
|
|
||||||
let mime = '';
|
|
||||||
if (arr && arr.length > 0) {
|
|
||||||
const match = arr[0].match(/:(.*?);/);
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
if (match && match.length >= 2) mime = match[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const bstr = atob(arr[1]);
|
|
||||||
let n = bstr.length;
|
|
||||||
const u8arr = new Uint8Array(n);
|
|
||||||
|
|
||||||
while (n--) {
|
|
||||||
u8arr[n] = bstr.charCodeAt(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
const blobObj = new Blob([u8arr], { type: mime });
|
|
||||||
|
|
||||||
if (window.navigator.msSaveBlob) {
|
|
||||||
window.navigator.msSaveBlob(blobObj, fileName);
|
|
||||||
} else {
|
|
||||||
link.addEventListener('click', () => {
|
|
||||||
link.download = fileName;
|
|
||||||
link.href = window.URL.createObjectURL(blobObj);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
link.addEventListener('click', function() {
|
|
||||||
link.download = fileName;
|
|
||||||
link.href = dataURL;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const e = document.createEvent('MouseEvents');
|
const e = document.createEvent('MouseEvents');
|
||||||
e.initEvent('click', false, false);
|
e.initEvent('click', false, false);
|
||||||
@ -1422,6 +1474,43 @@ export default class Graph extends EventEmitter implements IGraph {
|
|||||||
}, 16);
|
}, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private dataURLToImage(dataURL: string, renderer: string, link, fileName) {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
if (window.Blob && window.URL && renderer !== 'svg') {
|
||||||
|
const arr = dataURL.split(',');
|
||||||
|
let mime = '';
|
||||||
|
if (arr && arr.length > 0) {
|
||||||
|
const match = arr[0].match(/:(.*?);/);
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
if (match && match.length >= 2) mime = match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const bstr = atob(arr[1]);
|
||||||
|
let n = bstr.length;
|
||||||
|
const u8arr = new Uint8Array(n);
|
||||||
|
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blobObj = new Blob([u8arr], { type: mime });
|
||||||
|
|
||||||
|
if (window.navigator.msSaveBlob) {
|
||||||
|
window.navigator.msSaveBlob(blobObj, fileName);
|
||||||
|
} else {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
link.download = fileName;
|
||||||
|
link.href = window.URL.createObjectURL(blobObj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
link.addEventListener('click', function() {
|
||||||
|
link.download = fileName;
|
||||||
|
link.href = dataURL;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 更换布局配置项
|
* 更换布局配置项
|
||||||
* @param {object} cfg 新布局配置项
|
* @param {object} cfg 新布局配置项
|
||||||
|
@ -495,11 +495,17 @@ export interface IGraph extends EventEmitter {
|
|||||||
toDataURL(type?: string, backgroundColor?: string): string;
|
toDataURL(type?: string, backgroundColor?: string): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 画布导出图片
|
* 画布导出图片,图片仅包含画布可见区域部分内容
|
||||||
* @param {String} name 图片的名称
|
* @param {String} name 图片的名称
|
||||||
*/
|
*/
|
||||||
downloadImage(name?: string, backgroundColor?: string): void;
|
downloadImage(name?: string, backgroundColor?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出包含全图的图片
|
||||||
|
* @param {String} name 图片的名称
|
||||||
|
*/
|
||||||
|
downloadFullImage(name?: string, imageConfig?: { backgroundColor?: string, padding?: number | number[]}): void;
|
||||||
|
|
||||||
// TODO 需要添加布局配置类型
|
// TODO 需要添加布局配置类型
|
||||||
/**
|
/**
|
||||||
* 更换布局配置项
|
* 更换布局配置项
|
||||||
|
@ -1375,4 +1375,48 @@ describe('auto rotate label on edge', () => {
|
|||||||
expect(groupMatrix[6]).toBe(100);
|
expect(groupMatrix[6]).toBe(100);
|
||||||
expect(groupMatrix[7]).toBe(120);
|
expect(groupMatrix[7]).toBe(120);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('auto rotate label on edge', () => {
|
||||||
|
const graph = new Graph({
|
||||||
|
container: div,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
modes: {
|
||||||
|
default: ['drag-node', 'zoom-canvas', 'drag-canvas'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'node1',
|
||||||
|
x: 100,
|
||||||
|
y: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'node2',
|
||||||
|
x: 800,
|
||||||
|
y: 200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
id: 'edge1',
|
||||||
|
target: 'node2',
|
||||||
|
source: 'node1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
it('downloadFullImage', () => {
|
||||||
|
graph.data(data);
|
||||||
|
graph.render();
|
||||||
|
graph.on('canvas:click', evt => {
|
||||||
|
graph.downloadFullImage('graph', {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: [ 40, 10, 10, 10 ]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue
Block a user