offline-exporting.src.js 21 KB

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