UNPKG

20.6 kBJavaScriptView Raw
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 'use strict';
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}));