@toast-ui/chart
Version:
TOAST UI Application: Chart
288 lines (287 loc) • 12.3 kB
JavaScript
import { isString, isUndefined, isNumber, includes, isNull, range, getFirstValidValue, } from "./utils";
import { getCoordinateXValue, getCoordinateYValue } from "./coordinate";
const DATA_URI_HEADERS = {
xls: 'data:application/vnd.ms-excel;base64,',
csv: 'data:text/csv;charset=utf-8,%EF%BB%BF' /* BOM for utf-8 */,
};
function getDownloadMethod() {
let method;
const isDownloadAttributeSupported = !isUndefined(document.createElement('a').download);
const isMSSaveOrOpenBlobSupported = !isUndefined(window.Blob && window.navigator.msSaveOrOpenBlob);
if (isMSSaveOrOpenBlobSupported) {
method = downloadWithMSSaveOrOpenBlob;
}
else if (isDownloadAttributeSupported) {
method = downloadWithAnchorElementDownloadAttribute;
}
return method;
}
/**
* Base64 string to blob
* original source ref: https://github.com/miguelmota/base64toblob/blob/master/base64toblob.js
* Licence: MIT Licence
*/
function base64toBlob(base64String) {
const contentType = base64String
.substr(0, base64String.indexOf(';base64,'))
.substr(base64String.indexOf(':') + 1);
const sliceSize = 1024;
const byteCharacters = atob(base64String.substr(base64String.indexOf(',') + 1));
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i += 1) {
byteNumbers[i] = slice.charCodeAt(i);
}
byteArrays.push(new window.Uint8Array(byteNumbers));
}
try {
// for IE 11
return new Blob(byteArrays, { type: contentType });
}
catch (e) {
// for IE 10
return new Blob(byteArrays.map((byteArr) => byteArr.buffer), { type: contentType });
}
}
function isImageExtension(extension) {
return extension === 'jpeg' || extension === 'png';
}
function downloadWithMSSaveOrOpenBlob(fileName, extension, content, contentType) {
const blobObject = isImageExtension(extension)
? base64toBlob(content)
: new Blob([content], { type: contentType });
window.navigator.msSaveOrOpenBlob(blobObject, `${fileName}.${extension}`);
}
function downloadWithAnchorElementDownloadAttribute(fileName, extension, content) {
if (content) {
const anchorElement = document.createElement('a');
anchorElement.href = content;
anchorElement.target = '_blank';
anchorElement.download = `${fileName}.${extension}`;
document.body.appendChild(anchorElement);
anchorElement.click();
anchorElement.remove();
}
}
function oneLineTrim(...args) {
const normalTag = (template, ...expressions) => template.reduce((accumulator, part, i) => accumulator + expressions[i - 1] + part);
return normalTag(...args).replace(/\n\s*/g, '');
}
function isNeedDataEncoding() {
const isDownloadAttributeSupported = !isUndefined(document.createElement('a').download);
const isMSSaveOrOpenBlobSupported = !isUndefined(window.Blob && window.navigator.msSaveOrOpenBlob);
return !isMSSaveOrOpenBlobSupported && isDownloadAttributeSupported;
}
function getBulletLongestArrayLength(arr, field) {
return arr.reduce((acc, cur, idx) => { var _a, _b; return (!idx || acc < ((_b = (_a = cur) === null || _a === void 0 ? void 0 : _a[field]) === null || _b === void 0 ? void 0 : _b.length) ? cur[field].length : acc); }, 0);
}
function makeBulletExportData({ series }) {
const seriesData = series.bullet.data;
const markerCount = getBulletLongestArrayLength(seriesData, 'markers');
const rangeCount = getBulletLongestArrayLength(seriesData, 'ranges');
const rangesHeaders = range(0, rangeCount).map((idx) => `Range ${idx + 1}`);
const markerHeaders = range(0, markerCount).map((idx) => `Marker ${idx + 1}`);
return seriesData.reduce((acc, { data, markers, name, ranges }) => {
const rangeDatum = rangesHeaders.map((_, index) => {
var _a;
const rangeData = (_a = ranges) === null || _a === void 0 ? void 0 : _a[index];
return rangeData ? `${rangeData[0]} ~ ${rangeData[1]}` : '';
});
const markerDatum = markerHeaders.map((_, index) => { var _a, _b; return _b = (_a = markers) === null || _a === void 0 ? void 0 : _a[index], (_b !== null && _b !== void 0 ? _b : ''); });
return [...acc, [name, (data !== null && data !== void 0 ? data : ''), ...rangeDatum, ...markerDatum]];
}, [['', 'Actual', ...rangesHeaders, ...markerHeaders]]);
}
function makeHeatmapExportData({ categories, series }) {
const xCategories = categories.x;
return series.heatmap.data.reduce((acc, { data, yCategory }) => [
...acc,
[yCategory, ...data.map((datum) => (isNull(datum) ? '' : datum))],
], [['', ...xCategories]]);
}
function recursiveTreemapData({ label, data, children = [] }, result) {
if (data) {
result.push([label, data]);
}
children.forEach((childrenData) => recursiveTreemapData(childrenData, result));
return result;
}
function makeTreemapExportData(exportData) {
const { series } = exportData;
const result = [['Label', 'Data']];
series.treemap.data.forEach((datum) => {
recursiveTreemapData(datum, result);
});
return result;
}
function makeBubbleExportData(exportData) {
const { series } = exportData;
return series.bubble.data.reduce((acc, { name, data }) => [
...acc,
...data.map((datum) => isNull(datum) ? [] : [name, datum.label, String(datum.x), datum.y, datum.r]),
], [['Name', 'Label', 'X', 'Y', 'Radius']]);
}
function makeBoxPlotExportData(exportData) {
var _a;
const { series } = exportData;
const categories = (_a = exportData.categories, (_a !== null && _a !== void 0 ? _a : []));
return series.boxPlot.data.reduce((acc, { name, data, outliers }) => {
const values = ((data !== null && data !== void 0 ? data : [])).map((rawData, index) => {
var _a;
const outlierValue = (_a = ((outliers !== null && outliers !== void 0 ? outliers : [])).find((outlier) => outlier[0] === index)) === null || _a === void 0 ? void 0 : _a[1];
const value = outlierValue ? [...rawData, outlierValue] : [...rawData];
return value.join();
});
return [...acc, [name, ...values]];
}, [['', ...categories]]);
}
function makePieExportData(exportData) {
var _a;
const { series } = exportData;
const categories = (_a = exportData.categories, (_a !== null && _a !== void 0 ? _a : []));
return series.pie.data.reduce((acc, { name, data }) => {
const values = Array.isArray(data)
? ((data !== null && data !== void 0 ? data : [])).reduce((accNestedPieValue, value) => {
var _a;
return [...accNestedPieValue, [value.name, (_a = value.data, (_a !== null && _a !== void 0 ? _a : ''))]];
}, [])
: [[name, (data !== null && data !== void 0 ? data : '')]];
return [...acc, ...values];
}, categories.length ? [['', ...categories]] : []);
}
function makeCoordinateExportDataValues(type, categories, data) {
return categories.map((category, index) => {
if (type === 'area' && Array.isArray(data[index])) {
return data[index].join();
}
const foundItem = data.find((value) => category === String(getCoordinateXValue(value)));
return foundItem ? getCoordinateYValue(foundItem) : '';
});
}
function makeExportData(exportData) {
const { series } = exportData;
const categories = exportData.categories;
return Object.keys(series).reduce((acc, type) => {
const result = series[type].data.map(({ name, data }) => {
const values = Array.isArray(getFirstValidValue(data)) && includes(['line', 'area', 'scatter'], type)
? makeCoordinateExportDataValues(type, categories, data)
: data.map((value) => (Array.isArray(value) ? value.join() : value));
return [name, ...values];
});
return [...acc, ...result];
}, series.gauge ? [] : [['', ...categories]]);
}
function get2DArrayFromRawData(exportData) {
let result;
const { series } = exportData;
if (series.bullet) {
result = makeBulletExportData(exportData);
}
else if (series.heatmap) {
result = makeHeatmapExportData(exportData);
}
else if (series.bubble) {
result = makeBubbleExportData(exportData);
}
else if (series.boxPlot) {
result = makeBoxPlotExportData(exportData);
}
else if (series.pie) {
result = makePieExportData(exportData);
}
else if (series.treemap) {
result = makeTreemapExportData(exportData);
}
else {
result = makeExportData(exportData);
}
return result;
}
function getTableElementStringForXLS(chartData2DArray) {
let tableElementString = '<table>';
chartData2DArray.forEach((row, rowIndex) => {
const cellTagName = rowIndex === 0 ? 'th' : 'td';
tableElementString += '<tr>';
row.forEach((cell, cellIndex) => {
const cellNumberClass = rowIndex !== 0 || cellIndex === 0 ? ' class="number"' : '';
const cellString = `<${cellTagName}${cellNumberClass}>${cell}</${cellTagName}>`;
tableElementString += cellString;
});
tableElementString += '</tr>';
});
tableElementString += '</table>';
return tableElementString;
}
function makeXLSBodyWithRawData(chartData2DArray) {
return oneLineTrim `<html xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns="http://www.w3.org/TR/REC-html40">
<head>
<!--[if gte mso 9]>
<xml>
<x:ExcelWorkbook>
<x:ExcelWorksheets>
<x:ExcelWorksheet>
<x:Name>Ark1</x:Name>
<x:WorksheetOptions>
<x:DisplayGridlines/>
</x:WorksheetOptions>
</x:ExcelWorksheet>
</x:ExcelWorksheets>
</x:ExcelWorkbook>
</xml>
<![endif]-->
<meta name=ProgId content=Excel.Sheet>
<meta charset=UTF-8>
</head>
<body>
${getTableElementStringForXLS(chartData2DArray)}
</body>
</html>`;
}
function makeCSVBodyWithRawData(chartData2DArray, option = {}) {
const { lineDelimiter = '\u000a', itemDelimiter = ',' } = option;
const lastRowIndex = chartData2DArray.length - 1;
let csvText = '';
chartData2DArray.forEach((row, rowIndex) => {
const lastCellIndex = row.length - 1;
row.forEach((cell, cellIndex) => {
const cellContent = isNumber(cell) ? cell : `"${cell}"`;
csvText += cellContent;
if (cellIndex < lastCellIndex) {
csvText += itemDelimiter;
}
});
if (rowIndex < lastRowIndex) {
csvText += lineDelimiter;
}
});
return csvText;
}
export function execDownload(fileName, extension, content, contentType) {
const downloadMethod = getDownloadMethod();
if (!isString(content) || !downloadMethod) {
return;
}
downloadMethod(fileName, extension, content, contentType);
}
export function downloadSpreadSheet(fileName, extension, data) {
const chartData2DArray = get2DArrayFromRawData(data);
const contentType = DATA_URI_HEADERS[extension].replace(/(data:|;base64,|,%EF%BB%BF)/g, '');
let content = '';
if (extension === 'csv') {
content = makeCSVBodyWithRawData(chartData2DArray);
}
else {
content = makeXLSBodyWithRawData(chartData2DArray);
}
if (isNeedDataEncoding()) {
if (extension !== 'csv') {
// base64 encoding for data URI scheme.
content = window.btoa(unescape(encodeURIComponent(content)));
}
content = DATA_URI_HEADERS[extension] + content;
}
execDownload(fileName, extension, content, contentType);
}