You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
8.1 KiB

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