UNPKG

@amcharts/amcharts5

Version:
1,598 lines 60.9 kB
import { __awaiter } from "tslib";
import { Entity } from "../../core/util/Entity";
import { Color } from "../../core/util/Color";
import { Root } from "../../core/Root";
import { StyleRule } from "../../core/util/Utils";
import * as $array from "../../core/util/Array";
import * as $object from "../../core/util/Object";
import * as $type from "../../core/util/Type";
import * as $utils from "../../core/util/Utils";
/**
 * This is used to cache the pdfmake loading.
 *
 * @ignore
 */
let pdfmakePromise;
/**
 * Loads pdfmake dynamic module
 *
 * This is an asynchronous function. Check the description of `getImage()`
 * for description and example usage.
 *
 * @ignore
 */
function _pdfmake() {
    return __awaiter(this, void 0, void 0, function* () {
        let a = yield Promise.all([
            import(/* webpackChunkName: "pdfmake" */ "pdfmake/build/pdfmake.js"),
            import(/* webpackChunkName: "pdfmake" */ "./pdfmake/vfs_fonts")
        ]);
        let pdfmake = a[0].default;
        let vfs_fonts = a[1].default;
        // pdfmake 0.3+ no longer reads a plain `vfs` assignment; the virtual file
        // system must be registered through `addVirtualFileSystem()`. The default
        // Roboto font definition is already set up by pdfmake itself.
        pdfmake.addVirtualFileSystem(vfs_fonts);
        return pdfmake;
    });
}
/**
 * A list of characters that, when a spreadsheet cell value begins with one of
 * them, can cause the cell to be interpreted as a formula by Excel, Google
 * Sheets, LibreOffice, etc. (a.k.a. "formula injection" / "CSV injection").
 *
 * @ignore
 */
const formulaInjectionChars = ["=", "+", "-", "@", "\t", "\r"];
/**
 * A plugin that can be used to export chart snapshots and data.
 *
 * @see {@link https://www.amcharts.com/docs/v5/concepts/exporting/} for more info
 */
export class Exporting extends Entity {
    //public extraImages: Array<Root | IExportingImageSource> = [];
    //public dataSources: any[] = [];
    _afterNew() {
        super._afterNew();
        this._setRawDefault("filePrefix", "chart");
        this._setRawDefault("charset", "utf-8");
        this._setRawDefault("numericFields", []);
        this._setRawDefault("dateFields", []);
        this._setRawDefault("durationFields", []);
        this._setRawDefault("extraImages", []);
        this._setRawDefault("pngOptions", { quality: 1, maintainPixelRatio: false });
        this._setRawDefault("jpgOptions", { quality: 0.8, maintainPixelRatio: false });
        this._setRawDefault("printOptions", { quality: 1, maintainPixelRatio: false, delay: 500, printMethod: "iframe", imageFormat: "png" });
        this._setRawDefault("jsonOptions", { indent: 2, renameFields: true });
        this._setRawDefault("csvOptions", { separator: ",", addColumnNames: true, emptyAs: "", addBOM: true });
        this._setRawDefault("htmlOptions", { emptyAs: "-", addColumnNames: true });
        this._setRawDefault("xlsxOptions", { emptyAs: "", addColumnNames: true });
        this._setRawDefault("pdfOptions", { fontSize: 14, imageFormat: "png", align: "left", addURL: true });
        this._setRawDefault("pdfdataOptions", { emptyAs: "", addColumnNames: true });
        this._root.addDisposer(this);
    }
    _beforeChanged() {
        super._beforeChanged();
        if (this.isDirty("menu")) {
            const menu = this.get("menu");
            if (menu) {
                menu.set("exporting", this);
                this._disposers.push(menu);
            }
        }
    }
    _getFormatOptions(format, options) {
        const newOptions = $object.copy(this.get(format + "Options", {}));
        if (options) {
            $object.each(options, (key, value) => {
                newOptions[key] = value;
            });
        }
        return newOptions;
    }
    /**
     * Triggers a download of the chart/data in specific format.
     *
     * @param  format         Format
     * @param  customOptions  Format options
     */
    download(format, customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const ext = format == "pdfdata" ? "pdf" : format;
            const fileName = this.get("filePrefix", "chart") + "." + ext;
            const options = this._getFormatOptions(format, customOptions);
            this.events.dispatch("downloadstarted", {
                type: "downloadstarted",
                format: format,
                options: options,
                fileName: fileName,
                target: this
            });
            const uri = yield this.export(format, options);
            this.streamFile(uri, fileName, (options && options.addBOM));
        });
    }
    /**
     * Triggers print of the chart.
     *
     * @param  customOptions  Print options
     */
    print(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("print", customOptions);
            this.events.dispatch("printstarted", {
                type: "printstarted",
                format: "print",
                options: options,
                target: this
            });
            const uri = yield this.export(options.imageFormat || "png", options);
            this.initiatePrint(uri, options, this.get("title"));
        });
    }
    /**
     * Returns data uri of the chart/data in specific format.
     *
     * @param          format  Format
     * @param   customOptions  Format options
     * @return                 Promise
     */
    export(format, customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions(format, customOptions);
            this.events.dispatch("exportstarted", {
                type: "exportstarted",
                format: format,
                options: options,
                target: this
            });
            let promise = "";
            switch (format) {
                case "png":
                case "jpg":
                    this._root._runTickerNow();
                    promise = this.exportImage(format, options);
                    break;
                case "json":
                    promise = this.exportJSON(options);
                    break;
                case "csv":
                    promise = this.exportCSV(options);
                    break;
                case "html":
                    promise = this.exportHTML(options);
                    break;
                case "xlsx":
                    promise = this.exportXLSX(options);
                    break;
                case "pdf":
                    this._root._runTickerNow();
                    promise = this.exportPDF(options);
                    break;
                case "pdfdata":
                    promise = this.exportPDFData(options);
                    break;
            }
            this.events.dispatch("exportfinished", {
                type: "exportfinished",
                format: format,
                options: options,
                target: this
            });
            return promise;
        });
    }
    /**
     * ==========================================================================
     * Images
     * ==========================================================================
     */
    /**
     * Returns chart image as a data:uri.
     *
     * @param   format         Image format
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportImage(format, customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions(format, customOptions);
            const canvas = yield this.getCanvas(options);
            const data = canvas.toDataURL(this.getContentType(format), options.quality || 1);
            this.disposeCanvas(canvas);
            return data;
        });
    }
    /**
     * Returns canvas data.
     *
     * @param   customOptions  Image options
     * @return                 Promise
     */
    exportCanvas(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("canvas", customOptions);
            const canvas = yield this.getCanvas(options);
            const data = canvas.toDataURL(this.getContentType("canvas"), options.quality || 1);
            this.disposeCanvas(canvas);
            return data;
        });
    }
    /**
     * Returns a `<canvas>` element with snapshot of the chart.
     *
     * @param   options  Image options
     * @return           Promise
     */
    getCanvas(options) {
        return __awaiter(this, void 0, void 0, function* () {
            const mainCanvas = this._root._renderer.getCanvas(this._root._rootContainer._display, options);
            const extraImages = this.get("extraImages", []);
            // Add other canvases
            let middleLeft = 0;
            let middleTop = 0;
            let middleWidth = mainCanvas.width;
            let middleHeight = mainCanvas.height;
            let extraRight = 0;
            let extraBottom = 0;
            const extras = [];
            $array.each(extraImages, (extraRoot) => {
                // Get that extra
                let extra;
                if (extraRoot instanceof Root) {
                    extra = {
                        source: extraRoot,
                        position: "bottom"
                    };
                }
                else {
                    extra = extraRoot;
                }
                // Set defaults
                extra.position = extra.position || "bottom";
                extra.marginTop = extra.marginTop || 0;
                extra.marginRight = extra.marginRight || 0;
                extra.marginBottom = extra.marginBottom || 0;
                extra.marginLeft = extra.marginLeft || 0;
                const extraCanvas = extra.source._renderer.getCanvas(extra.source._rootContainer._display, options);
                const extraWidth = extraCanvas.width + extra.marginLeft + extra.marginRight;
                const extraHeight = extraCanvas.height + extra.marginTop + extra.marginBottom;
                if (extra.position == "top") {
                    middleWidth = extra.crop ? middleHeight : Math.max(middleWidth, extraWidth);
                    middleTop += extraHeight;
                }
                else if (extra.position == "right") {
                    middleHeight = extra.crop ? middleHeight : Math.max(middleHeight, extraHeight);
                    extraRight += extraWidth;
                }
                else if (extra.position == "left") {
                    middleHeight = extra.crop ? middleHeight : Math.max(middleHeight, extraHeight);
                    middleLeft += extraWidth;
                }
                else if (extra.position === "bottom") {
                    middleWidth = extra.crop ? middleHeight : Math.max(middleWidth, extraWidth);
                    extraBottom += extraHeight;
                }
                extras.push({
                    canvas: extraCanvas,
                    position: extra.position,
                    left: extra.marginLeft,
                    top: extra.marginTop,
                    width: extraWidth,
                    height: extraHeight
                });
            });
            const newCanvas = this.getDisposableCanvas();
            newCanvas.width = middleLeft + middleWidth + extraRight;
            newCanvas.height = middleTop + middleHeight + extraBottom;
            const ctx = newCanvas.getContext("2d");
            // Get background
            const background = this.get("backgroundColor", this.findBackgroundColor(this._root.dom));
            const backgroundOpacity = this.get("backgroundOpacity", 1);
            if (background) {
                ctx.fillStyle = background.toCSS(backgroundOpacity);
                ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
            }
            let left = middleLeft;
            let top = middleTop;
            let right = left + middleWidth;
            let bottom = top + middleHeight;
            // Radiates outwards from center
            $array.each(extras, (extra) => {
                if (extra.position == "top") {
                    top -= extra.height;
                    ctx.drawImage(extra.canvas, middleLeft + extra.left, top + extra.top);
                }
                else if (extra.position == "right") {
                    ctx.drawImage(extra.canvas, right + extra.left, middleTop + extra.top);
                    right += extra.width;
                }
                else if (extra.position == "left") {
                    left -= extra.width;
                    ctx.drawImage(extra.canvas, left + extra.left, middleTop + extra.top);
                }
                else if (extra.position === "bottom") {
                    ctx.drawImage(extra.canvas, middleLeft + extra.left, bottom + extra.top);
                    bottom += extra.height;
                }
                //this.disposeCanvas(extra.canvas);
            });
            ctx.drawImage(mainCanvas, middleLeft, middleTop);
            return newCanvas;
        });
    }
    /**
     * ==========================================================================
     * JSON
     * ==========================================================================
     */
    /**
     * Returns a data:uri representation of a JSON file with chart data.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportJSON(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return "data:" + this.getContentType("json") + ";" + this.get("charset", "utf-8") + "," + encodeURIComponent(yield this.getJSON(customOptions));
        });
    }
    /**
     * Returns data in JSON format.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    getJSON(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("json", customOptions);
            return JSON.stringify(this.getData("json", customOptions, options.renameFields), (_key, value) => {
                if ($type.isObject(value)) {
                    $object.each(value, (field, item) => {
                        value[field] = this.convertToSpecialFormat(field, item, options);
                    });
                }
                return value;
            }, options.indent);
        });
    }
    /**
     * ==========================================================================
     * CSV
     * ==========================================================================
     */
    /**
     * Returns a data:uri representation of a CSV file with chart data.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportCSV(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return "data:" + this.getContentType("csv") + ";" + this.get("charset", "utf-8") + "," + encodeURIComponent(yield this.getCSV(customOptions));
        });
    }
    /**
     * Returns a CSV with export data.
     *
     * @param   customOptions  CSV options
     * @return                 Promise
     */
    getCSV(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("csv", customOptions);
            // Init output
            let csv = "";
            // Add rows
            let br = "";
            const data = this.getData("csv", options);
            // Data fields
            const dataFields = this.getDataFields(data);
            // Vertical or horizontal (default) layout
            if (options.pivot) {
                // Data fields order
                const dataFieldsOrder = this.get("dataFieldsOrder", []);
                $object.eachOrdered(dataFields, (key, val) => {
                    let dataRow = [];
                    if (options.addColumnNames) {
                        dataRow.push(val);
                    }
                    for (let len = data.length, i = 0; i < len; i++) {
                        let dataValue = data[i][key];
                        dataRow.push(this.convertToSpecialFormat(key, dataValue, options, true));
                    }
                    csv += br + this.getCSVRow(dataRow, options, undefined, true);
                    br = "\n";
                }, (a, b) => {
                    let ai = dataFieldsOrder.indexOf(a);
                    let bi = dataFieldsOrder.indexOf(b);
                    if (ai > bi) {
                        return -1;
                    }
                    else if (ai < bi) {
                        return 1;
                    }
                    return 0;
                });
            }
            else {
                for (let len = data.length, i = 0; i < len; i++) {
                    let row = this.getCSVRow(data[i], options, dataFields);
                    if (options.reverse) {
                        csv = row + br + csv;
                    }
                    else {
                        csv += br + row;
                    }
                    br = "\n";
                }
                // Add column names?
                if (options.addColumnNames) {
                    csv = this.getCSVRow(dataFields, options, undefined, true) + br + csv;
                }
            }
            return csv;
        });
    }
    /**
     * @ignore
     */
    getCSVRow(row, options, dataFields, asIs = false) {
        // Init
        let separator = options.separator || ",";
        let items = [];
        // Data fields
        if (!dataFields) {
            dataFields = {};
            $object.each(row, (key, value) => {
                dataFields[key] = value;
            });
        }
        // Data fields order
        const dataFieldsOrder = this.get("dataFieldsOrder", []);
        // Process each row item
        $object.eachOrdered(dataFields, (key, _name) => {
            // Get value
            let value = this.convertEmptyValue(key, row[key], options);
            // Check if we need to skip
            // This is no longer required because we are iterating via dataFields anyway
            /*if (this.dataFields != null && this.dataFields[key] == null) {
                return;
            }*/
            // Convert dates
            let item = asIs ? value : this.convertToSpecialFormat(key, value, options);
            // Neutralize spreadsheet formula injection
            if (options.escapeFormulas !== false) {
                item = this.escapeFormulas(item);
            }
            // Cast and escape doublequotes
            item = "" + item;
            item = item.replace(/"/g, '""');
            // Enclose into double quotes
            if (options.forceQuotes || (item.search(new RegExp("\"|\n|" + separator, "g")) >= 0)) {
                item = "\"" + item + "\"";
            }
            // Add to item
            items.push(item);
        }, (a, b) => {
            let ai = dataFieldsOrder.indexOf(a);
            let bi = dataFieldsOrder.indexOf(b);
            if (ai > bi) {
                return 1;
            }
            else if (ai < bi) {
                return -1;
            }
            return 0;
        });
        return items.join(separator);
    }
    /**
     * ==========================================================================
     * HTML
     * ==========================================================================
     */
    /**
     * Returns a data:uri representation of an HTML file with chart data.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportHTML(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return "data:" + this.getContentType("html") + ";" + this.get("charset", "utf-8") + "," + encodeURIComponent(yield this.getHTML(customOptions));
        });
    }
    /**
     * Returns an HTML with a table with export data.
     *
     * @param   customOptions  HTML options
     * @return                 Promise
     */
    getHTML(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("html", customOptions);
            // Init output
            let html = "<table>";
            if (options.tableClass) {
                html = "<table class=\"" + options.tableClass + "\">";
            }
            // Get data
            const data = this.getData("html", options);
            const dataFields = this.getDataFields(data);
            // Vertical or horizontal (default) layout
            if (options.pivot) {
                // Data fields order
                const dataFieldsOrder = this.get("dataFieldsOrder", []);
                html += "\n<tbody>";
                $object.eachOrdered(dataFields, (key, val) => {
                    let dataRow = [];
                    if (options.addColumnNames) {
                        dataRow.push(val);
                    }
                    for (let len = data.length, i = 0; i < len; i++) {
                        let dataValue = data[i][key];
                        dataRow.push(this.convertToSpecialFormat(key, dataValue, options, true));
                    }
                    html += "\n" + this.getHTMLRow(dataRow, options, undefined, true);
                }, (a, b) => {
                    let ai = dataFieldsOrder.indexOf(a);
                    let bi = dataFieldsOrder.indexOf(b);
                    if (ai > bi) {
                        return -1;
                    }
                    else if (ai < bi) {
                        return 1;
                    }
                    return 0;
                });
                html += "\n</tbody>";
            }
            else {
                // Add column names?
                if (options.addColumnNames) {
                    html += "\n<thead>\n" + this.getHTMLRow(dataFields, options, undefined, true, true) + "\n</thead>";
                }
                html += "\n<tbody>";
                for (let len = data.length, i = 0; i < len; i++) {
                    html += "\n" + this.getHTMLRow(data[i], options, dataFields);
                }
                html += "\n</tbody>";
            }
            html += "\n</table>";
            return html;
        });
    }
    /**
     * @ignore
     */
    getHTMLRow(row, options, dataFields, asIs = false, headerRow = false) {
        // Init output
        let html = "\t<tr>";
        if (options.rowClass) {
            html = "\t<tr class=\"" + options.rowClass + "\">";
        }
        // Data fields
        if (!dataFields) {
            dataFields = row;
        }
        // Data fields order
        const dataFieldsOrder = this.get("dataFieldsOrder", []);
        // th or dh?
        const tag = headerRow ? "th" : "td";
        // Process each row item
        let first = true;
        $object.eachOrdered(dataFields, (key, _name) => {
            // Get value
            let value = this.convertEmptyValue(key, row[key], options);
            // Convert dates
            let item = asIs ? value : this.convertToSpecialFormat(key, value, options);
            // Escape HTML entities
            item = "" + item;
            item = item.replace(/[\u00A0-\u9999<>\&]/gim, function (i) {
                return "&#" + i.charCodeAt(0) + ";";
            });
            // Which tag to use
            let useTag = tag;
            if (options.pivot && first) {
                useTag = "th";
            }
            // Add cell
            if (options.cellClass) {
                html += "\n\t\t<" + useTag + " class=\"" + options.cellClass + "\">" + item + "</" + useTag + ">";
            }
            else {
                html += "\n\t\t<" + useTag + ">" + item + "</" + useTag + ">";
            }
            first = false;
        }, (a, b) => {
            let ai = dataFieldsOrder.indexOf(a);
            let bi = dataFieldsOrder.indexOf(b);
            if (ai > bi) {
                return 1;
            }
            else if (ai < bi) {
                return -1;
            }
            return 0;
        });
        html += "\n\t</tr>";
        return html;
    }
    /**
     * ==========================================================================
     * XLSX
     * ==========================================================================
     */
    /**
     * Returns a data:uri representation of an XLSX file with chart data.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportXLSX(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return "data:" + this.getContentType("xlsx") + ";" + this.get("charset", "utf-8") + "," + encodeURIComponent(yield this.getXLSX(customOptions));
        });
    }
    /**
     * Returns a data:uri of XLSX data.
     *
     * @param  customOptions  Format options
     * @return                Promise
     */
    getXLSX(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("xlsx", customOptions);
            // Load xlsx
            let XLSX = yield this.getXLSXLib();
            // Create workbook options
            let wbOptions = {
                bookType: "xlsx",
                bookSST: false,
                type: "base64",
                //dateNF: 'yyyy-mm-dd'
            };
            // Get sheet name
            let sheetName = this._normalizeExcelSheetName(this.get("title", this._t("Data")));
            // Create a workbook
            let wb = {
                SheetNames: [sheetName],
                Sheets: {}
            };
            // Init worksheet data
            let wsData = [];
            // Get data
            const data = this.getData("html", options);
            const dataFields = this.getDataFields(data);
            // Vertical or horizontal (default) layout
            if (options.pivot) {
                // Data fields order
                const dataFieldsOrder = this.get("dataFieldsOrder", []);
                $object.eachOrdered(dataFields, (key, val) => {
                    let dataRow = [];
                    if (options.addColumnNames) {
                        dataRow.push(val);
                    }
                    for (let len = data.length, i = 0; i < len; i++) {
                        let dataValue = data[i][key];
                        dataRow.push(this.convertToSpecialFormat(key, dataValue, options, true));
                    }
                    wsData.push(this.getXLSXRow(dataRow, options, undefined, true));
                }, (a, b) => {
                    let ai = dataFieldsOrder.indexOf(a);
                    let bi = dataFieldsOrder.indexOf(b);
                    if (ai > bi) {
                        return 1;
                    }
                    else if (ai < bi) {
                        return -1;
                    }
                    return 0;
                });
            }
            else {
                // Add column names?
                if (options.addColumnNames) {
                    wsData.push(this.getXLSXRow(dataFields, options, undefined, true));
                }
                // Add lines
                for (let len = data.length, i = 0; i < len; i++) {
                    wsData.push(this.getXLSXRow(data[i], options, dataFields));
                }
            }
            // Create sheet and add data
            wb.Sheets[sheetName] = XLSX.utils.aoa_to_sheet(wsData);
            this.events.dispatch("workbookready", {
                type: "workbookready",
                format: "xlsx",
                options: options,
                workbook: wb,
                workbookOptions: wbOptions,
                xlsx: XLSX,
                target: this
            });
            return XLSX.write(wb, wbOptions);
        });
    }
    _normalizeExcelSheetName(name) {
        name = name.replace(/([:\\\/?*\[\]]+)/g, " ");
        return name.length > 30 ? name.substr(0, 30) + "..." : name;
    }
    /**
     * @ignore
     */
    getXLSXRow(row, options, dataFields, asIs = false) {
        // Init
        let items = [];
        // Data fields
        if (!dataFields) {
            dataFields = row;
        }
        // Data fields order
        const dataFieldsOrder = this.get("dataFieldsOrder", []);
        // Process each row item
        $object.eachOrdered(dataFields, (key, _name) => {
            // Get value
            let value = this.convertEmptyValue(key, row[key], options);
            // Convert dates
            let item = asIs ? value : this.convertToSpecialFormat(key, value, options, true);
            // Neutralize spreadsheet formula injection
            if (options.escapeFormulas !== false) {
                item = this.escapeFormulas(item);
            }
            items.push(item);
        }, (a, b) => {
            let ai = dataFieldsOrder.indexOf(a);
            let bi = dataFieldsOrder.indexOf(b);
            if (ai > bi) {
                return 1;
            }
            else if (ai < bi) {
                return -1;
            }
            return 0;
        });
        return items;
    }
    /**
     * @ignore
     */
    _xlsx() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield import(/* webpackChunkName: "xlsx" */ "../../bundled/xlsx");
        });
    }
    /**
     * @ignore
     */
    getXLSXLib() {
        return this._xlsx();
    }
    /**
     * ==========================================================================
     * PDF
     * ==========================================================================
     */
    /**
     * Returns a data:uri representation of a PDF file with chart image.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportPDF(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return "data:" + this.getContentType("pdf") + ";" + this.get("charset", "utf-8") + "," + encodeURIComponent(yield this.getPDF(customOptions, true));
        });
    }
    /**
     * Returns a data:uri representation of a PDF file with chart data.
     *
     * @param   customOptions  Format options
     * @return                 Promise
     */
    exportPDFData(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            return "data:" + this.getContentType("pdf") + ";" + this.get("charset", "utf-8") + "," + encodeURIComponent(yield this.getPDF(customOptions, false, true));
        });
    }
    /**
     * Returns Base64-encoded binary data for a PDF file.
     * @param   customOptions  PDF options
     * @param   includeImage   Include chart snapshot
     * @param   includeData    Include data
     * @return                 Promise
     */
    getPDF(customOptions, includeImage = true, includeData = false) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("pdf", customOptions);
            const dataOptions = this._getFormatOptions("pdfdata", customOptions);
            const orientation = options.pageOrientation || "portrait";
            // Get image
            let image;
            const imageSize = {
                width: 0,
                height: 0
            };
            if (includeImage) {
                const imageFormat = options.imageFormat || "png";
                const imageOptions = this._getFormatOptions(imageFormat, options);
                const canvas = yield this.getCanvas(imageOptions);
                imageSize.width = canvas.clientWidth;
                imageSize.height = canvas.clientHeight;
                image = canvas.toDataURL(this.getContentType(imageFormat), options.quality || 1);
                this.disposeCanvas(canvas);
            }
            // Load pdfmake
            const pdfmake = yield this.getPdfmake();
            // Defaults
            const defaultMargins = [30, 30, 30, 30];
            // Init content for PDF
            let doc = {
                pageSize: options.pageSize || "A4",
                pageOrientation: orientation,
                pageMargins: options.pageMargins || defaultMargins,
                defaultStyle: {
                    font: options.font ? options.font.name : undefined
                },
                //header: <any>[],
                content: []
            };
            // Should we add title?
            const title = this.get("title");
            let extraMargin = 0;
            if (title) {
                doc.content.push({
                    text: title,
                    fontSize: options.fontSize || 14,
                    bold: true,
                    margin: [0, 0, 0, 15]
                });
                // Add some leftover margin for title
                extraMargin += 50;
            }
            // Add page URL?
            if (options.addURL) {
                doc.content.push({
                    text: this._t("Saved from") + ": " + document.location.href,
                    fontSize: options.fontSize,
                    margin: [0, 0, 0, 15]
                });
                // Add some leftover margin for URL
                extraMargin += 50;
            }
            // Add image
            if (includeImage && image) {
                const fitSize = this.getPageSizeFit(doc.pageSize, doc.pageMargins, extraMargin, orientation);
                if ((imageSize.width > fitSize[0]) || (imageSize.height > fitSize[1])) {
                    doc.content.push({
                        image: image,
                        alignment: options.align || "left",
                        fit: fitSize
                    });
                }
                else {
                    doc.content.push({
                        image: image,
                        alignment: options.align || "left"
                    });
                }
            }
            // Add data
            if ((includeData || options.includeData) && this.hasData()) {
                doc.content.push({
                    table: yield this.getPDFData(dataOptions),
                    fontSize: options.fontSize || 14
                });
            }
            let fonts = null;
            let vfs = null;
            function addFont(font) {
                const paths = {};
                paths.normal = font.normal.path;
                vfs[font.normal.path] = font.normal.bytes;
                if (font.bold) {
                    paths.bold = font.bold.path;
                    vfs[font.bold.path] = font.bold.bytes;
                }
                else {
                    paths.bold = font.normal.path;
                }
                if (font.italics) {
                    paths.italics = font.italics.path;
                    vfs[font.italics.path] = font.italics.bytes;
                }
                else {
                    paths.italics = font.normal.path;
                }
                if (font.bolditalics) {
                    paths.bolditalics = font.bolditalics.path;
                    vfs[font.bolditalics.path] = font.bolditalics.bytes;
                }
                else {
                    paths.bolditalics = font.normal.path;
                }
                fonts[font.name] = paths;
            }
            if (options.font) {
                fonts = {};
                vfs = {};
                addFont(options.font);
                if (options.extraFonts) {
                    $array.each(options.extraFonts, addFont);
                }
                // In pdfmake 0.3+ custom fonts and their files are registered on the
                // instance rather than passed to `createPdf()`. Merge them onto the
                // existing defaults so subsequent exports keep working.
                pdfmake.addFonts(fonts);
                pdfmake.addVirtualFileSystem(vfs);
            }
            this.events.dispatch("pdfdocready", {
                type: "pdfdocready",
                format: "pdf",
                options: options,
                doc: doc,
                target: this
            });
            // Create PDF
            return yield pdfmake.createPdf(doc, {}).getBase64();
        });
    }
    /**
     * @ignore
     */
    getPDFData(customOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const options = this._getFormatOptions("pdfdata", customOptions);
            // Init output
            let content = {
                "body": []
            };
            // Get data
            const data = this.getData("html", options);
            const dataFields = this.getDataFields(data);
            // Vertical or horizontal (default) layout
            if (options.pivot) {
                // Data fields order
                const dataFieldsOrder = this.get("dataFieldsOrder", []);
                $object.eachOrdered(dataFields, (key, val) => {
                    let dataRow = [];
                    if (options.addColumnNames) {
                        dataRow.push(val);
                    }
                    for (let len = data.length, i = 0; i < len; i++) {
                        let dataValue = data[i][key];
                        dataRow.push(this.convertToSpecialFormat(key, dataValue, options, true));
                    }
                    content.body.push(this.getPDFDataRow(dataRow, options, undefined, true));
                }, (a, b) => {
                    let ai = dataFieldsOrder.indexOf(a);
                    let bi = dataFieldsOrder.indexOf(b);
                    if (ai > bi) {
                        return 1;
                    }
                    else if (ai < bi) {
                        return -1;
                    }
                    return 0;
                });
            }
            else {
                // Add column names?
                if (options.addColumnNames) {
                    content.body.push(this.getPDFDataRow(dataFields, options, undefined, true));
                    content.headerRows = 1;
                }
                for (let len = data.length, i = 0; i < len; i++) {
                    content.body.push(this.getPDFDataRow(data[i], options, dataFields));
                }
            }
            return content;
        });
    }
    /**
     * @ignore
     */
    getPDFDataRow(row, options, dataFields, asIs = false) {
        // Init
        let items = [];
        // Data fields
        if (!dataFields) {
            dataFields = row;
        }
        // Data fields order
        const dataFieldsOrder = this.get("dataFieldsOrder", []);
        // Process each row item
        $object.eachOrdered(dataFields, (key, _name) => {
            // Get value
            let value = this.convertEmptyValue(key, row[key], options);
            // Convert dates
            let item = asIs ? value : this.convertToSpecialFormat(key, value, options);
            item = "" + item;
            // Add to item
            items.push(item);
        }, (a, b) => {
            let ai = dataFieldsOrder.indexOf(a);
            let bi = dataFieldsOrder.indexOf(b);
            if (ai > bi) {
                return 1;
            }
            else if (ai < bi) {
                return -1;
            }
            return 0;
        });
        return items;
    }
    /**
     * Returns pdfmake instance.
     *
     * @ignore
     * @return Instance of pdfmake
     */
    getPdfmake() {
        if (pdfmakePromise === undefined) {
            pdfmakePromise = _pdfmake();
        }
        return pdfmakePromise;
    }
    /**
     * @ignore
     */
    getPageSizeFit(pageSize, margins, extraMargin = 0, orientation = "portrait") {
        // Check margins
        let newMargins = [0, 0, 0, 0];
        if ($type.isNumber(margins)) {
            newMargins = [margins, margins, margins, margins];
        }
        else if (margins.length == 2) {
            newMargins = [margins[0], margins[1], margins[0], margins[1]];
        }
        else if (margins.length == 4) {
            newMargins = margins;
        }
        // Define available page sizes
        let sizes = {
            "4A0": [4767.87, 6740.79],
            "2A0": [3370.39, 4767.87],
            A0: [2383.94, 3370.39],
            A1: [1683.78, 2383.94],
            A2: [1190.55, 1683.78],
            A3: [841.89, 1190.55],
            A4: [595.28, 841.89],
            A5: [419.53, 595.28],
            A6: [297.64, 419.53],
            A7: [209.76, 297.64],
            A8: [147.40, 209.76],
            A9: [104.88, 147.40],
            A10: [73.70, 104.88],
            B0: [2834.65, 4008.19],
            B1: [2004.09, 2834.65],
            B2: [1417.32, 2004.09],
            B3: [1000.63, 1417.32],
            B4: [708.66, 1000.63],
            B5: [498.90, 708.66],
            B6: [354.33, 498.90],
            B7: [249.45, 354.33],
            B8: [175.75, 249.45],
            B9: [124.72, 175.75],
            B10: [87.87, 124.72],
            C0: [2599.37, 3676.54],
            C1: [1836.85, 2599.37],
            C2: [1298.27, 1836.85],
            C3: [918.43, 1298.27],
            C4: [649.13, 918.43],
            C5: [459.21, 649.13],
            C6: [323.15, 459.21],
            C7: [229.61, 323.15],
            C8: [161.57, 229.61],
            C9: [113.39, 161.57],
            C10: [79.37, 113.39],
            RA0: [2437.80, 3458.27],
            RA1: [1729.13, 2437.80],
            RA2: [1218.90, 1729.13],
            RA3: [864.57, 1218.90],
            RA4: [609.45, 864.57],
            SRA0: [2551.18, 3628.35],
            SRA1: [1814.17, 2551.18],
            SRA2: [1275.59, 1814.17],
            SRA3: [907.09, 1275.59],
            SRA4: [637.80, 907.09],
            EXECUTIVE: [521.86, 756.00],
            FOLIO: [612.00, 936.00],
            LEGAL: [612.00, 1008.00],
            LETTER: [612.00, 792.00],
            TABLOID: [792.00, 1224.00]
        };
        // Calculate size
        let fitSize = sizes[pageSize];
        if (orientation == "landscape") {
            fitSize.reverse();
        }
        fitSize[0] -= newMargins[0] + newMargins[2];
        fitSize[1] -= newMargins[1] + newMargins[3] + extraMargin;
        return fitSize;
    }
    /**
     * ==========================================================================
     * Data
     * ==========================================================================
     */
    /**
        * Returns `true` if `dataSource` is set, and the contents are proper
        * data (array).
        *
        * @return Has data?
        */
    hasData() {
        const dataSource = this.get("dataSource");
        return $type.isArray(dataSource) && dataSource.length ? true : false;
    }
    /**
     * Returns processed data according to format options.
     *
     * @param   format         Format
     * @param   customOptions  Format options
     * @param   renameFields   Should fields be renamed?
     * @return                 Processed data
     */
    getData(format, customOptions, renameFields = false) {
        const options = this._getFormatOptions(format, customOptions);
        const dataSource = this.get("dataSource", []);
        let data = dataSource;
        // Re-generate the data based on data fields if set
        const dataFields = this.get("dataFields");
        if (dataFields && $type.isArray(dataSource)) {
            data = [];
            $array.each(dataSource, (row) => {
                if ($type.isObject(row)) {
                    const newRow = {};
                    $object.each(dataFields, (field, value) => {
                        if (dataFields[field] != null) {
                            newRow[renameFields ? value : field] = this.convertToSpecialFormat(field, row[field], options);
                        }
                    });
                    data.push(newRow);
                }
            });
        }
        const event = {
            type: "dataprocessed",
            format: format,
            options: options,
            data: data,
            target: this
        };
        this.events.dispatch("dataprocessed", event);
        return event.data;
    }
    /**
     * @ignore
     */
    getDataFields(data) {
        let dataFields = this.get("dataFields");
        if (!dataFields) {
            dataFields = {};
            if ($type.isArray(data) && data.length) {
                $array.each(data, (row) => {
                    $object.each(row, (key, _value) => {
                        if (dataFields[key] == null) {
                            dataFields[key] = key;
                        }
                    });
                });
            }
        }
        return dataFields;
    }
    /**
     * @ignore
     */
    convertEmptyValue(_field, value, options) {
        return value != null ? value : options.emptyAs;
    }
    /**
     * @ignore
     */
    convertToSpecialFormat(field, value, options, keepOriginal) {
        // Is this a timestamp or duration?
        if (typeof value == "number") {
            if (this.isDateField(field)) {
                value = new Date(value);
            }
            else if (this.isNumericField(field) && this.get("numberFormat")) {
                return this._root.numberFormatter.format(value, this.get("numberFormat"));
            }
            else if (this.isDurationField(field)) {
                return this._root.durationFormatter.format(value, this.get("durationFormat"), this.get("durationUnit"));
            }
        }
        if (value instanceof Date) {
            if (options.useTimestamps) {
                value = value.getTime();
            }
            else if (options.useLocale) {
                if (!keepOriginal) {
                    value = value.toLocaleString();
                }
            }
            else {
                value = this._root.dateFormatter.format(value, this.get("dateFormat"));
            }
        }
        return value;
    }
    /**
     * If `value` is a string that begins with a character a spreadsheet
     * application could treat as the start of a formula, prefixes it with a
     * single quote so it is imported as plain text. This mitigates spreadsheet
     * formula injection ("CSV injection") attacks via untrusted cell data.
     *
     * Non-string values (numbers, dates) are returned unchanged, so legitimate
     * signed numbers are not affected.
     *
     * @ignore
     */
    escapeFormulas(value) {
        if (typeof value == "string" && value.length > 0 && formulaInjectionChars.indexOf(value.charAt(0)) !== -1) {
            return "'" + value;
        }
        return value;
    }
    /**
     * @ignore
     */
    isDateField(field) {
        return this.get("dateFields").indexOf(field) !== -1;
    }
    /**
     * @ignore
     */
    isNumericField(field) {
        return this.get("numericFields").indexOf(field) !== -1;
    }
    /**
     * @ignore
     */
    isDurationField(field) {
        return this.get("durationFields").indexOf(field) !== -1;
    }
    /**
     * @ignore
     */
    getContentType(type) {
        let contentType = "";
        switch (type) {
            case "png":
                contentType = "image/" + type;
                break;
            case "jpg":
                contentType = "image/jpeg";
                break;
            case "csv":
                contentType = "text/csv";
                break;
            case "json":
                contentType = "application/json";
                break;
            case "html":
                contentType = "text/html";
                break;
            case "pdf":
            case "pdfdata":
                contentType = "application/pdf";
                break;
            case "xlsx":
                contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                break;
            default:
                contentType = "application/octet-stream";
        }
        return contentType;
    }
    getDisposableCanvas() {
        let canvas = document.createElement("canvas");
        canvas.style.position = "fixed";
        canvas.style.top = "-10000px";
        this._root.dom.appendChild(canvas);
        return canvas;
    }
    disposeCanvas(canvas) {
        this._root.dom.removeChild(canvas);
    }
    /**
     * @ignore
     */
    findBackgroundColor(element) {
        // Check if element has styles set
        let opacity = 1;
        let currentColor = getComputedStyle(element, "background-color").getPropertyValue("background-color");
        // Check opacity
        if (currentColor.match(/[^,]*,[^,]*,[^,]*,[ ]?0/) || currentColor == "transparent") {
            opacity = 0;
        }
        if (opacity == 0) {
            let parent = element.parentElement; // || <Element>element.parentNode;
            // Completely transparent. Look for a parent
            if (parent) {
                return this.findBackgroundColor(parent);
            }
            else {
                return Color.fromHex(0xffffff);
            }
        }
        else {
            return Color.fromCSS(currentColor);
        }
    }
    /**
     * Triggers download of the file.
     *
     * @param   uri       data:uri with file content
     * @param   fileName  File name
     * @param   addBOM    Should download include byte order mark?
     * @return            Promise
     */
    streamFile(uri, fileName, addBOM = false) {
        if (this.blobDownloadSupport()) {
            /**
             * Supports Blob object.
             * Use it.
             */
            let link = document.createElement("a");
            link.download = fileName;
            document.body.appendChild(link);
            // Extract content type and get pure data without headers
            let parts = uri.split(";");
            let contentType = parts.shift().replace(/data:/, "");
            uri = decodeURIComponent(parts.join(";").replace(/^[^,]*,/, ""));
            if (["image/svg+xml", "application/json", "text/csv", "text/html"].indexOf(contentType) == -1) {
                try {
                    let decoded = atob(uri);
                    uri = decoded;
                }
                catch (e) {
                    // Error occurred, meaning string was not Base64-encoded. Do nothing.
                    return false;
                }
            }
            else {
                if (addBOM) {
                    uri = "\ufeff" + uri;
                }
                let blob = new Blob([uri], { type: contentType });
                let url = window.URL.createObjectURL(blob);
                link.href = url;
                link.download = fileName;
                link.click();
                setTimeout(() => {
                    document.body.removeChild(link);
                    window.URL.revokeObjectURL(url);
                }, 100);
                return true;
            }
            // Dissect uri into array
            let chars = new Array(uri.length);
            for (let i = 0; i < uri.length; ++i) {
                let charCode = uri.charCodeAt(i);
                chars[i] = charCode;
            }
            if (addBOM) {
                chars = [0xEF, 0xBB, 0xBF].concat(chars);
            }
            let blob = new Blob([new Uint8Array(chars)], { type: contentType });
            let url = window.URL.createObjectURL(blob);
            link.href = url;
            link.download = fileName;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            setTimeout(() => {
                window.URL.revokeObjectURL(url);
            }, 100);
        }
        else if (this.linkDownloadSupport()) {
            /**
             * For regular browsers, we create a link then simulate a click on it
             */
            let link = document.createElement("a");
            link.download = fileName;
            link.href = uri;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
        else {
            /**
             * Something else - perhaps a mobile.
             * Let's just display it in the same page.
             * (hey we don't like it either)
             */
            window.location.href = uri;
        }
        return true;
    }
    /**
     * @ignore
     */
    downloadSupport() {
        return this.linkDownloadSupport();
    }
    /**
     * @ignore
     */
    linkDownloadSupport() {
        let a = document.createElement("a");
        let res = typeof a.download !== "undefined";
        return res;
    }
    /**
     * @ignore
     */
    blobDownloadSupport() {
        return window.Blob != null;
    }
    /**
     * ==========================================================================
     * Print
     * ==========================================================================
     */
    /**
     * Initiates print of the chart.
     *
     * @param   data     data:uri for the image
     * @param   options  Options
     * @param   title    Optional title to use (uses window's title by default)
     * @return           Promise
     */
    initiatePrint(data, customOptions, title) {
        const options = this._getFormatOptions("print", customOptions);
        if (options.printMethod == "css") {
            this._printViaCSS(data, options, title);
        }
        else {
            this._printViaIframe(data, options, title);
        }
    }
    _printViaCSS(data, customOptions, title) {
        const options = this._getFormatOptions("print", customOptions);
        let delay = options.delay || 500;
        //Save current scroll position
        let scroll = document.documentElement.scrollTop || document.body.scrollTop;
        // Hide all document nodes by applying custom CSS
        let rule = new StyleRule($utils.getShadowRoot(this._root.dom), "body > *", {
            "display": "none",
            "position": "fixed",
            "visibility": "hidden",
            "opacity": "0",
            "clipPath": "polygon(0px 0px,0px 0px,0px 0px,0px 0px);"
        }, this._root.nonce);
        let rule2 = new StyleRule($utils.getShadowRoot(this._root.dom), "body", {
            "padding": "0",
            "margin": "0"
        }, this._root.nonce);
        // Replace title?
        let originalTitle;
        if (title && document && document.title) {
            originalTitle = document.title;
            document.title = title;
        }
        // Create and add exported image
        let img = new Image();
        img.src = data;
        img.style.maxWidth = "100%";
        img.style.display = "block";
        img.style.position = "relative";
        img.style.visibility = "visible";
        img.style.opacity = "1";
        img.style.clipPath = "none";
        document.body.appendChild(img);
        // Print
        this.setTimeout(() => {
            window.print();
        }, 50);
        // Delay needs to be at least a second for iOS devices
        let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
        if (isIOS && (delay < 1000)) {
            delay = 1000;
        }
        else if (delay < 100) {
            delay = 100;
        }
        // Delay function that resets back the document the way ot was before
        this.setTimeout(() => {
            // Remove image
            document.body.removeChild(img);
            // Reset back all elements
            rule.dispose();
            rule2.dispose();
            // Restore title
            if (originalTitle) {
                document.title = document.title;
            }
            // Scroll back the document the way it was before
            document.documentElement.scrollTop = document.body.scrollTop = scroll;
        }, delay || 500);
    }
    _printViaIframe(data, customOptions, title) {
        const options = this._getFormatOptions("print", customOptions);
        let delay = options.delay || 500;
        // Create an iframe
        const iframe = document.createElement("iframe");
        iframe.style.visibility = "hidden";
        document.body.appendChild(iframe);
        // This is needed for FireFox
        iframe.contentWindow.document.open();
        iframe.contentWindow.document.close();
        // Create and add exported image
        let img = new Image();
        img.src = data;
        img.style.maxWidth = "100%";
        img.style.height = "auto";
        if (title) {
            iframe.contentWindow.document.title = title;
        }
        iframe.contentWindow.document.body.appendChild(img);
        iframe.load = function () {
            iframe.contentWindow.document.body.appendChild(img);
        };
        // Print
        this.setTimeout(() => {
            try {
                if (!iframe.contentWindow.document.execCommand("print", false, null)) {
                    iframe.contentWindow.print();
                }
            }
            catch (e) {
                iframe.contentWindow.print();
            }
        }, delay || 50);
        // Delay needs to be at least a second for iOS devices
        let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
        if (isIOS && (delay < 1000)) {
            delay = 1000;
        }
        else if (delay < 100) {
            delay = 100;
        }
        // Delay function that resets back the document the way ot was before
        this.setTimeout(() => {
            // Remove image
            document.body.removeChild(iframe);
        }, delay + 50 || 100);
    }
    /**
     * Returns a list of formats that can be exported in current browser.
     *
     * @return Formats
     */
    supportedFormats() {
        const res = [];
        const hasData = this.hasData();
        const downloadSupport = this.downloadSupport();
        $array.each(["png", "jpg", "canvas", "pdf", "xlsx", "csv", "json", "html", "pdfdata", "print"], (format) => {
            const options = this._getFormatOptions(format);
            if (options.disabled !== true) {
                if (["xlsx", "csv", "json", "html", "pdfdata"].indexOf(format) == -1 || (hasData && downloadSupport)) {
                    res.push(format);
                }
            }
        });
        return res;
    }
    /**
     * Returns a list of supported export types: image or print.
     *
     * @return Supported types
     */
    supportedExportTypes() {
        const res = ["image", "print"];
        if (this.downloadSupport() && this.hasData()) {
            res.push("data");
        }
        return res;
    }
}
Exporting.className = "Exporting";
Exporting.classNames = Entity.classNames.concat([Exporting.className]);
//# sourceMappingURL=Exporting.js.map