1f5a508649d80aeca7419404b6ba7906215255cf.svn-base 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /**
  2. * @license Highcharts JS v4.2.5 (2016-05-06)
  3. * Client side exporting module
  4. *
  5. * (c) 2015 Torstein Honsi / Oystein Moseng
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. /*global MSBlobBuilder */
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. var win = Highcharts.win,
  18. nav = win.navigator,
  19. doc = win.document;
  20. // Dummy object so we can reuse our canvas-tools.js without errors
  21. Highcharts.CanVGRenderer = {};
  22. /**
  23. * Downloads a script and executes a callback when done.
  24. * @param {String} scriptLocation
  25. * @param {Function} callback
  26. */
  27. function getScript(scriptLocation, callback) {
  28. var head = doc.getElementsByTagName('head')[0],
  29. script = doc.createElement('script');
  30. script.type = 'text/javascript';
  31. script.src = scriptLocation;
  32. script.onload = callback;
  33. head.appendChild(script);
  34. }
  35. /**
  36. * Add a new method to the Chart object to perform a local download
  37. */
  38. Highcharts.Chart.prototype.exportChartLocal = function (exportingOptions, chartOptions) {
  39. var chart = this,
  40. options = Highcharts.merge(chart.options.exporting, exportingOptions),
  41. webKit = nav.userAgent.indexOf('WebKit') > -1 && nav.userAgent.indexOf('Chrome') < 0, // Webkit and not chrome
  42. scale = options.scale || 2,
  43. chartCopyContainer,
  44. domurl = win.URL || win.webkitURL || win,
  45. images,
  46. imagesEmbedded = 0,
  47. el,
  48. i,
  49. l,
  50. fallbackToExportServer = function () {
  51. if (options.fallbackToExportServer === false) {
  52. if (options.error) {
  53. options.error();
  54. } else {
  55. throw 'Fallback to export server disabled';
  56. }
  57. } else {
  58. chart.exportChart(options);
  59. }
  60. },
  61. // Get data:URL from image URL
  62. // Pass in callbacks to handle results. finallyCallback is always called at the end of the process. Supplying this callback is optional.
  63. // All callbacks receive three arguments: imageURL, imageType, and callbackArgs. callbackArgs is used only by callbacks and can contain whatever.
  64. imageToDataUrl = function (imageURL, imageType, callbackArgs, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) {
  65. var img = new win.Image(),
  66. taintedHandler,
  67. loadHandler = function () {
  68. var canvas = doc.createElement('canvas'),
  69. ctx = canvas.getContext && canvas.getContext('2d'),
  70. dataURL;
  71. try {
  72. if (!ctx) {
  73. noCanvasSupportCallback(imageURL, imageType, callbackArgs);
  74. } else {
  75. canvas.height = img.height * scale;
  76. canvas.width = img.width * scale;
  77. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  78. // Now we try to get the contents of the canvas.
  79. try {
  80. dataURL = canvas.toDataURL(imageType);
  81. successCallback(dataURL, imageType, callbackArgs);
  82. } catch (e) {
  83. // Failed - either tainted canvas or something else went horribly wrong
  84. if (e.name === 'SecurityError' || e.name === 'SECURITY_ERR' || e.message === 'SecurityError') {
  85. taintedHandler(imageURL, imageType, callbackArgs);
  86. } else {
  87. throw e;
  88. }
  89. }
  90. }
  91. } finally {
  92. if (finallyCallback) {
  93. finallyCallback(imageURL, imageType, callbackArgs);
  94. }
  95. }
  96. },
  97. // Image load failed (e.g. invalid URL)
  98. errorHandler = function () {
  99. failedLoadCallback(imageURL, imageType, callbackArgs);
  100. if (finallyCallback) {
  101. finallyCallback(imageURL, imageType, callbackArgs);
  102. }
  103. };
  104. // This is called on load if the image drawing to canvas failed with a security error.
  105. // We retry the drawing with crossOrigin set to Anonymous.
  106. taintedHandler = function () {
  107. img = new win.Image();
  108. taintedHandler = taintedCallback;
  109. img.crossOrigin = 'Anonymous'; // Must be set prior to loading image source
  110. img.onload = loadHandler;
  111. img.onerror = errorHandler;
  112. img.src = imageURL;
  113. };
  114. img.onload = loadHandler;
  115. img.onerror = errorHandler;
  116. img.src = imageURL;
  117. },
  118. // Get blob URL from SVG code. Falls back to normal data URI.
  119. svgToDataUrl = function (svg) {
  120. try {
  121. // Safari requires data URI since it doesn't allow navigation to blob URLs
  122. // Firefox has an issue with Blobs and internal references, leading to gradients not working using Blobs (#4550)
  123. if (!webKit && nav.userAgent.toLowerCase().indexOf('firefox') < 0) {
  124. return domurl.createObjectURL(new win.Blob([svg], { type: 'image/svg+xml;charset-utf-16' }));
  125. }
  126. } catch (e) {
  127. // Ignore
  128. }
  129. return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
  130. },
  131. // Download contents by dataURL/blob
  132. download = function (dataURL, extension) {
  133. var a = doc.createElement('a'),
  134. filename = (options.filename || 'chart') + '.' + extension,
  135. windowRef;
  136. // IE specific blob implementation
  137. if (nav.msSaveOrOpenBlob) {
  138. nav.msSaveOrOpenBlob(dataURL, filename);
  139. return;
  140. }
  141. // Try HTML5 download attr if supported
  142. if (a.download !== undefined) {
  143. a.href = dataURL;
  144. a.download = filename; // HTML5 download attribute
  145. a.target = '_blank';
  146. doc.body.appendChild(a);
  147. a.click();
  148. doc.body.removeChild(a);
  149. } else {
  150. // No download attr, just opening data URI
  151. try {
  152. windowRef = win.open(dataURL, 'chart');
  153. if (windowRef === undefined || windowRef === null) {
  154. throw 'Failed to open window';
  155. }
  156. } catch (e) {
  157. // window.open failed, trying location.href
  158. win.location.href = dataURL;
  159. }
  160. }
  161. },
  162. // Get data URL to an image of the chart and call download on it
  163. initiateDownload = function () {
  164. var svgurl,
  165. blob,
  166. imageType = options && options.type || 'image/png',
  167. imageExtension = imageType.split('/')[1],
  168. svg = chart.sanitizeSVG(chartCopyContainer.innerHTML); // SVG of chart copy
  169. // Initiate download depending on file type
  170. if (imageType === 'image/svg+xml') {
  171. // SVG download. In this case, we want to use Microsoft specific Blob if available
  172. try {
  173. if (nav.msSaveOrOpenBlob) {
  174. blob = new MSBlobBuilder();
  175. blob.append(svg);
  176. svgurl = blob.getBlob('image/svg+xml');
  177. } else {
  178. svgurl = svgToDataUrl(svg);
  179. }
  180. download(svgurl, 'svg');
  181. } catch (e) {
  182. fallbackToExportServer();
  183. }
  184. } else {
  185. // PNG/JPEG download - create bitmap from SVG
  186. // First, try to get PNG by rendering on canvas
  187. svgurl = svgToDataUrl(svg);
  188. imageToDataUrl(svgurl, imageType, { /* args */ }, function (imageURL) {
  189. // Success
  190. try {
  191. download(imageURL, imageExtension);
  192. } catch (e) {
  193. fallbackToExportServer();
  194. }
  195. }, function () {
  196. // Failed due to tainted canvas
  197. // Create new and untainted canvas
  198. var canvas = doc.createElement('canvas'),
  199. ctx = canvas.getContext('2d'),
  200. imageWidth = svg.match(/^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
  201. imageHeight = svg.match(/^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
  202. downloadWithCanVG = function () {
  203. ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight);
  204. try {
  205. download(nav.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL(imageType), imageExtension);
  206. } catch (e) {
  207. fallbackToExportServer();
  208. }
  209. };
  210. canvas.width = imageWidth;
  211. canvas.height = imageHeight;
  212. if (win.canvg) {
  213. // Use preloaded canvg
  214. downloadWithCanVG();
  215. } else {
  216. // Must load canVG first
  217. chart.showLoading();
  218. getScript(Highcharts.getOptions().global.canvasToolsURL, function () {
  219. chart.hideLoading();
  220. downloadWithCanVG();
  221. });
  222. }
  223. },
  224. // No canvas support
  225. fallbackToExportServer,
  226. // Failed to load image
  227. fallbackToExportServer,
  228. // Finally
  229. function () {
  230. try {
  231. domurl.revokeObjectURL(svgurl);
  232. } catch (e) {
  233. // Ignore
  234. }
  235. });
  236. }
  237. },
  238. // Success handler, we converted image to base64!
  239. embeddedSuccess = function (imageURL, imageType, callbackArgs) {
  240. ++imagesEmbedded;
  241. // Change image href in chart copy
  242. callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL);
  243. // Start download when done with the last image
  244. if (imagesEmbedded === images.length) {
  245. initiateDownload();
  246. }
  247. };
  248. // Hook into getSVG to get a copy of the chart copy's container
  249. Highcharts.wrap(Highcharts.Chart.prototype, 'getChartHTML', function (proceed) {
  250. chartCopyContainer = this.container.cloneNode(true);
  251. return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  252. });
  253. // Trigger hook to get chart copy
  254. chart.getSVGForExport(options, chartOptions);
  255. images = chartCopyContainer.getElementsByTagName('image');
  256. try {
  257. // If there are no images to embed, just go ahead and start the download process
  258. if (!images.length) {
  259. initiateDownload();
  260. }
  261. // Go through the images we want to embed
  262. for (i = 0, l = images.length; i < l; ++i) {
  263. el = images[i];
  264. imageToDataUrl(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), 'image/png', { imageElement: el },
  265. embeddedSuccess,
  266. // Tainted canvas
  267. fallbackToExportServer,
  268. // No canvas support
  269. fallbackToExportServer,
  270. // Failed to load source
  271. fallbackToExportServer
  272. );
  273. }
  274. } catch (e) {
  275. fallbackToExportServer();
  276. }
  277. };
  278. // Extend the default options to use the local exporter logic
  279. Highcharts.getOptions().exporting.buttons.contextButton.menuItems = [{
  280. textKey: 'printChart',
  281. onclick: function () {
  282. this.print();
  283. }
  284. }, {
  285. separator: true
  286. }, {
  287. textKey: 'downloadPNG',
  288. onclick: function () {
  289. this.exportChartLocal();
  290. }
  291. }, {
  292. textKey: 'downloadJPEG',
  293. onclick: function () {
  294. this.exportChartLocal({
  295. type: 'image/jpeg'
  296. });
  297. }
  298. }, {
  299. textKey: 'downloadSVG',
  300. onclick: function () {
  301. this.exportChartLocal({
  302. type: 'image/svg+xml'
  303. });
  304. }
  305. }];
  306. }));