UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

226 lines 9.9 kB
import { __decorate } from "tslib"; import { Basecoat, DataUri, Dom, disposable, FunctionExt, NumberExt, Vector, } from '../../common'; import { Rectangle } from '../../geometry'; import './api'; export class Export extends Basecoat { constructor() { super(...arguments); this.name = 'export'; } get view() { return this.graph.view; } init(graph) { this.graph = graph; } exportPNG(fileName = 'chart', options = {}) { this.toPNG((dataUri) => { DataUri.downloadDataUri(dataUri, fileName); }, options); } exportJPEG(fileName = 'chart', options = {}) { this.toJPEG((dataUri) => { DataUri.downloadDataUri(dataUri, fileName); }, options); } exportSVG(fileName = 'chart', options = {}) { this.toSVG((svg) => { DataUri.downloadDataUri(DataUri.svgToDataUrl(svg), fileName); }, options); } toSVG(callback, options = {}) { this.notify('before:export', options); // Keep pace with the doc: default values should apply only when // the option keys are not present on the target object. // If a key exists (even with `undefined`), we respect that and do not override. if (!Object.hasOwn(options, 'copyStyles')) { options.copyStyles = true; } if (!Object.hasOwn(options, 'serializeImages')) { options.serializeImages = true; } const rawSVG = this.view.svg; const vSVG = Vector.create(rawSVG).clone(); let clonedSVG = vSVG.node; const vStage = vSVG.findOne(`.${this.view.prefixClassName('graph-svg-stage')}`); const viewBox = options.viewBox || this.graph.graphToLocal(this.graph.getContentBBox()); const dimension = options.preserveDimensions; if (dimension) { const size = typeof dimension === 'boolean' ? viewBox : dimension; vSVG.attr({ width: size.width, height: size.height, }); } vSVG .removeAttribute('style') .attr('viewBox', [viewBox.x, viewBox.y, viewBox.width, viewBox.height].join(' ')); vStage.removeAttribute('transform'); // Copies style declarations from external stylesheets into inline `style` attributes by computing style differences. // Implementation steps: // 1) Compute default UA styles in an isolated document. // 2) Compute styles in the current document for each original SVG node. // 3) Build the diff (properties that differ from defaults). // 4) Apply the diff to cloned SVG nodes via inline `style`. if (options.copyStyles) { const document = rawSVG.ownerDocument; const raws = Array.from(rawSVG.querySelectorAll('*')); const clones = Array.from(clonedSVG.querySelectorAll('*')); const isolatedDoc = document.implementation.createHTMLDocument('x6-export-defaults'); const isolatedSVG = isolatedDoc.importNode(rawSVG, true); isolatedDoc.body.appendChild(isolatedSVG); const isolatedRaws = Array.from(isolatedSVG.querySelectorAll('*')); const defaultComputedStyles = {}; isolatedRaws.forEach((elem, index) => { const computedStyle = window.getComputedStyle(elem, null); const defaultComputedStyle = {}; // Use the style declaration list for reliable property names. for (let i = 0; i < computedStyle.length; i += 1) { const prop = computedStyle[i]; const val = computedStyle.getPropertyValue(prop); defaultComputedStyle[prop] = val; } defaultComputedStyles[index] = defaultComputedStyle; }); const customStyles = {}; raws.forEach((elem, index) => { const computedStyle = window.getComputedStyle(elem, null); const defaultComputedStyle = defaultComputedStyles[index] || {}; const customStyle = {}; for (let i = 0; i < computedStyle.length; i += 1) { const prop = computedStyle[i]; const val = computedStyle.getPropertyValue(prop); if (val !== defaultComputedStyle[prop]) { customStyle[prop] = val; } } customStyles[index] = customStyle; }); clones.forEach((elem, index) => { Dom.css(elem, customStyles[index]); }); } const stylesheet = options.stylesheet; if (typeof stylesheet === 'string') { const cDATASection = rawSVG .ownerDocument.implementation.createDocument(null, 'xml', null) .createCDATASection(stylesheet); vSVG.prepend(Vector.create('style', { type: 'text/css', }, [cDATASection])); } const format = () => { const beforeSerialize = options.beforeSerialize; if (typeof beforeSerialize === 'function') { const ret = FunctionExt.call(beforeSerialize, this.graph, clonedSVG); if (ret instanceof SVGSVGElement) { clonedSVG = ret; } } const dataUri = new XMLSerializer() .serializeToString(clonedSVG) .replace(/&nbsp;/g, '\u00a0'); this.notify('after:export', options); callback(dataUri); }; if (options.serializeImages) { const deferrals = vSVG.find('image').map((vImage) => { return new Promise((resolve) => { const url = vImage.attr('xlink:href') || vImage.attr('href'); DataUri.imageToDataUri(url, (err, dataUri) => { if (!err && dataUri) { vImage.attr('xlink:href', dataUri); vImage.attr('href', dataUri); } resolve(); }); }); }); Promise.all(deferrals).then(format); } else { format(); } } toDataURL(callback, options) { let viewBox = options.viewBox || this.graph.getContentBBox(); const padding = NumberExt.normalizeSides(options.padding); if (options.width && options.height) { if (padding.left + padding.right >= options.width) { padding.left = padding.right = 0; } if (padding.top + padding.bottom >= options.height) { padding.top = padding.bottom = 0; } } const expanding = new Rectangle(-padding.left, -padding.top, padding.left + padding.right, padding.top + padding.bottom); if (options.width && options.height) { const width = viewBox.width + padding.left + padding.right; const height = viewBox.height + padding.top + padding.bottom; expanding.scale(width / options.width, height / options.height); } viewBox = Rectangle.create(viewBox).moveAndExpand(expanding); const rawSize = typeof options.width === 'number' && typeof options.height === 'number' ? { width: options.width, height: options.height } : viewBox; let scale = options.ratio ? options.ratio : 1; if (!Number.isFinite(scale) || scale === 0) { scale = 1; } const size = { width: Math.max(Math.round(rawSize.width * scale), 1), height: Math.max(Math.round(rawSize.height * scale), 1), }; { const imgDataCanvas = document.createElement('canvas'); const context2D = imgDataCanvas.getContext('2d'); imgDataCanvas.width = size.width; imgDataCanvas.height = size.height; const x = size.width - 1; const y = size.height - 1; context2D.fillStyle = 'rgb(1,1,1)'; context2D.fillRect(x, y, 1, 1); const data = context2D.getImageData(x, y, 1, 1).data; if (data[0] !== 1 || data[1] !== 1 || data[2] !== 1) { throw new Error('size exceeded'); } } const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = size.width; canvas.height = size.height; const context = canvas.getContext('2d'); context.fillStyle = options.backgroundColor || 'white'; context.fillRect(0, 0, size.width, size.height); try { context.drawImage(img, 0, 0, size.width, size.height); const dataUri = canvas.toDataURL(options.type, options.quality); callback(dataUri); } catch (error) { // pass } }; this.toSVG((dataUri) => { img.src = `data:image/svg+xml,${encodeURIComponent(dataUri)}`; }, Object.assign(Object.assign({}, options), { viewBox, serializeImages: true, preserveDimensions: Object.assign({}, size) })); } toPNG(callback, options = {}) { this.toDataURL(callback, Object.assign(Object.assign({}, options), { type: 'image/png' })); } toJPEG(callback, options = {}) { this.toDataURL(callback, Object.assign(Object.assign({}, options), { type: 'image/jpeg' })); } notify(name, args) { this.trigger(name, args); this.graph.trigger(name, args); } dispose() { this.off(); } } __decorate([ disposable() ], Export.prototype, "dispose", null); //# sourceMappingURL=index.js.map