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