1 | /**
|
2 | * @license Highcharts JS v5.0.2 (2016-10-26)
|
3 | * Client side exporting module
|
4 | *
|
5 | * (c) 2015 Torstein Honsi / Oystein Moseng
|
6 | *
|
7 | * License: www.highcharts.com/license
|
8 | */
|
9 | (function(factory) {
|
10 | if (typeof module === 'object' && module.exports) {
|
11 | module.exports = factory;
|
12 | } else {
|
13 | factory(Highcharts);
|
14 | }
|
15 | }(function(Highcharts) {
|
16 | (function(Highcharts) {
|
17 | /**
|
18 | * Client side exporting module
|
19 | *
|
20 | * (c) 2015 Torstein Honsi / Oystein Moseng
|
21 | *
|
22 | * License: www.highcharts.com/license
|
23 | */
|
24 |
|
25 | ;
|
26 | /*global MSBlobBuilder */
|
27 |
|
28 | var merge = Highcharts.merge,
|
29 | win = Highcharts.win,
|
30 | nav = win.navigator,
|
31 | doc = win.document,
|
32 | each = Highcharts.each,
|
33 | domurl = win.URL || win.webkitURL || win,
|
34 | isMSBrowser = /Edge\/|Trident\/|MSIE /.test(nav.userAgent),
|
35 | loadEventDeferDelay = isMSBrowser ? 150 : 0; // Milliseconds to defer image load event handlers to offset IE bug
|
36 |
|
37 | // Dummy object so we can reuse our canvas-tools.js without errors
|
38 | Highcharts.CanVGRenderer = {};
|
39 |
|
40 |
|
41 | /**
|
42 | * Downloads a script and executes a callback when done.
|
43 | * @param {String} scriptLocation
|
44 | * @param {Function} callback
|
45 | */
|
46 | function getScript(scriptLocation, callback) {
|
47 | var head = doc.getElementsByTagName('head')[0],
|
48 | script = doc.createElement('script');
|
49 |
|
50 | script.type = 'text/javascript';
|
51 | script.src = scriptLocation;
|
52 | script.onload = callback;
|
53 | script.onerror = function() {
|
54 | console.error('Error loading script', scriptLocation); // eslint-disable-line no-console
|
55 | };
|
56 |
|
57 | head.appendChild(script);
|
58 | }
|
59 |
|
60 | // Download contents by dataURL/blob
|
61 | Highcharts.downloadURL = function(dataURL, filename) {
|
62 | var a = doc.createElement('a'),
|
63 | windowRef;
|
64 |
|
65 | // IE specific blob implementation
|
66 | if (nav.msSaveOrOpenBlob) {
|
67 | nav.msSaveOrOpenBlob(dataURL, filename);
|
68 | return;
|
69 | }
|
70 |
|
71 | // Try HTML5 download attr if supported
|
72 | if (a.download !== undefined) {
|
73 | a.href = dataURL;
|
74 | a.download = filename; // HTML5 download attribute
|
75 | a.target = '_blank';
|
76 | doc.body.appendChild(a);
|
77 | a.click();
|
78 | doc.body.removeChild(a);
|
79 | } else {
|
80 | // No download attr, just opening data URI
|
81 | try {
|
82 | windowRef = win.open(dataURL, 'chart');
|
83 | if (windowRef === undefined || windowRef === null) {
|
84 | throw 'Failed to open window';
|
85 | }
|
86 | } catch (e) {
|
87 | // window.open failed, trying location.href
|
88 | win.location.href = dataURL;
|
89 | }
|
90 | }
|
91 | };
|
92 |
|
93 | // Get blob URL from SVG code. Falls back to normal data URI.
|
94 | Highcharts.svgToDataUrl = function(svg) {
|
95 | var webKit = nav.userAgent.indexOf('WebKit') > -1 && nav.userAgent.indexOf('Chrome') < 0; // Webkit and not chrome
|
96 | try {
|
97 | // Safari requires data URI since it doesn't allow navigation to blob URLs
|
98 | // Firefox has an issue with Blobs and internal references, leading to gradients not working using Blobs (#4550)
|
99 | if (!webKit && nav.userAgent.toLowerCase().indexOf('firefox') < 0) {
|
100 | return domurl.createObjectURL(new win.Blob([svg], {
|
101 | type: 'image/svg+xml;charset-utf-16'
|
102 | }));
|
103 | }
|
104 | } catch (e) {
|
105 | // Ignore
|
106 | }
|
107 | return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
|
108 | };
|
109 |
|
110 | // Get data:URL from image URL
|
111 | // Pass in callbacks to handle results. finallyCallback is always called at the end of the process. Supplying this callback is optional.
|
112 | // All callbacks receive four arguments: imageURL, imageType, callbackArgs and scale. callbackArgs is used only by callbacks and can contain whatever.
|
113 | Highcharts.imageToDataUrl = function(imageURL, imageType, callbackArgs, scale, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) {
|
114 | var img = new win.Image(),
|
115 | taintedHandler,
|
116 | loadHandler = function() {
|
117 | setTimeout(function() {
|
118 | var canvas = doc.createElement('canvas'),
|
119 | ctx = canvas.getContext && canvas.getContext('2d'),
|
120 | dataURL;
|
121 | try {
|
122 | if (!ctx) {
|
123 | noCanvasSupportCallback(imageURL, imageType, callbackArgs, scale);
|
124 | } else {
|
125 | canvas.height = img.height * scale;
|
126 | canvas.width = img.width * scale;
|
127 | ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
128 |
|
129 | // Now we try to get the contents of the canvas.
|
130 | try {
|
131 | dataURL = canvas.toDataURL(imageType);
|
132 | successCallback(dataURL, imageType, callbackArgs, scale);
|
133 | } catch (e) {
|
134 | taintedHandler(imageURL, imageType, callbackArgs, scale);
|
135 | }
|
136 | }
|
137 | } finally {
|
138 | if (finallyCallback) {
|
139 | finallyCallback(imageURL, imageType, callbackArgs, scale);
|
140 | }
|
141 | }
|
142 | }, loadEventDeferDelay); // IE bug where image is not always ready despite calling load event.
|
143 | },
|
144 | // Image load failed (e.g. invalid URL)
|
145 | errorHandler = function() {
|
146 | failedLoadCallback(imageURL, imageType, callbackArgs, scale);
|
147 | if (finallyCallback) {
|
148 | finallyCallback(imageURL, imageType, callbackArgs, scale);
|
149 | }
|
150 | };
|
151 |
|
152 | // This is called on load if the image drawing to canvas failed with a security error.
|
153 | // We retry the drawing with crossOrigin set to Anonymous.
|
154 | taintedHandler = function() {
|
155 | img = new win.Image();
|
156 | taintedHandler = taintedCallback;
|
157 | img.crossOrigin = 'Anonymous'; // Must be set prior to loading image source
|
158 | img.onload = loadHandler;
|
159 | img.onerror = errorHandler;
|
160 | img.src = imageURL;
|
161 | };
|
162 |
|
163 | img.onload = loadHandler;
|
164 | img.onerror = errorHandler;
|
165 | img.src = imageURL;
|
166 | };
|
167 |
|
168 | /**
|
169 | * Get data URL to an image of an SVG and call download on it
|
170 | *
|
171 | * options object:
|
172 | * filename: Name of resulting downloaded file without extension
|
173 | * type: File type of resulting download
|
174 | * scale: Scaling factor of downloaded image compared to source
|
175 | * libURL: URL pointing to location of dependency scripts to download on demand
|
176 | */
|
177 | Highcharts.downloadSVGLocal = function(svg, options, failCallback, successCallback) {
|
178 | var svgurl,
|
179 | blob,
|
180 | objectURLRevoke = true,
|
181 | finallyHandler,
|
182 | libURL = options.libURL || Highcharts.getOptions().exporting.libURL,
|
183 | dummySVGContainer = doc.createElement('div'),
|
184 | imageType = options.type || 'image/png',
|
185 | filename = (options.filename || 'chart') + '.' + (imageType === 'image/svg+xml' ? 'svg' : imageType.split('/')[1]),
|
186 | scale = options.scale || 1;
|
187 |
|
188 | libURL = libURL.slice(-1) !== '/' ? libURL + '/' : libURL; // Allow libURL to end with or without fordward slash
|
189 |
|
190 | function svgToPdf(svgElement, margin) {
|
191 | var width = svgElement.width.baseVal.value + 2 * margin,
|
192 | height = svgElement.height.baseVal.value + 2 * margin,
|
193 | pdf = new win.jsPDF('l', 'pt', [width, height]); // eslint-disable-line new-cap
|
194 | win.svgElementToPdf(svgElement, pdf, {
|
195 | removeInvalid: true
|
196 | });
|
197 | return pdf.output('datauristring');
|
198 | }
|
199 |
|
200 | function downloadPDF() {
|
201 | dummySVGContainer.innerHTML = svg;
|
202 | var textElements = dummySVGContainer.getElementsByTagName('text'),
|
203 | svgElementStyle = dummySVGContainer.getElementsByTagName('svg')[0].style;
|
204 | // Workaround for the text styling. Making sure it does pick up the root element
|
205 | each(textElements, function(el) {
|
206 | each(['font-family', 'font-size'], function(property) {
|
207 | if (!el.style[property] && svgElementStyle[property]) {
|
208 | el.style[property] = svgElementStyle[property];
|
209 | }
|
210 | });
|
211 | el.style['font-family'] = el.style['font-family'] && el.style['font-family'].split(' ').splice(-1);
|
212 | });
|
213 | var svgData = svgToPdf(dummySVGContainer.firstChild, 0);
|
214 | Highcharts.downloadURL(svgData, filename);
|
215 | if (successCallback) {
|
216 | successCallback();
|
217 | }
|
218 | }
|
219 |
|
220 | // Initiate download depending on file type
|
221 | if (imageType === 'image/svg+xml') {
|
222 | // SVG download. In this case, we want to use Microsoft specific Blob if available
|
223 | try {
|
224 | if (nav.msSaveOrOpenBlob) {
|
225 | blob = new MSBlobBuilder();
|
226 | blob.append(svg);
|
227 | svgurl = blob.getBlob('image/svg+xml');
|
228 | } else {
|
229 | svgurl = Highcharts.svgToDataUrl(svg);
|
230 | }
|
231 | Highcharts.downloadURL(svgurl, filename);
|
232 | if (successCallback) {
|
233 | successCallback();
|
234 | }
|
235 | } catch (e) {
|
236 | failCallback();
|
237 | }
|
238 | } else if (imageType === 'application/pdf') {
|
239 | if (win.jsPDF && win.svgElementToPdf) {
|
240 | downloadPDF();
|
241 | } else {
|
242 | // Must load pdf libraries first
|
243 | 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.
|
244 | getScript(libURL + 'jspdf.js', function() {
|
245 | getScript(libURL + 'rgbcolor.js', function() {
|
246 | getScript(libURL + 'svg2pdf.js', function() {
|
247 | downloadPDF();
|
248 | });
|
249 | });
|
250 | });
|
251 | }
|
252 | } else {
|
253 | // PNG/JPEG download - create bitmap from SVG
|
254 |
|
255 | svgurl = Highcharts.svgToDataUrl(svg);
|
256 | finallyHandler = function() {
|
257 | try {
|
258 | domurl.revokeObjectURL(svgurl);
|
259 | } catch (e) {
|
260 | // Ignore
|
261 | }
|
262 | };
|
263 | // First, try to get PNG by rendering on canvas
|
264 | Highcharts.imageToDataUrl(svgurl, imageType, { /* args */ }, scale, function(imageURL) {
|
265 | // Success
|
266 | try {
|
267 | Highcharts.downloadURL(imageURL, filename);
|
268 | if (successCallback) {
|
269 | successCallback();
|
270 | }
|
271 | } catch (e) {
|
272 | failCallback();
|
273 | }
|
274 | }, function() {
|
275 | // Failed due to tainted canvas
|
276 | // Create new and untainted canvas
|
277 | var canvas = doc.createElement('canvas'),
|
278 | ctx = canvas.getContext('2d'),
|
279 | imageWidth = svg.match(/^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
|
280 | imageHeight = svg.match(/^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
|
281 | downloadWithCanVG = function() {
|
282 | ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight);
|
283 | try {
|
284 | Highcharts.downloadURL(nav.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL(imageType), filename);
|
285 | if (successCallback) {
|
286 | successCallback();
|
287 | }
|
288 | } catch (e) {
|
289 | failCallback();
|
290 | } finally {
|
291 | finallyHandler();
|
292 | }
|
293 | };
|
294 |
|
295 | canvas.width = imageWidth;
|
296 | canvas.height = imageHeight;
|
297 | if (win.canvg) {
|
298 | // Use preloaded canvg
|
299 | downloadWithCanVG();
|
300 | } else {
|
301 | // Must load canVG first
|
302 | 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.
|
303 | getScript(libURL + 'rgbcolor.js', function() { // Get RGBColor.js first
|
304 | getScript(libURL + 'canvg.js', function() {
|
305 | downloadWithCanVG();
|
306 | });
|
307 | });
|
308 | }
|
309 | },
|
310 | // No canvas support
|
311 | failCallback,
|
312 | // Failed to load image
|
313 | failCallback,
|
314 | // Finally
|
315 | function() {
|
316 | if (objectURLRevoke) {
|
317 | finallyHandler();
|
318 | }
|
319 | });
|
320 | }
|
321 | };
|
322 |
|
323 | // Get SVG of chart prepared for client side export. This converts embedded images in the SVG to data URIs.
|
324 | // The options and chartOptions arguments are passed to the getSVGForExport function.
|
325 | Highcharts.Chart.prototype.getSVGForLocalExport = function(options, chartOptions, failCallback, successCallback) {
|
326 | var chart = this,
|
327 | images,
|
328 | imagesEmbedded = 0,
|
329 | chartCopyContainer,
|
330 | el,
|
331 | i,
|
332 | l,
|
333 | // Success handler, we converted image to base64!
|
334 | embeddedSuccess = function(imageURL, imageType, callbackArgs) {
|
335 | ++imagesEmbedded;
|
336 |
|
337 | // Change image href in chart copy
|
338 | callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL);
|
339 |
|
340 | // When done with last image we have our SVG
|
341 | if (imagesEmbedded === images.length) {
|
342 | successCallback(chart.sanitizeSVG(chartCopyContainer.innerHTML));
|
343 | }
|
344 | };
|
345 |
|
346 | // Hook into getSVG to get a copy of the chart copy's container
|
347 | Highcharts.wrap(Highcharts.Chart.prototype, 'getChartHTML', function(proceed) {
|
348 | chartCopyContainer = this.container.cloneNode(true);
|
349 | return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
|
350 | });
|
351 |
|
352 | // Trigger hook to get chart copy
|
353 | chart.getSVGForExport(options, chartOptions);
|
354 | images = chartCopyContainer.getElementsByTagName('image');
|
355 |
|
356 | try {
|
357 | // If there are no images to embed, the SVG is okay now.
|
358 | if (!images.length) {
|
359 | successCallback(chart.sanitizeSVG(chartCopyContainer.innerHTML)); // Use SVG of chart copy
|
360 | return;
|
361 | }
|
362 |
|
363 | // Go through the images we want to embed
|
364 | for (i = 0, l = images.length; i < l; ++i) {
|
365 | el = images[i];
|
366 | Highcharts.imageToDataUrl(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), 'image/png', {
|
367 | imageElement: el
|
368 | }, options.scale,
|
369 | embeddedSuccess,
|
370 | // Tainted canvas
|
371 | failCallback,
|
372 | // No canvas support
|
373 | failCallback,
|
374 | // Failed to load source
|
375 | failCallback
|
376 | );
|
377 | }
|
378 | } catch (e) {
|
379 | failCallback();
|
380 | }
|
381 | };
|
382 |
|
383 | /**
|
384 | * Add a new method to the Chart object to perform a local download
|
385 | */
|
386 | Highcharts.Chart.prototype.exportChartLocal = function(exportingOptions, chartOptions) {
|
387 | var chart = this,
|
388 | options = Highcharts.merge(chart.options.exporting, exportingOptions),
|
389 | fallbackToExportServer = function() {
|
390 | if (options.fallbackToExportServer === false) {
|
391 | if (options.error) {
|
392 | options.error();
|
393 | } else {
|
394 | throw 'Fallback to export server disabled';
|
395 | }
|
396 | } else {
|
397 | chart.exportChart(options);
|
398 | }
|
399 | },
|
400 | svgSuccess = function(svg) {
|
401 | Highcharts.downloadSVGLocal(svg, options, fallbackToExportServer);
|
402 | };
|
403 |
|
404 | // If we have embedded images and are exporting to JPEG/PNG, Microsoft browsers won't handle it, so fall back
|
405 | if ((isMSBrowser && options.imageType !== 'image/svg+xml' || options.imageType !== 'application/pdf') && chart.container.getElementsByTagName('image').length) {
|
406 | fallbackToExportServer();
|
407 | return;
|
408 | }
|
409 |
|
410 | chart.getSVGForLocalExport(options, chartOptions, fallbackToExportServer, svgSuccess);
|
411 | };
|
412 |
|
413 | // Extend the default options to use the local exporter logic
|
414 | merge(true, Highcharts.getOptions().exporting, {
|
415 | libURL: 'http://code.highcharts.com/5.0.2/lib/',
|
416 | buttons: {
|
417 | contextButton: {
|
418 | menuItems: [{
|
419 | textKey: 'printChart',
|
420 | onclick: function() {
|
421 | this.print();
|
422 | }
|
423 | }, {
|
424 | separator: true
|
425 | }, {
|
426 | textKey: 'downloadPNG',
|
427 | onclick: function() {
|
428 | this.exportChartLocal();
|
429 | }
|
430 | }, {
|
431 | textKey: 'downloadJPEG',
|
432 | onclick: function() {
|
433 | this.exportChartLocal({
|
434 | type: 'image/jpeg'
|
435 | });
|
436 | }
|
437 | }, {
|
438 | textKey: 'downloadSVG',
|
439 | onclick: function() {
|
440 | this.exportChartLocal({
|
441 | type: 'image/svg+xml'
|
442 | });
|
443 | }
|
444 | }, {
|
445 | textKey: 'downloadPDF',
|
446 | onclick: function() {
|
447 | this.exportChartLocal({
|
448 | type: 'application/pdf'
|
449 | });
|
450 | }
|
451 | }]
|
452 | }
|
453 | }
|
454 | });
|
455 |
|
456 | }(Highcharts));
|
457 | }));
|