/** * @license Highcharts JS v5.0.2 (2016-10-26) * Client side exporting module * * (c) 2015 Torstein Honsi / Oystein Moseng * * License: www.highcharts.com/license */ (function(factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else { factory(Highcharts); } }(function(Highcharts) { (function(Highcharts) { /** * Client side exporting module * * (c) 2015 Torstein Honsi / Oystein Moseng * * License: www.highcharts.com/license */ 'use strict'; /*global MSBlobBuilder */ var merge = Highcharts.merge, win = Highcharts.win, nav = win.navigator, doc = win.document, each = Highcharts.each, domurl = win.URL || win.webkitURL || win, isMSBrowser = /Edge\/|Trident\/|MSIE /.test(nav.userAgent), loadEventDeferDelay = isMSBrowser ? 150 : 0; // Milliseconds to defer image load event handlers to offset IE bug // 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; script.onerror = function() { console.error('Error loading script', scriptLocation); // eslint-disable-line no-console }; head.appendChild(script); } // Download contents by dataURL/blob Highcharts.downloadURL = function(dataURL, filename) { var a = doc.createElement('a'), 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 blob URL from SVG code. Falls back to normal data URI. Highcharts.svgToDataUrl = function(svg) { var webKit = nav.userAgent.indexOf('WebKit') > -1 && nav.userAgent.indexOf('Chrome') < 0; // Webkit and not chrome 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); }; // 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 four arguments: imageURL, imageType, callbackArgs and scale. callbackArgs is used only by callbacks and can contain whatever. Highcharts.imageToDataUrl = function(imageURL, imageType, callbackArgs, scale, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) { var img = new win.Image(), taintedHandler, loadHandler = function() { setTimeout(function() { var canvas = doc.createElement('canvas'), ctx = canvas.getContext && canvas.getContext('2d'), dataURL; try { if (!ctx) { noCanvasSupportCallback(imageURL, imageType, callbackArgs, scale); } 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, scale); } catch (e) { taintedHandler(imageURL, imageType, callbackArgs, scale); } } } finally { if (finallyCallback) { finallyCallback(imageURL, imageType, callbackArgs, scale); } } }, loadEventDeferDelay); // IE bug where image is not always ready despite calling load event. }, // Image load failed (e.g. invalid URL) errorHandler = function() { failedLoadCallback(imageURL, imageType, callbackArgs, scale); if (finallyCallback) { finallyCallback(imageURL, imageType, callbackArgs, scale); } }; // 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 data URL to an image of an SVG and call download on it * * options object: * filename: Name of resulting downloaded file without extension * type: File type of resulting download * scale: Scaling factor of downloaded image compared to source * libURL: URL pointing to location of dependency scripts to download on demand */ Highcharts.downloadSVGLocal = function(svg, options, failCallback, successCallback) { var svgurl, blob, objectURLRevoke = true, finallyHandler, libURL = options.libURL || Highcharts.getOptions().exporting.libURL, dummySVGContainer = doc.createElement('div'), imageType = options.type || 'image/png', filename = (options.filename || 'chart') + '.' + (imageType === 'image/svg+xml' ? 'svg' : imageType.split('/')[1]), scale = options.scale || 1; libURL = libURL.slice(-1) !== '/' ? libURL + '/' : libURL; // Allow libURL to end with or without fordward slash function svgToPdf(svgElement, margin) { var width = svgElement.width.baseVal.value + 2 * margin, height = svgElement.height.baseVal.value + 2 * margin, pdf = new win.jsPDF('l', 'pt', [width, height]); // eslint-disable-line new-cap win.svgElementToPdf(svgElement, pdf, { removeInvalid: true }); return pdf.output('datauristring'); } function downloadPDF() { dummySVGContainer.innerHTML = svg; var textElements = dummySVGContainer.getElementsByTagName('text'), svgElementStyle = dummySVGContainer.getElementsByTagName('svg')[0].style; // Workaround for the text styling. Making sure it does pick up the root element each(textElements, function(el) { each(['font-family', 'font-size'], function(property) { if (!el.style[property] && svgElementStyle[property]) { el.style[property] = svgElementStyle[property]; } }); el.style['font-family'] = el.style['font-family'] && el.style['font-family'].split(' ').splice(-1); }); var svgData = svgToPdf(dummySVGContainer.firstChild, 0); Highcharts.downloadURL(svgData, filename); if (successCallback) { successCallback(); } } // 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 = Highcharts.svgToDataUrl(svg); } Highcharts.downloadURL(svgurl, filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(); } } else if (imageType === 'application/pdf') { if (win.jsPDF && win.svgElementToPdf) { downloadPDF(); } else { // Must load pdf libraries first objectURLRevoke = true; // Don't destroy the object URL yet since we are doing things asynchronously. A cleaner solution would be nice, but this will do for now. getScript(libURL + 'jspdf.js', function() { getScript(libURL + 'rgbcolor.js', function() { getScript(libURL + 'svg2pdf.js', function() { downloadPDF(); }); }); }); } } else { // PNG/JPEG download - create bitmap from SVG svgurl = Highcharts.svgToDataUrl(svg); finallyHandler = function() { try { domurl.revokeObjectURL(svgurl); } catch (e) { // Ignore } }; // First, try to get PNG by rendering on canvas Highcharts.imageToDataUrl(svgurl, imageType, { /* args */ }, scale, function(imageURL) { // Success try { Highcharts.downloadURL(imageURL, filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(); } }, 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 { Highcharts.downloadURL(nav.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL(imageType), filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(); } finally { finallyHandler(); } }; canvas.width = imageWidth; canvas.height = imageHeight; if (win.canvg) { // Use preloaded canvg downloadWithCanVG(); } else { // Must load canVG first objectURLRevoke = true; // Don't destroy the object URL yet since we are doing things asynchronously. A cleaner solution would be nice, but this will do for now. getScript(libURL + 'rgbcolor.js', function() { // Get RGBColor.js first getScript(libURL + 'canvg.js', function() { downloadWithCanVG(); }); }); } }, // No canvas support failCallback, // Failed to load image failCallback, // Finally function() { if (objectURLRevoke) { finallyHandler(); } }); } }; // Get SVG of chart prepared for client side export. This converts embedded images in the SVG to data URIs. // The options and chartOptions arguments are passed to the getSVGForExport function. Highcharts.Chart.prototype.getSVGForLocalExport = function(options, chartOptions, failCallback, successCallback) { var chart = this, images, imagesEmbedded = 0, chartCopyContainer, el, i, l, // 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); // When done with last image we have our SVG if (imagesEmbedded === images.length) { successCallback(chart.sanitizeSVG(chartCopyContainer.innerHTML)); } }; // 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, the SVG is okay now. if (!images.length) { successCallback(chart.sanitizeSVG(chartCopyContainer.innerHTML)); // Use SVG of chart copy return; } // Go through the images we want to embed for (i = 0, l = images.length; i < l; ++i) { el = images[i]; Highcharts.imageToDataUrl(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), 'image/png', { imageElement: el }, options.scale, embeddedSuccess, // Tainted canvas failCallback, // No canvas support failCallback, // Failed to load source failCallback ); } } catch (e) { failCallback(); } }; /** * 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), fallbackToExportServer = function() { if (options.fallbackToExportServer === false) { if (options.error) { options.error(); } else { throw 'Fallback to export server disabled'; } } else { chart.exportChart(options); } }, svgSuccess = function(svg) { Highcharts.downloadSVGLocal(svg, options, fallbackToExportServer); }; // If we have embedded images and are exporting to JPEG/PNG, Microsoft browsers won't handle it, so fall back if ((isMSBrowser && options.imageType !== 'image/svg+xml' || options.imageType !== 'application/pdf') && chart.container.getElementsByTagName('image').length) { fallbackToExportServer(); return; } chart.getSVGForLocalExport(options, chartOptions, fallbackToExportServer, svgSuccess); }; // Extend the default options to use the local exporter logic merge(true, Highcharts.getOptions().exporting, { libURL: 'http://code.highcharts.com/5.0.2/lib/', 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' }); } }, { textKey: 'downloadPDF', onclick: function() { this.exportChartLocal({ type: 'application/pdf' }); } }] } } }); }(Highcharts)); }));