offline-exporting.src.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. * Client side exporting module
  4. *
  5. * (c) 2015 Torstein Honsi / Oystein Moseng
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. (function (Highcharts) {
  18. /**
  19. * Client side exporting module
  20. *
  21. * (c) 2015 Torstein Honsi / Oystein Moseng
  22. *
  23. * License: www.highcharts.com/license
  24. */
  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. isEdgeBrowser = /Edge\/\d+/.test(nav.userAgent),
  34. // Milliseconds to defer image load event handlers to offset IE bug
  35. loadEventDeferDelay = isMSBrowser ? 150 : 0;
  36. // Dummy object so we can reuse our canvas-tools.js without errors
  37. Highcharts.CanVGRenderer = {};
  38. /**
  39. * Downloads a script and executes a callback when done.
  40. * @param {String} scriptLocation
  41. * @param {Function} callback
  42. */
  43. function getScript(scriptLocation, callback) {
  44. var head = doc.getElementsByTagName('head')[0],
  45. script = doc.createElement('script');
  46. script.type = 'text/javascript';
  47. script.src = scriptLocation;
  48. script.onload = callback;
  49. script.onerror = function () {
  50. Highcharts.error('Error loading script ' + scriptLocation);
  51. };
  52. head.appendChild(script);
  53. }
  54. // Convert dataURL to Blob if supported, otherwise returns undefined
  55. Highcharts.dataURLtoBlob = function (dataURL) {
  56. if (
  57. win.atob &&
  58. win.ArrayBuffer &&
  59. win.Uint8Array &&
  60. win.Blob &&
  61. domurl.createObjectURL
  62. ) {
  63. // Try to convert data URL to Blob
  64. var parts = dataURL.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/),
  65. binStr = win.atob(parts[3]), // Assume base64 encoding
  66. buf = new win.ArrayBuffer(binStr.length),
  67. binary = new win.Uint8Array(buf),
  68. blob;
  69. for (var i = 0; i < binary.length; ++i) {
  70. binary[i] = binStr.charCodeAt(i);
  71. }
  72. blob = new win.Blob([binary], { 'type': parts[1] });
  73. return domurl.createObjectURL(blob);
  74. }
  75. };
  76. // Download contents by dataURL/blob
  77. Highcharts.downloadURL = function (dataURL, filename) {
  78. var a = doc.createElement('a'),
  79. windowRef;
  80. // IE specific blob implementation
  81. // Don't use for normal dataURLs
  82. if (
  83. typeof dataURL !== 'string' &&
  84. !(dataURL instanceof String) &&
  85. nav.msSaveOrOpenBlob
  86. ) {
  87. nav.msSaveOrOpenBlob(dataURL, filename);
  88. return;
  89. }
  90. // Some browsers have limitations for data URL lengths. Try to convert to
  91. // Blob or fall back. Edge always needs that blob.
  92. if (isEdgeBrowser || dataURL.length > 2000000) {
  93. dataURL = Highcharts.dataURLtoBlob(dataURL);
  94. if (!dataURL) {
  95. throw 'Data URL length limit reached';
  96. }
  97. }
  98. // Try HTML5 download attr if supported
  99. if (a.download !== undefined) {
  100. a.href = dataURL;
  101. a.download = filename; // HTML5 download attribute
  102. doc.body.appendChild(a);
  103. a.click();
  104. doc.body.removeChild(a);
  105. } else {
  106. // No download attr, just opening data URI
  107. try {
  108. windowRef = win.open(dataURL, 'chart');
  109. if (windowRef === undefined || windowRef === null) {
  110. throw 'Failed to open window';
  111. }
  112. } catch (e) {
  113. // window.open failed, trying location.href
  114. win.location.href = dataURL;
  115. }
  116. }
  117. };
  118. // Get blob URL from SVG code. Falls back to normal data URI.
  119. Highcharts.svgToDataUrl = function (svg) {
  120. // Webkit and not chrome
  121. var webKit = (
  122. nav.userAgent.indexOf('WebKit') > -1 &&
  123. nav.userAgent.indexOf('Chrome') < 0
  124. );
  125. try {
  126. // Safari requires data URI since it doesn't allow navigation to blob
  127. // URLs. Firefox has an issue with Blobs and internal references,
  128. // leading to gradients not working using Blobs (#4550)
  129. if (!webKit && nav.userAgent.toLowerCase().indexOf('firefox') < 0) {
  130. return domurl.createObjectURL(new win.Blob([svg], {
  131. type: 'image/svg+xml;charset-utf-16'
  132. }));
  133. }
  134. } catch (e) {
  135. // Ignore
  136. }
  137. return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
  138. };
  139. // Get data:URL from image URL
  140. // Pass in callbacks to handle results. finallyCallback is always called at the
  141. // end of the process. Supplying this callback is optional. All callbacks
  142. // receive four arguments: imageURL, imageType, callbackArgs and scale.
  143. // callbackArgs is used only by callbacks and can contain whatever.
  144. Highcharts.imageToDataUrl = function (
  145. imageURL,
  146. imageType,
  147. callbackArgs,
  148. scale,
  149. successCallback,
  150. taintedCallback,
  151. noCanvasSupportCallback,
  152. failedLoadCallback,
  153. finallyCallback
  154. ) {
  155. var img = new win.Image(),
  156. taintedHandler,
  157. loadHandler = function () {
  158. setTimeout(function () {
  159. var canvas = doc.createElement('canvas'),
  160. ctx = canvas.getContext && canvas.getContext('2d'),
  161. dataURL;
  162. try {
  163. if (!ctx) {
  164. noCanvasSupportCallback(
  165. imageURL,
  166. imageType,
  167. callbackArgs,
  168. scale
  169. );
  170. } else {
  171. canvas.height = img.height * scale;
  172. canvas.width = img.width * scale;
  173. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  174. // Now we try to get the contents of the canvas.
  175. try {
  176. dataURL = canvas.toDataURL(imageType);
  177. successCallback(
  178. dataURL,
  179. imageType,
  180. callbackArgs,
  181. scale
  182. );
  183. } catch (e) {
  184. taintedHandler(
  185. imageURL,
  186. imageType,
  187. callbackArgs,
  188. scale
  189. );
  190. }
  191. }
  192. } finally {
  193. if (finallyCallback) {
  194. finallyCallback(
  195. imageURL,
  196. imageType,
  197. callbackArgs,
  198. scale
  199. );
  200. }
  201. }
  202. // IE bug where image is not always ready despite calling load
  203. // event.
  204. }, loadEventDeferDelay);
  205. },
  206. // Image load failed (e.g. invalid URL)
  207. errorHandler = function () {
  208. failedLoadCallback(imageURL, imageType, callbackArgs, scale);
  209. if (finallyCallback) {
  210. finallyCallback(imageURL, imageType, callbackArgs, scale);
  211. }
  212. };
  213. // This is called on load if the image drawing to canvas failed with a
  214. // security error. We retry the drawing with crossOrigin set to Anonymous.
  215. taintedHandler = function () {
  216. img = new win.Image();
  217. taintedHandler = taintedCallback;
  218. // Must be set prior to loading image source
  219. img.crossOrigin = 'Anonymous';
  220. img.onload = loadHandler;
  221. img.onerror = errorHandler;
  222. img.src = imageURL;
  223. };
  224. img.onload = loadHandler;
  225. img.onerror = errorHandler;
  226. img.src = imageURL;
  227. };
  228. /**
  229. * Get data URL to an image of an SVG and call download on it
  230. *
  231. * options object:
  232. * - filename: Name of resulting downloaded file without extension
  233. * - type: File type of resulting download
  234. * - scale: Scaling factor of downloaded image compared to source
  235. * - libURL: URL pointing to location of dependency scripts to download on
  236. * demand
  237. */
  238. Highcharts.downloadSVGLocal = function (
  239. svg,
  240. options,
  241. failCallback,
  242. successCallback
  243. ) {
  244. var svgurl,
  245. blob,
  246. objectURLRevoke = true,
  247. finallyHandler,
  248. libURL = options.libURL || Highcharts.getOptions().exporting.libURL,
  249. dummySVGContainer = doc.createElement('div'),
  250. imageType = options.type || 'image/png',
  251. filename = (
  252. (options.filename || 'chart') +
  253. '.' +
  254. (imageType === 'image/svg+xml' ? 'svg' : imageType.split('/')[1])
  255. ),
  256. scale = options.scale || 1;
  257. // Allow libURL to end with or without fordward slash
  258. libURL = libURL.slice(-1) !== '/' ? libURL + '/' : libURL;
  259. function svgToPdf(svgElement, margin) {
  260. var width = svgElement.width.baseVal.value + 2 * margin,
  261. height = svgElement.height.baseVal.value + 2 * margin,
  262. pdf = new win.jsPDF( // eslint-disable-line new-cap
  263. 'l',
  264. 'pt',
  265. [width, height]
  266. );
  267. // Workaround for #7090, hidden elements were drawn anyway. It comes
  268. // down to https://github.com/yWorks/svg2pdf.js/issues/28. Check this
  269. // later.
  270. each(
  271. svgElement.querySelectorAll('*[visibility="hidden"]'),
  272. function (node) {
  273. node.parentNode.removeChild(node);
  274. }
  275. );
  276. win.svg2pdf(svgElement, pdf, { removeInvalid: true });
  277. return pdf.output('datauristring');
  278. }
  279. function downloadPDF() {
  280. dummySVGContainer.innerHTML = svg;
  281. var textElements = dummySVGContainer.getElementsByTagName('text'),
  282. titleElements,
  283. svgData,
  284. // Copy style property to element from parents if it's not there.
  285. // Searches up hierarchy until it finds prop, or hits the chart
  286. // container.
  287. setStylePropertyFromParents = function (el, propName) {
  288. var curParent = el;
  289. while (curParent && curParent !== dummySVGContainer) {
  290. if (curParent.style[propName]) {
  291. el.style[propName] = curParent.style[propName];
  292. break;
  293. }
  294. curParent = curParent.parentNode;
  295. }
  296. };
  297. // Workaround for the text styling. Making sure it does pick up settings
  298. // for parent elements.
  299. each(textElements, function (el) {
  300. // Workaround for the text styling. making sure it does pick up the
  301. // root element
  302. each(['font-family', 'font-size'], function (property) {
  303. setStylePropertyFromParents(el, property);
  304. });
  305. el.style['font-family'] = (
  306. el.style['font-family'] &&
  307. el.style['font-family'].split(' ').splice(-1)
  308. );
  309. // Workaround for plotband with width, removing title from text
  310. // nodes
  311. titleElements = el.getElementsByTagName('title');
  312. each(titleElements, function (titleElement) {
  313. el.removeChild(titleElement);
  314. });
  315. });
  316. svgData = svgToPdf(dummySVGContainer.firstChild, 0);
  317. try {
  318. Highcharts.downloadURL(svgData, filename);
  319. if (successCallback) {
  320. successCallback();
  321. }
  322. } catch (e) {
  323. failCallback();
  324. }
  325. }
  326. // Initiate download depending on file type
  327. if (imageType === 'image/svg+xml') {
  328. // SVG download. In this case, we want to use Microsoft specific Blob if
  329. // available
  330. try {
  331. if (nav.msSaveOrOpenBlob) {
  332. blob = new MSBlobBuilder();
  333. blob.append(svg);
  334. svgurl = blob.getBlob('image/svg+xml');
  335. } else {
  336. svgurl = Highcharts.svgToDataUrl(svg);
  337. }
  338. Highcharts.downloadURL(svgurl, filename);
  339. if (successCallback) {
  340. successCallback();
  341. }
  342. } catch (e) {
  343. failCallback();
  344. }
  345. } else if (imageType === 'application/pdf') {
  346. if (win.jsPDF && win.svg2pdf) {
  347. downloadPDF();
  348. } else {
  349. // Must load pdf libraries first. // Don't destroy the object URL
  350. // yet since we are doing things asynchronously. A cleaner solution
  351. // would be nice, but this will do for now.
  352. objectURLRevoke = true;
  353. getScript(libURL + 'jspdf.js', function () {
  354. getScript(libURL + 'svg2pdf.js', function () {
  355. downloadPDF();
  356. });
  357. });
  358. }
  359. } else {
  360. // PNG/JPEG download - create bitmap from SVG
  361. svgurl = Highcharts.svgToDataUrl(svg);
  362. finallyHandler = function () {
  363. try {
  364. domurl.revokeObjectURL(svgurl);
  365. } catch (e) {
  366. // Ignore
  367. }
  368. };
  369. // First, try to get PNG by rendering on canvas
  370. Highcharts.imageToDataUrl(
  371. svgurl,
  372. imageType,
  373. {},
  374. scale,
  375. function (imageURL) {
  376. // Success
  377. try {
  378. Highcharts.downloadURL(imageURL, filename);
  379. if (successCallback) {
  380. successCallback();
  381. }
  382. } catch (e) {
  383. failCallback();
  384. }
  385. }, function () {
  386. // Failed due to tainted canvas
  387. // Create new and untainted canvas
  388. var canvas = doc.createElement('canvas'),
  389. ctx = canvas.getContext('2d'),
  390. imageWidth = svg.match(
  391. /^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/
  392. )[1] * scale,
  393. imageHeight = svg.match(
  394. /^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/
  395. )[1] * scale,
  396. downloadWithCanVG = function () {
  397. ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight);
  398. try {
  399. Highcharts.downloadURL(
  400. nav.msSaveOrOpenBlob ?
  401. canvas.msToBlob() :
  402. canvas.toDataURL(imageType),
  403. filename
  404. );
  405. if (successCallback) {
  406. successCallback();
  407. }
  408. } catch (e) {
  409. failCallback();
  410. } finally {
  411. finallyHandler();
  412. }
  413. };
  414. canvas.width = imageWidth;
  415. canvas.height = imageHeight;
  416. if (win.canvg) {
  417. // Use preloaded canvg
  418. downloadWithCanVG();
  419. } else {
  420. // Must load canVG first. // Don't destroy the object URL
  421. // yet since we are doing things asynchronously. A cleaner
  422. // solution would be nice, but this will do for now.
  423. objectURLRevoke = true;
  424. // Get RGBColor.js first, then canvg
  425. getScript(libURL + 'rgbcolor.js', function () {
  426. getScript(libURL + 'canvg.js', function () {
  427. downloadWithCanVG();
  428. });
  429. });
  430. }
  431. },
  432. // No canvas support
  433. failCallback,
  434. // Failed to load image
  435. failCallback,
  436. // Finally
  437. function () {
  438. if (objectURLRevoke) {
  439. finallyHandler();
  440. }
  441. }
  442. );
  443. }
  444. };
  445. // Get SVG of chart prepared for client side export. This converts embedded
  446. // images in the SVG to data URIs. The options and chartOptions arguments are
  447. // passed to the getSVGForExport function.
  448. Highcharts.Chart.prototype.getSVGForLocalExport = function (
  449. options,
  450. chartOptions,
  451. failCallback,
  452. successCallback
  453. ) {
  454. var chart = this,
  455. images,
  456. imagesEmbedded = 0,
  457. chartCopyContainer,
  458. chartCopyOptions,
  459. el,
  460. i,
  461. l,
  462. // After grabbing the SVG of the chart's copy container we need to do
  463. // sanitation on the SVG
  464. sanitize = function (svg) {
  465. return chart.sanitizeSVG(svg, chartCopyOptions);
  466. },
  467. // Success handler, we converted image to base64!
  468. embeddedSuccess = function (imageURL, imageType, callbackArgs) {
  469. ++imagesEmbedded;
  470. // Change image href in chart copy
  471. callbackArgs.imageElement.setAttributeNS(
  472. 'http://www.w3.org/1999/xlink',
  473. 'href',
  474. imageURL
  475. );
  476. // When done with last image we have our SVG
  477. if (imagesEmbedded === images.length) {
  478. successCallback(sanitize(chartCopyContainer.innerHTML));
  479. }
  480. };
  481. // Hook into getSVG to get a copy of the chart copy's container
  482. Highcharts.wrap(
  483. Highcharts.Chart.prototype,
  484. 'getChartHTML',
  485. function (proceed) {
  486. var ret = proceed.apply(
  487. this,
  488. Array.prototype.slice.call(arguments, 1)
  489. );
  490. chartCopyOptions = this.options;
  491. chartCopyContainer = this.container.cloneNode(true);
  492. return ret;
  493. }
  494. );
  495. // Trigger hook to get chart copy
  496. chart.getSVGForExport(options, chartOptions);
  497. images = chartCopyContainer.getElementsByTagName('image');
  498. try {
  499. // If there are no images to embed, the SVG is okay now.
  500. if (!images.length) {
  501. // Use SVG of chart copy
  502. successCallback(sanitize(chartCopyContainer.innerHTML));
  503. return;
  504. }
  505. // Go through the images we want to embed
  506. for (i = 0, l = images.length; i < l; ++i) {
  507. el = images[i];
  508. Highcharts.imageToDataUrl(el.getAttributeNS(
  509. 'http://www.w3.org/1999/xlink',
  510. 'href'
  511. ), 'image/png', { imageElement: el }, options.scale,
  512. embeddedSuccess,
  513. // Tainted canvas
  514. failCallback,
  515. // No canvas support
  516. failCallback,
  517. // Failed to load source
  518. failCallback
  519. );
  520. }
  521. } catch (e) {
  522. failCallback();
  523. }
  524. };
  525. /**
  526. * Exporting and offline-exporting modules required. Export a chart to an image
  527. * locally in the user's browser.
  528. *
  529. * @param {Object} exportingOptions
  530. * Exporting options, the same as in {@link
  531. * Highcharts.Chart#exportChart}.
  532. * @param {Options} chartOptions
  533. * Additional chart options for the exported chart. For example a
  534. * different background color can be added here, or `dataLabels`
  535. * for export only.
  536. */
  537. Highcharts.Chart.prototype.exportChartLocal = function (
  538. exportingOptions,
  539. chartOptions
  540. ) {
  541. var chart = this,
  542. options = Highcharts.merge(chart.options.exporting, exportingOptions),
  543. fallbackToExportServer = function () {
  544. if (options.fallbackToExportServer === false) {
  545. if (options.error) {
  546. options.error(options);
  547. } else {
  548. throw 'Fallback to export server disabled';
  549. }
  550. } else {
  551. chart.exportChart(options);
  552. }
  553. },
  554. svgSuccess = function (svg) {
  555. // If SVG contains foreignObjects all exports except SVG will fail,
  556. // as both CanVG and svg2pdf choke on this. Gracefully fall back.
  557. if (
  558. svg.indexOf('<foreignObject') > -1 &&
  559. options.type !== 'image/svg+xml'
  560. ) {
  561. fallbackToExportServer();
  562. } else {
  563. Highcharts.downloadSVGLocal(
  564. svg,
  565. options,
  566. fallbackToExportServer
  567. );
  568. }
  569. };
  570. // If we are on IE and in styled mode, add a whitelist to the renderer for
  571. // inline styles that we want to pass through. There are so many styles by
  572. // default in IE that we don't want to blacklist them all.
  573. // Always fall back on:
  574. // - MS browsers: Embedded images JPEG/PNG, or any PDF
  575. // - Embedded images and PDF
  576. if (
  577. (
  578. isMSBrowser &&
  579. (
  580. options.type === 'application/pdf' ||
  581. chart.container.getElementsByTagName('image').length &&
  582. options.type !== 'image/svg+xml'
  583. )
  584. ) || (
  585. options.type === 'application/pdf' &&
  586. chart.container.getElementsByTagName('image').length
  587. )
  588. ) {
  589. fallbackToExportServer();
  590. return;
  591. }
  592. chart.getSVGForLocalExport(
  593. options,
  594. chartOptions,
  595. fallbackToExportServer,
  596. svgSuccess
  597. );
  598. };
  599. // Extend the default options to use the local exporter logic
  600. merge(true, Highcharts.getOptions().exporting, {
  601. libURL: 'https://code.highcharts.com/6.1.0/lib/',
  602. // When offline-exporting is loaded, redefine the menu item definitions
  603. // related to download.
  604. menuItemDefinitions: {
  605. downloadPNG: {
  606. textKey: 'downloadPNG',
  607. onclick: function () {
  608. this.exportChartLocal();
  609. }
  610. },
  611. downloadJPEG: {
  612. textKey: 'downloadJPEG',
  613. onclick: function () {
  614. this.exportChartLocal({
  615. type: 'image/jpeg'
  616. });
  617. }
  618. },
  619. downloadSVG: {
  620. textKey: 'downloadSVG',
  621. onclick: function () {
  622. this.exportChartLocal({
  623. type: 'image/svg+xml'
  624. });
  625. }
  626. },
  627. downloadPDF: {
  628. textKey: 'downloadPDF',
  629. onclick: function () {
  630. this.exportChartLocal({
  631. type: 'application/pdf'
  632. });
  633. }
  634. }
  635. }
  636. });
  637. }(Highcharts));
  638. }));