amexport.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. AmCharts.AmExport = AmCharts.Class({
  2. construct: function(chart, cfg, init ) {
  3. var _this = this;
  4. _this.DEBUG = false;
  5. _this.chart = chart;
  6. _this.canvas = null;
  7. _this.svgs = [];
  8. _this.userCFG = cfg;
  9. _this.buttonIcon = 'export.png';
  10. _this.exportPNG = true;
  11. _this.exportPDF = false;
  12. _this.exportJPG = false;
  13. _this.exportSVG = false;
  14. //_this.left;
  15. _this.right = 0;
  16. //_this.bottom;
  17. _this.top = 0;
  18. //_this.color;
  19. _this.buttonRollOverColor = "#EFEFEF";
  20. //_this.buttonColor = "#FFFFFF";
  21. //_this.buttonRollOverAlpha = 0.5;
  22. _this.textRollOverColor = "#CC0000";
  23. _this.buttonTitle = "Save chart as an image";
  24. _this.buttonAlpha = 0.75;
  25. _this.imageFileName = "amChart";
  26. _this.imageBackgroundColor = "#FFFFFF";
  27. if (init) {
  28. _this.init();
  29. }
  30. },
  31. toCoordinate:function(value){
  32. if(value === undefined){
  33. return "auto";
  34. }
  35. if(String(value).indexOf("%") != -1){
  36. return value;
  37. }
  38. else{
  39. return value + "px";
  40. }
  41. },
  42. init: function(){
  43. var _this = this;
  44. var formats = [];
  45. if (_this.exportPNG) {
  46. formats.push("png");
  47. }
  48. if (_this.exportPDF) {
  49. formats.push("pdf");
  50. }
  51. if (_this.exportJPG) {
  52. formats.push("jpg");
  53. }
  54. if (_this.exportSVG) {
  55. formats.push("svg");
  56. }
  57. var menuItems = [];
  58. if(formats.length == 1){
  59. var format = formats[0];
  60. menuItems.push({format:format, iconTitle:_this.buttonTitle, icon:_this.chart.pathToImages + _this.buttonIcon})
  61. }
  62. else if(formats.length > 1){
  63. var subItems = [];
  64. for(var i = 0; i < formats.length; i++){
  65. subItems.push({format:formats[i], title:formats[i].toUpperCase()});
  66. }
  67. menuItems.push({onclick: function() {}, icon:_this.chart.pathToImages + _this.buttonIcon, items:subItems})
  68. }
  69. var color = _this.color;
  70. if(color === undefined){
  71. color = _this.chart.color;
  72. }
  73. var buttonColor = _this.buttonColor;
  74. if(buttonColor === undefined){
  75. buttonColor = "transparent";
  76. }
  77. _this.cfg = {
  78. menuTop : _this.toCoordinate(_this.top),
  79. menuLeft : _this.toCoordinate(_this.left),
  80. menuRight : _this.toCoordinate(_this.right),
  81. menuBottom : _this.toCoordinate(_this.bottom),
  82. menuItems : menuItems,
  83. menuItemStyle: {
  84. backgroundColor : buttonColor,
  85. opacity :_this.buttonAlpha,
  86. rollOverBackgroundColor : _this.buttonRollOverColor,
  87. color : color,
  88. rollOverColor : _this.textRollOverColor,
  89. paddingTop : '6px',
  90. paddingRight : '6px',
  91. paddingBottom : '6px',
  92. paddingLeft : '6px',
  93. marginTop : '0px',
  94. marginRight : '0px',
  95. marginBottom : '0px',
  96. marginLeft : '0px',
  97. textAlign : 'left',
  98. textDecoration : 'none',
  99. fontFamily : _this.chart.fontFamily,
  100. fontSize : _this.chart.fontSize + 'px'
  101. },
  102. menuItemOutput: {
  103. backgroundColor : _this.imageBackgroundColor,
  104. fileName : _this.imageFileName,
  105. format : 'png',
  106. output : 'dataurlnewwindow',
  107. render : 'browser',
  108. dpi : 90,
  109. onclick : function(instance, config, event) {
  110. event.preventDefault();
  111. instance.output(config);
  112. }
  113. },
  114. removeImagery: true
  115. };
  116. _this.processing = {
  117. buffer: [],
  118. drawn: 0,
  119. timer: 0
  120. };
  121. // Config dependency adaption
  122. if (typeof(window.canvg) != 'undefined' && typeof(window.RGBColor) != 'undefined') {
  123. _this.cfg.menuItemOutput.render = 'canvg';
  124. }
  125. if (typeof(window.saveAs) != 'undefined') {
  126. _this.cfg.menuItemOutput.output = 'save';
  127. }
  128. if (AmCharts.isIE && AmCharts.IEversion < 10) {
  129. _this.cfg.menuItemOutput.output = 'dataurlnewwindow';
  130. }
  131. // Merge given configs
  132. var cfg = _this.userCFG;
  133. if (cfg) {
  134. cfg.menuItemOutput = AmCharts.extend(_this.cfg.menuItemOutput, cfg.menuItemOutput || {});
  135. cfg.menuItemStyle = AmCharts.extend(_this.cfg.menuItemStyle, cfg.menuItemStyle || {});
  136. _this.cfg = AmCharts.extend(_this.cfg, cfg);
  137. }
  138. // Add reference to chart
  139. _this.chart.AmExport = _this;
  140. // Listen to the drawer
  141. _this.chart.addListener('rendered', function() {
  142. _this.setup();
  143. });
  144. // DEBUG; Public reference
  145. if (_this.DEBUG) {
  146. window.AmExport = _this;
  147. }
  148. },
  149. /*
  150. Simple log function for internal purpose
  151. @param **args
  152. */
  153. log: function() {
  154. console.log('AmExport: ', arguments);
  155. },
  156. /* PUBLIC
  157. Prepares everything to get exported
  158. @param none
  159. */
  160. setup: function() {
  161. var _this = this;
  162. if (_this.DEBUG == 10) {
  163. _this.log('SETUP START');
  164. } // DEBUG
  165. if (!AmCharts.isIE || (AmCharts.isIE && AmCharts.IEversion > 9)) {
  166. // Build Buttons
  167. _this.generateButtons();
  168. if (_this.DEBUG == 10) {
  169. _this.log('SETUP END');
  170. } // DEBUG
  171. } else {
  172. if (_this.DEBUG == 10) {
  173. _this.log('< IE10 NOT SUPPORTED');
  174. } // DEBUG
  175. }
  176. },
  177. /* PUBLIC
  178. Decodes base64 string to binary array
  179. @param base64_string
  180. @copyright Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr
  181. */
  182. generateBinaryArray: function(base64_string) {
  183. var
  184. len = base64_string.length,
  185. buffer = new Uint8Array(len / 4 * 3 | 0),
  186. i = 0,
  187. outptr = 0,
  188. last = [0, 0],
  189. state = 0,
  190. save = 0,
  191. rank, code, undef, base64_ranks = new Uint8Array([
  192. 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
  193. ]);
  194. while (len--) {
  195. code = base64_string.charCodeAt(i++);
  196. rank = base64_ranks[code - 43];
  197. if (rank !== 255 && rank !== undef) {
  198. last[1] = last[0];
  199. last[0] = code;
  200. save = (save << 6) | rank;
  201. state++;
  202. if (state === 4) {
  203. buffer[outptr++] = save >>> 16;
  204. if (last[1] !== 61 /* padding character */ ) {
  205. buffer[outptr++] = save >>> 8;
  206. }
  207. if (last[0] !== 61 /* padding character */ ) {
  208. buffer[outptr++] = save;
  209. }
  210. state = 0;
  211. }
  212. }
  213. }
  214. // 2/3 chance there's going to be some null bytes at the end, but that
  215. // doesn't really matter with most image formats.
  216. // If it somehow matters for you, truncate the buffer up outptr.
  217. return buffer;
  218. },
  219. /*
  220. Creates blob object
  221. @param base64_datastring string
  222. @param type string
  223. */
  224. generateBlob: function(datastring, type) {
  225. var _this = this,
  226. header_end = type!='image/svg+xml'?datastring.indexOf(',') + 1:0,
  227. header = datastring.substring(0, header_end),
  228. data = datastring,
  229. blob = new Blob();
  230. if (header.indexOf('base64') != -1) {
  231. data = _this.generateBinaryArray(datastring.substring(header_end));
  232. }
  233. // Fake blob for IE
  234. if (AmCharts.isIE && AmCharts.IEversion < 10) {
  235. blob.data = data;
  236. blob.size = data.length;
  237. blob.type = type;
  238. blob.encoding = 'base64';
  239. } else {
  240. blob = new Blob([data], {
  241. type: type
  242. });
  243. }
  244. return blob;
  245. },
  246. /*
  247. Creates PDF object
  248. @param config object
  249. */
  250. generatePDF: function(cfg) {
  251. var _this = this,
  252. pdf = {
  253. output: function() {
  254. return '';
  255. }
  256. },
  257. data = _this.canvas.toDataURL('image/jpeg'), // JSPDF ONLY SUPPORTS JPG
  258. width = (_this.canvas.width * 25.4) / cfg.dpi,
  259. height = (_this.canvas.height * 25.4) / cfg.dpi;
  260. // Check
  261. if (window.jsPDF) {
  262. pdf = new jsPDF();
  263. if (pdf.addImage) {
  264. pdf.addImage(data, 'JPEG', 0, 0, width, height);
  265. } else {
  266. alert("Missing jsPDF plugin; Please add the 'addImage' plugin.");
  267. }
  268. } else {
  269. alert("Missing jsPDF lib; Don't forget to add the addImage plugin.");
  270. }
  271. return pdf;
  272. },
  273. /*
  274. Creates the CANVAS to receive the image data
  275. @param format void()
  276. @param callback; given callback function which returns the blob or datastring of the configured ouput type
  277. */
  278. output: function(cfg, externalCallback) {
  279. var _this = this;
  280. cfg = AmCharts.extend(AmCharts.extend({}, _this.cfg.menuItemOutput), cfg || {});
  281. if(_this.chart.prepareForExport){
  282. _this.chart.prepareForExport();
  283. }
  284. /* PRIVATE
  285. Callback function which gets called after the drawing process is done
  286. @param none
  287. */
  288. function internalCallback() {
  289. var data = null;
  290. var blob;
  291. if (_this.DEBUG == 10) {
  292. _this.log('OUTPUT', cfg.format);
  293. } // DEBUG
  294. // SVG
  295. if (cfg.format == 'image/svg+xml' || cfg.format == 'svg') {
  296. data = _this.generateSVG();
  297. blob = _this.generateBlob(data, 'image/svg+xml');
  298. if (cfg.output == 'save') {
  299. saveAs(blob, cfg.fileName + '.svg');
  300. } else if (cfg.output == 'datastring' || cfg.output == 'datauristring' || cfg.output == 'dataurlstring') {
  301. blob = 'data:image/svg+xml;base64,' + btoa(data);
  302. } else if (cfg.output == 'dataurlnewwindow') {
  303. window.open('data:image/svg+xml;base64,' + btoa(data));
  304. } else if (cfg.output == 'datauri' || cfg.output == 'dataurl') {
  305. location.href = 'data:image/svg+xml;base64,' + btoa(data);
  306. } else if (cfg.output == 'datastream') {
  307. location.href = 'data:image/octet-stream;base64,' + data;
  308. }
  309. if (externalCallback)
  310. externalCallback.apply(_this, [blob]);
  311. // PDF
  312. } else if (cfg.format == 'application/pdf' || cfg.format == 'pdf') {
  313. data = _this.generatePDF(cfg).output('dataurlstring');
  314. blob = _this.generateBlob(data, 'application/pdf');
  315. if (cfg.output == 'save') {
  316. saveAs(blob, cfg.fileName + '.pdf');
  317. } else if (cfg.output == 'datastring' || cfg.output == 'datauristring' || cfg.output == 'dataurlstring') {
  318. blob = data;
  319. } else if (cfg.output == 'dataurlnewwindow') {
  320. window.open(data);
  321. } else if (cfg.output == 'datauri' || cfg.output == 'dataurl') {
  322. location.href = data;
  323. } else if (cfg.output == 'datastream') {
  324. location.href = data.replace('application/pdf', 'application/octet-stream');
  325. }
  326. if (externalCallback)
  327. externalCallback.apply(_this, [blob]);
  328. // PNG
  329. } else if (cfg.format == 'image/png' || cfg.format == 'png') {
  330. data = _this.canvas.toDataURL('image/png');
  331. blob = _this.generateBlob(data, 'image/png');
  332. if (cfg.output == 'save') {
  333. saveAs(blob, cfg.fileName + '.png');
  334. } else if (cfg.output == 'datastring' || cfg.output == 'datauristring' || cfg.output == 'dataurlstring') {
  335. blob = data;
  336. } else if (cfg.output == 'dataurlnewwindow') {
  337. window.open(data);
  338. } else if (cfg.output == 'datauri' || cfg.output == 'dataurl') {
  339. location.href = data;
  340. } else if (cfg.output == 'datastream') {
  341. location.href = data.replace('image/png', 'image/octet-stream');
  342. }
  343. if (externalCallback)
  344. externalCallback.apply(_this, [blob]);
  345. // JPG
  346. } else if (cfg.format == 'image/jpeg' || cfg.format == 'jpeg' || cfg.format == 'jpg') {
  347. data = _this.canvas.toDataURL('image/jpeg');
  348. blob = _this.generateBlob(data, 'image/jpeg');
  349. if (cfg.output == 'save') {
  350. saveAs(blob, cfg.fileName + '.jpg');
  351. } else if (cfg.output == 'datastring' || cfg.output == 'datauristring' || cfg.output == 'dataurlstring') {
  352. blob = data;
  353. } else if (cfg.output == 'dataurlnewwindow') {
  354. window.open(data);
  355. } else if (cfg.output == 'datauri' || cfg.output == 'dataurl') {
  356. location.href = data;
  357. } else if (cfg.output == 'datastream') {
  358. location.href = data.replace('image/jpeg', 'image/octet-stream');
  359. }
  360. if (externalCallback)
  361. externalCallback.apply(_this, [blob]);
  362. }
  363. }
  364. return _this.generateOutput(cfg, internalCallback);
  365. },
  366. /* PUBLIC
  367. Polifies missing attributes to the SVG and replaces images to embedded base64 images
  368. @param none
  369. */
  370. polifySVG: function(svg) {
  371. var _this = this;
  372. // Recursive function to force the attributes
  373. function recursiveChange(svg, tag) {
  374. var items = svg.getElementsByTagName(tag);
  375. var i = items.length;
  376. while(i--) {
  377. if (_this.cfg.removeImagery) {
  378. items[i].parentNode.removeChild(items[i]);
  379. } else {
  380. var image = document.createElement('img');
  381. var canvas = document.createElement('canvas');
  382. var ctx = canvas.getContext('2d');
  383. canvas.width = items[i].getAttribute('width');
  384. canvas.height = items[i].getAttribute('height');
  385. image.src = items[i].getAttribute('xlink:href');
  386. image.width = items[i].getAttribute('width');
  387. image.height = items[i].getAttribute('height');
  388. try {
  389. ctx.drawImage(image, 0, 0, image.width, image.height);
  390. datastring = canvas.toDataURL(); // image.src; // canvas.toDataURL(); //
  391. } catch (err) {
  392. datastring = image.src; // image.src; // canvas.toDataURL(); //
  393. _this.log('Tainted canvas, reached browser CORS security; origin from imagery must be equal to the server!');
  394. throw new Error(err);
  395. }
  396. items[i].setAttribute('xlink:href', datastring);
  397. }
  398. if (_this.DEBUG == 10) {
  399. _this.log('POLIFIED', items[i]);
  400. } // DEBUG
  401. }
  402. }
  403. // Put some attrs to it; fixed 20/03/14 xmlns is required to produce a valid svg file
  404. if (AmCharts.IEversion == 0) {
  405. svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  406. if ( !_this.cfg.removeImagery ) {
  407. svg.setAttribute('xmlns:xlink','http://www.w3.org/1999/xlink');
  408. }
  409. }
  410. // DEBUG
  411. if (_this.DEBUG == 10) {
  412. _this.log('POLIFIED', svg);
  413. }
  414. // Force link adaption
  415. recursiveChange(svg, 'pattern');
  416. recursiveChange(svg, 'image');
  417. _this.svgs.push(svg);
  418. return svg;
  419. },
  420. /* PUBLIC
  421. Stacks multiple SVGs into one
  422. @param none
  423. */
  424. generateSVG: function() {
  425. var _this = this;
  426. var context = document.createElement('svg');
  427. context.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  428. context.setAttribute('xmlns:xlink','http://www.w3.org/1999/xlink');
  429. for (var i = 0; i < _this.processing.buffer.length; i++) {
  430. var group = document.createElement('g'),
  431. data = _this.processing.buffer[i];
  432. data[0].setAttribute('xmlns', 'http://www.w3.org/2000/svg');
  433. data[0].setAttribute('xmlns:xlink','http://www.w3.org/1999/xlink');
  434. group.setAttribute('transform', 'translate('+data[1].x+','+data[1].y+')');
  435. group.appendChild(data[0]);
  436. context.appendChild(group);
  437. }
  438. return new XMLSerializer().serializeToString(context);
  439. },
  440. /* PUBLIC
  441. Generates the canvas with the given SVGs and configured renderer
  442. @param callback; function(); gets called after drawing process on the canvas has been finished
  443. */
  444. generateOutput: function(cfg, callback) {
  445. var _this = this,
  446. svgs = _this.chart.div.getElementsByTagName('svg'),
  447. canvas = document.createElement('canvas'),
  448. context = canvas.getContext('2d'),
  449. offset = {
  450. y: 0,
  451. x: 0
  452. },
  453. tmp = {};
  454. // Reset
  455. _this.processing.buffer = [];
  456. _this.processing.drawn = 0;
  457. _this.canvas = canvas;
  458. _this.svgs = [];
  459. // Walkthroug SVGs
  460. if (_this.DEBUG == 10) {
  461. _this.log('START EXPORT');
  462. } // DEBUG
  463. if (_this.DEBUG == 10) {
  464. _this.log('START BUFFERING');
  465. } // DEBUG
  466. for (var i = 0; i < svgs.length; i++) {
  467. var parent = svgs[i].parentNode,
  468. svgX = Number(parent.style.left.slice(0, -2)),
  469. svgY = Number(parent.style.top.slice(0, -2)),
  470. svgClone = _this.polifySVG(svgs[i].cloneNode(true)),
  471. tmp = AmCharts.extend({}, offset);
  472. // Overtake parent position if given; fixed 20/03/14 distinguish between relativ and others
  473. if (parent.style.position == 'relative') {
  474. offset.x = svgX ? svgX : offset.x;
  475. offset.y = svgY ? svgY : offset.y;
  476. } else {
  477. offset.x = svgX;
  478. offset.y = svgY;
  479. }
  480. _this.processing.buffer.push([svgClone, AmCharts.extend({}, offset)]);
  481. // Put back from "cache"
  482. if (svgY && svgX) {
  483. offset = tmp;
  484. // New offset for next one
  485. } else {
  486. offset.y += svgY ? 0 : parent.offsetHeight;
  487. }
  488. if (_this.DEBUG == 10) {
  489. _this.log('BUFFERED', svgs[i], offset);
  490. } // DEBUG
  491. }
  492. if (_this.DEBUG == 10) {
  493. _this.log('END BUFFERING');
  494. } // DEBUG
  495. // Apply background
  496. if (_this.DEBUG == 10) {
  497. _this.log('START DRAWING', cfg.render);
  498. } // DEBUG
  499. if (_this.DEBUG == 10) {
  500. _this.log('FILL BACKGROUND');
  501. } // DEBUG
  502. canvas.id = AmCharts.getUniqueId();
  503. canvas.width = _this.chart.divRealWidth;
  504. canvas.height = _this.chart.divRealHeight;
  505. // Stockchart exception
  506. var adapted = {
  507. width: false,
  508. height: false
  509. };
  510. if ( _this.chart.periodSelector ) {
  511. if ( ['left','right'].indexOf(_this.chart.periodSelector.position) != -1 ) {
  512. canvas.width -= _this.chart.periodSelector.div.offsetWidth + 16;
  513. adapted.width = true;
  514. } else {
  515. canvas.height -= _this.chart.periodSelector.div.offsetHeight;
  516. adapted.height = true;
  517. }
  518. }
  519. if ( _this.chart.dataSetSelector ) {
  520. if ( ['left','right'].indexOf(_this.chart.dataSetSelector.position) != -1 ) {
  521. if ( !adapted.width ) {
  522. canvas.width -= _this.chart.dataSetSelector.div.offsetWidth + 16;
  523. }
  524. } else {
  525. canvas.height -= _this.chart.dataSetSelector.div.offsetHeight;
  526. }
  527. }
  528. // Set given background; jpeg default
  529. if (cfg.backgroundColor || cfg.format == 'image/jpeg') {
  530. context.fillStyle = cfg.backgroundColor || '#FFFFFF';
  531. context.fillRect(0, 0, canvas.width, canvas.height);
  532. }
  533. /* PRIVATE
  534. Recursive function to draw the images to the canvas;
  535. @param none;
  536. */
  537. function drawItWhenItsLoaded() {
  538. var img, buffer, offset, source;
  539. // DRAWING PROCESS DONE
  540. if (_this.processing.buffer.length == _this.processing.drawn || cfg.format == 'svg' ) {
  541. if (_this.DEBUG == 10) {
  542. _this.log('END DRAWING');
  543. } // DEBUG
  544. return callback();
  545. // LOOPING LUI
  546. } else {
  547. if (_this.DEBUG == 10) {
  548. _this.log('DRAW', _this.processing.drawn + 1, 'OF', _this.processing.buffer.length);
  549. } // DEBUG
  550. buffer = _this.processing.buffer[_this.processing.drawn];
  551. source = new XMLSerializer().serializeToString(buffer[0]); //source = 'data:image/svg+xml;base64,' + btoa();
  552. offset = buffer[1];
  553. if (_this.DEBUG == 10) {
  554. _this.log('SOURCE', source);
  555. } // DEBUG
  556. // NATIVE
  557. if (cfg.render == 'browser') {
  558. img = new Image();
  559. img.id = AmCharts.getUniqueId();
  560. source = 'data:image/svg+xml;base64,' + btoa(source);
  561. //img.crossOrigin = "Anonymous";
  562. img.onload = function() {
  563. context.drawImage(this, buffer[1].x, buffer[1].y);
  564. _this.processing.drawn++;
  565. if (_this.DEBUG == 10) {
  566. _this.log('ONLOAD', this);
  567. } // DEBUG
  568. drawItWhenItsLoaded();
  569. };
  570. img.onerror = function() {
  571. if (_this.DEBUG == 10) {
  572. _this.log('ONERROR', this);
  573. } // DEBUG
  574. context.drawImage(this, buffer[1].x, buffer[1].y);
  575. _this.processing.drawn++;
  576. drawItWhenItsLoaded();
  577. };
  578. img.src = source;
  579. if (_this.DEBUG == 10) {
  580. _this.log('ADD', img);
  581. } // DEBUG
  582. if (img.complete || typeof(img.complete) == 'undefined' || img.complete === undefined) {
  583. if (_this.DEBUG == 10) {
  584. _this.log('FORCE ONLOAD', img);
  585. } // DEBUG
  586. img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
  587. img.src = source;
  588. }
  589. // CANVG
  590. } else if (cfg.render == 'canvg') {
  591. canvg(canvas, source, {
  592. offsetX: offset.x,
  593. offsetY: offset.y,
  594. ignoreMouse: true,
  595. ignoreAnimation: true,
  596. ignoreDimensions: true,
  597. ignoreClear: true,
  598. renderCallback: function() {
  599. _this.processing.drawn++;
  600. drawItWhenItsLoaded();
  601. }
  602. });
  603. }
  604. }
  605. }
  606. return drawItWhenItsLoaded();
  607. },
  608. /*
  609. Generates the export menu to trigger the exportation
  610. @param none;
  611. */
  612. generateButtons: function() {
  613. var _this = this,
  614. div = document.createElement('div'),
  615. lvl = 0;
  616. // Push sublings
  617. function createList(items) {
  618. var ul = document.createElement('ul');
  619. ul.setAttribute('style', 'list-style: none; margin: 0; padding: 0;');
  620. // Walkthrough items
  621. for (var i = 0; i < items.length; i++) {
  622. var li = document.createElement('li'),
  623. img = document.createElement('img'),
  624. a = document.createElement('a'),
  625. item = items[i],
  626. children = null,
  627. itemStyle = AmCharts.extend(AmCharts.extend({}, _this.cfg.menuItemStyle), items[i]);
  628. // MERGE CFG
  629. item = AmCharts.extend(AmCharts.extend({}, _this.cfg.menuItemOutput), item);
  630. // ICON
  631. if (item['icon']) {
  632. img.alt = '';
  633. img.src = item['icon'];
  634. img.setAttribute('style', 'margin: 0 auto;border: none;outline: none');
  635. if (item['iconTitle']) {
  636. img.title = item['iconTitle'];
  637. }
  638. a.appendChild(img);
  639. }
  640. // TITLE; STYLING
  641. a.href = '#';
  642. if (item['title']) {
  643. img.setAttribute('style', 'margin-right: 5px;');
  644. a.innerHTML += item.title;
  645. }
  646. a.setAttribute('style', 'display: block;');
  647. AmCharts.extend(a.style, itemStyle);
  648. // ONCLICK
  649. a.onclick = item.onclick.bind(a, _this, item);
  650. li.appendChild(a);
  651. // APPEND SIBLINGS
  652. if (item.items) {
  653. children = createList(item.items);
  654. li.appendChild(children);
  655. li.onmouseover = function() {
  656. children.style.display = 'block';
  657. };
  658. li.onmouseout = function() {
  659. children.style.display = 'none';
  660. };
  661. children.style.display = 'none';
  662. }
  663. // Append to parent
  664. ul.appendChild(li);
  665. // Apply hover
  666. a.onmouseover = function() {
  667. this.style.backgroundColor = itemStyle.rollOverBackgroundColor;
  668. this.style.color = itemStyle.rollOverColor;
  669. this.style.borderColor = itemStyle.rollOverBorderColor;
  670. };
  671. a.onmouseout = function() {
  672. this.style.backgroundColor = itemStyle.backgroundColor;
  673. this.style.color = itemStyle.color;
  674. this.style.borderColor = itemStyle.borderColor;
  675. };
  676. }
  677. lvl++;
  678. if (_this.DEBUG == 10) {
  679. _this.log('MENU', ul);
  680. } // DEBUG
  681. return ul;
  682. }
  683. // Style wrapper; Push into chart div
  684. div.setAttribute('style', 'width:39px; height:28px; position: absolute;top:' + _this.cfg.menuTop + ';right:' + _this.cfg.menuRight + ';bottom:' + _this.cfg.menuBottom + ';left:' + _this.cfg.menuLeft + ';box-shadow:0px 0px 1px 0px rgba(0,0,0,0);');
  685. div.setAttribute('class', 'amExportButton');
  686. div.appendChild(createList(_this.cfg.menuItems));
  687. _this.chart.containerDiv.appendChild(div);
  688. }
  689. });