/** * @license Highcharts JS v4.2.5 (2016-05-06) * Client side exporting module * * (c) 2015 Torstein Honsi / Oystein Moseng * * License: www.highcharts.com/license */ /*global MSBlobBuilder */ (function (factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else { factory(Highcharts); } }(function (Highcharts) { var win = Highcharts.win, nav = win.navigator, doc = win.document; // Dummy object so we can reuse our canvas-tools.js without errors Highcharts.CanVGRenderer = {}; /** * Downloads a script and executes a callback when done. * @param {String} scriptLocation * @param {Function} callback */ function getScript(scriptLocation, callback) { var head = doc.getElementsByTagName('head')[0], script = doc.createElement('script'); script.type = 'text/javascript'; script.src = scriptLocation; script.onload = callback; head.appendChild(script); } /** * Add a new method to the Chart object to perform a local download */ Highcharts.Chart.prototype.exportChartLocal = function (exportingOptions, chartOptions) { var chart = this, options = Highcharts.merge(chart.options.exporting, exportingOptions), webKit = nav.userAgent.indexOf('WebKit') > -1 && nav.userAgent.indexOf('Chrome') < 0, // Webkit and not chrome scale = options.scale || 2, chartCopyContainer, domurl = win.URL || win.webkitURL || win, images, imagesEmbedded = 0, el, i, l, fallbackToExportServer = function () { if (options.fallbackToExportServer === false) { if (options.error) { options.error(); } else { throw 'Fallback to export server disabled'; } } else { chart.exportChart(options); } }, // Get data:URL from image URL // Pass in callbacks to handle results. finallyCallback is always called at the end of the process. Supplying this callback is optional. // All callbacks receive three arguments: imageURL, imageType, and callbackArgs. callbackArgs is used only by callbacks and can contain whatever. imageToDataUrl = function (imageURL, imageType, callbackArgs, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) { var img = new win.Image(), taintedHandler, loadHandler = function () { var canvas = doc.createElement('canvas'), ctx = canvas.getContext && canvas.getContext('2d'), dataURL; try { if (!ctx) { noCanvasSupportCallback(imageURL, imageType, callbackArgs); } else { canvas.height = img.height * scale; canvas.width = img.width * scale; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // Now we try to get the contents of the canvas. try { dataURL = canvas.toDataURL(imageType); successCallback(dataURL, imageType, callbackArgs); } catch (e) { // Failed - either tainted canvas or something else went horribly wrong if (e.name === 'SecurityError' || e.name === 'SECURITY_ERR' || e.message === 'SecurityError') { taintedHandler(imageURL, imageType, callbackArgs); } else { throw e; } } } } finally { if (finallyCallback) { finallyCallback(imageURL, imageType, callbackArgs); } } }, // Image load failed (e.g. invalid URL) errorHandler = function () { failedLoadCallback(imageURL, imageType, callbackArgs); if (finallyCallback) { finallyCallback(imageURL, imageType, callbackArgs); } }; // This is called on load if the image drawing to canvas failed with a security error. // We retry the drawing with crossOrigin set to Anonymous. taintedHandler = function () { img = new win.Image(); taintedHandler = taintedCallback; img.crossOrigin = 'Anonymous'; // Must be set prior to loading image source img.onload = loadHandler; img.onerror = errorHandler; img.src = imageURL; }; img.onload = loadHandler; img.onerror = errorHandler; img.src = imageURL; }, // Get blob URL from SVG code. Falls back to normal data URI. svgToDataUrl = function (svg) { try { // Safari requires data URI since it doesn't allow navigation to blob URLs // Firefox has an issue with Blobs and internal references, leading to gradients not working using Blobs (#4550) if (!webKit && nav.userAgent.toLowerCase().indexOf('firefox') < 0) { return domurl.createObjectURL(new win.Blob([svg], { type: 'image/svg+xml;charset-utf-16' })); } } catch (e) { // Ignore } return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg); }, // Download contents by dataURL/blob download = function (dataURL, extension) { var a = doc.createElement('a'), filename = (options.filename || 'chart') + '.' + extension, windowRef; // IE specific blob implementation if (nav.msSaveOrOpenBlob) { nav.msSaveOrOpenBlob(dataURL, filename); return; } // Try HTML5 download attr if supported if (a.download !== undefined) { a.href = dataURL; a.download = filename; // HTML5 download attribute a.target = '_blank'; doc.body.appendChild(a); a.click(); doc.body.removeChild(a); } else { // No download attr, just opening data URI try { windowRef = win.open(dataURL, 'chart'); if (windowRef === undefined || windowRef === null) { throw 'Failed to open window'; } } catch (e) { // window.open failed, trying location.href win.location.href = dataURL; } } }, // Get data URL to an image of the chart and call download on it initiateDownload = function () { var svgurl, blob, imageType = options && options.type || 'image/png', imageExtension = imageType.split('/')[1], svg = chart.sanitizeSVG(chartCopyContainer.innerHTML); // SVG of chart copy // Initiate download depending on file type if (imageType === 'image/svg+xml') { // SVG download. In this case, we want to use Microsoft specific Blob if available try { if (nav.msSaveOrOpenBlob) { blob = new MSBlobBuilder(); blob.append(svg); svgurl = blob.getBlob('image/svg+xml'); } else { svgurl = svgToDataUrl(svg); } download(svgurl, 'svg'); } catch (e) { fallbackToExportServer(); } } else { // PNG/JPEG download - create bitmap from SVG // First, try to get PNG by rendering on canvas svgurl = svgToDataUrl(svg); imageToDataUrl(svgurl, imageType, { /* args */ }, function (imageURL) { // Success try { download(imageURL, imageExtension); } catch (e) { fallbackToExportServer(); } }, function () { // Failed due to tainted canvas // Create new and untainted canvas var canvas = doc.createElement('canvas'), ctx = canvas.getContext('2d'), imageWidth = svg.match(/^]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale, imageHeight = svg.match(/^]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale, downloadWithCanVG = function () { ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight); try { download(nav.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL(imageType), imageExtension); } catch (e) { fallbackToExportServer(); } }; canvas.width = imageWidth; canvas.height = imageHeight; if (win.canvg) { // Use preloaded canvg downloadWithCanVG(); } else { // Must load canVG first chart.showLoading(); getScript(Highcharts.getOptions().global.canvasToolsURL, function () { chart.hideLoading(); downloadWithCanVG(); }); } }, // No canvas support fallbackToExportServer, // Failed to load image fallbackToExportServer, // Finally function () { try { domurl.revokeObjectURL(svgurl); } catch (e) { // Ignore } }); } }, // Success handler, we converted image to base64! embeddedSuccess = function (imageURL, imageType, callbackArgs) { ++imagesEmbedded; // Change image href in chart copy callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL); // Start download when done with the last image if (imagesEmbedded === images.length) { initiateDownload(); } }; // Hook into getSVG to get a copy of the chart copy's container Highcharts.wrap(Highcharts.Chart.prototype, 'getChartHTML', function (proceed) { chartCopyContainer = this.container.cloneNode(true); return proceed.apply(this, Array.prototype.slice.call(arguments, 1)); }); // Trigger hook to get chart copy chart.getSVGForExport(options, chartOptions); images = chartCopyContainer.getElementsByTagName('image'); try { // If there are no images to embed, just go ahead and start the download process if (!images.length) { initiateDownload(); } // Go through the images we want to embed for (i = 0, l = images.length; i < l; ++i) { el = images[i]; imageToDataUrl(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), 'image/png', { imageElement: el }, embeddedSuccess, // Tainted canvas fallbackToExportServer, // No canvas support fallbackToExportServer, // Failed to load source fallbackToExportServer ); } } catch (e) { fallbackToExportServer(); } }; // Extend the default options to use the local exporter logic Highcharts.getOptions().exporting.buttons.contextButton.menuItems = [{ textKey: 'printChart', onclick: function () { this.print(); } }, { separator: true }, { textKey: 'downloadPNG', onclick: function () { this.exportChartLocal(); } }, { textKey: 'downloadJPEG', onclick: function () { this.exportChartLocal({ type: 'image/jpeg' }); } }, { textKey: 'downloadSVG', onclick: function () { this.exportChartLocal({ type: 'image/svg+xml' }); } }]; }));