1/* 2Plugin Name: amCharts Export 3Description: Adds export capabilities to amCharts products 4Author: Benjamin Maertz, amCharts 5Version: 1.4.13 6Author URI: http://www.amcharts.com/ 7 8Copyright 2015 amCharts 9 10Licensed under the Apache License, Version 2.0 (the "License"); 11you may not use this file except in compliance with the License. 12You may obtain a copy of the License at 13 14 http://www.apache.org/licenses/LICENSE-2.0 15 16Unless required by applicable law or agreed to in writing, software 17distributed under the License is distributed on an "AS IS" BASIS, 18WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19See the License for the specific language governing permissions and 20limitations under the License. 21 22Please note that the above license covers only this plugin. It by all means does 23not apply to any other amCharts products that are covered by different licenses. 24*/ 25 26/* 27 ** Polyfill translation 28 */ 29if ( !AmCharts.translations[ "export" ] ) { 30 AmCharts.translations[ "export" ] = {} 31} 32if ( !AmCharts.translations[ "export" ][ "en" ] ) { 33 AmCharts.translations[ "export" ][ "en" ] = { 34 "fallback.save.text": "CTRL + C to copy the data into the clipboard.", 35 "fallback.save.image": "Rightclick -> Save picture as... to save the image.", 36 37 "capturing.delayed.menu.label": "{{duration}}", 38 "capturing.delayed.menu.title": "Click to cancel", 39 40 "menu.label.print": "Print", 41 "menu.label.undo": "Undo", 42 "menu.label.redo": "Redo", 43 "menu.label.cancel": "Cancel", 44 45 "menu.label.save.image": "Download as ...", 46 "menu.label.save.data": "Save as ...", 47 48 "menu.label.draw": "Annotate ...", 49 "menu.label.draw.change": "Change ...", 50 "menu.label.draw.add": "Add ...", 51 "menu.label.draw.shapes": "Shape ...", 52 "menu.label.draw.colors": "Color ...", 53 "menu.label.draw.widths": "Size ...", 54 "menu.label.draw.opacities": "Opacity ...", 55 "menu.label.draw.text": "Text", 56 57 "menu.label.draw.modes": "Mode ...", 58 "menu.label.draw.modes.pencil": "Pencil", 59 "menu.label.draw.modes.line": "Line", 60 "menu.label.draw.modes.arrow": "Arrow" 61 } 62} 63 64/* 65 ** Polyfill export class 66 */ 67( function() { 68 AmCharts[ "export" ] = function( chart, config ) { 69 var _this = { 70 name: "export", 71 version: "1.4.13", 72 libs: { 73 async: true, 74 autoLoad: true, 75 reload: false, 76 resources: [ { 77 "pdfmake/pdfmake.js": [ "pdfmake/vfs_fonts.js" ], 78 "jszip/jszip.js": [ "xlsx/xlsx.js" ] 79 }, "fabric.js/fabric.js", "FileSaver.js/FileSaver.js" ], 80 namespaces: { 81 "pdfmake.js": "pdfMake", 82 "jszip.js": "JSZip", 83 "xlsx.js": "XLSX", 84 "fabric.js": "fabric", 85 "FileSaver.js": "saveAs" 86 } 87 }, 88 config: {}, 89 setup: { 90 chart: chart, 91 hasBlob: false, 92 wrapper: false 93 }, 94 drawing: { 95 enabled: false, 96 undos: [], 97 redos: [], 98 buffer: { 99 position: { 100 x1: 0, 101 y1: 0, 102 x2: 0, 103 y2: 0, 104 xD: 0, 105 yD: 0 106 } 107 }, 108 handler: { 109 undo: function( options, skipped ) { 110 var item = _this.drawing.undos.pop(); 111 if ( item ) { 112 item.selectable = true; 113 _this.drawing.redos.push( item ); 114 115 if ( item.action == "added" ) { 116 _this.setup.fabric.remove( item.target ); 117 } 118 119 var state = JSON.parse( item.state ); 120 item.target.set( state ); 121 122 if ( item.target instanceof fabric.Group ) { 123 _this.drawing.handler.change( { 124 color: state.cfg.color, 125 width: state.cfg.width, 126 opacity: state.cfg.opacity 127 }, true, item.target ); 128 } 129 130 _this.setup.fabric.renderAll(); 131 132 // RECALL 133 if ( item.state == item.target.recentState && !skipped ) { 134 _this.drawing.handler.undo( item, true ); 135 } 136 } 137 }, 138 redo: function( options, skipped ) { 139 var item = _this.drawing.redos.pop(); 140 if ( item ) { 141 item.selectable = true; 142 _this.drawing.undos.push( item ); 143 144 if ( item.action == "added" ) { 145 _this.setup.fabric.add( item.target ); 146 } 147 148 var state = JSON.parse( item.state ); 149 item.target.recentState = item.state; 150 item.target.set( state ); 151 152 if ( item.target instanceof fabric.Group ) { 153 _this.drawing.handler.change( { 154 color: state.cfg.color, 155 width: state.cfg.width, 156 opacity: state.cfg.opacity 157 }, true, item.target ); 158 } 159 160 _this.setup.fabric.renderAll(); 161 162 // RECALL 163 if ( item.action == "addified" ) { 164 _this.drawing.handler.redo(); 165 } 166 } 167 }, 168 done: function( options ) { 169 _this.drawing.buffer.enabled = false; 170 _this.drawing.undos = []; 171 _this.drawing.redos = []; 172 _this.createMenu( _this.config.menu ); 173 _this.setup.fabric.deactivateAll(); 174 175 if ( _this.setup.wrapper ) { 176 _this.setup.chart.containerDiv.removeChild( _this.setup.wrapper ); 177 _this.setup.wrapper = false; 178 } 179 }, 180 add: function( options ) { 181 var cfg = _this.deepMerge( { 182 top: _this.setup.fabric.height / 2, 183 left: _this.setup.fabric.width / 2 184 }, options || {} ); 185 var method = cfg.url.indexOf( ".svg" ) != -1 ? fabric.loadSVGFromURL : fabric.Image.fromURL; 186 187 method( cfg.url, function( objects, options ) { 188 var group = options !== undefined ? fabric.util.groupSVGElements( objects, options ) : objects; 189 var ratio = false; 190 191 // RESCALE ONLY IF IT EXCEEDS THE CANVAS 192 if ( group.height > _this.setup.fabric.height || group.width > _this.setup.fabric.width ) { 193 ratio = ( _this.setup.fabric.height / 2 ) / group.height; 194 } 195 196 if ( cfg.top > _this.setup.fabric.height ) { 197 cfg.top = _this.setup.fabric.height / 2; 198 } 199 200 if ( cfg.left > _this.setup.fabric.width ) { 201 cfg.left = _this.setup.fabric.width / 2; 202 } 203 204 group.set( { 205 originX: "center", 206 originY: "center", 207 top: cfg.top, 208 left: cfg.left, 209 width: ratio ? group.width * ratio : group.width, 210 height: ratio ? group.height * ratio : group.height, 211 fill: _this.drawing.color 212 } ); 213 _this.setup.fabric.add( group ); 214 } ); 215 }, 216 change: function( options, skipped, target ) { 217 var cfg = _this.deepMerge( {}, options || {} ); 218 var state, i1, rgba; 219 var current = target || _this.drawing.buffer.target; 220 var objects = current ? current._objects ? current._objects : [ current ] : null; 221 222 // UPDATE DRAWING OBJECT 223 if ( cfg.mode ) { 224 _this.drawing.mode = cfg.mode; 225 } 226 if ( cfg.width ) { 227 _this.drawing.width = cfg.width; 228 _this.drawing.fontSize = cfg.width * 3; 229 } 230 if ( cfg.fontSize ) { 231 _this.drawing.fontSize = cfg.fontSize; 232 } 233 if ( cfg.color ) { 234 _this.drawing.color = cfg.color; 235 } 236 if ( cfg.opacity ) { 237 _this.drawing.opacity = cfg.opacity; 238 } 239 240 // APPLY OPACITY ON CURRENT COLOR 241 rgba = new fabric.Color( _this.drawing.color ).getSource(); 242 rgba.pop(); 243 rgba.push( _this.drawing.opacity ); 244 _this.drawing.color = "rgba(" + rgba.join() + ")"; 245 _this.setup.fabric.freeDrawingBrush.color = _this.drawing.color; 246 _this.setup.fabric.freeDrawingBrush.width = _this.drawing.width; 247 248 // UPDATE CURRENT SELECTION 249 if ( current ) { 250 state = JSON.parse( current.recentState ).cfg; 251 252 // UPDATE GIVE OPTIONS ONLY 253 if ( state ) { 254 cfg.color = cfg.color || state.color; 255 cfg.width = cfg.width || state.width; 256 cfg.opacity = cfg.opacity || state.opacity; 257 cfg.fontSize = cfg.fontSize || cfg.width * 3; 258 259 rgba = new fabric.Color( cfg.color ).getSource(); 260 rgba.pop(); 261 rgba.push( cfg.opacity ); 262 cfg.color = "rgba(" + rgba.join() + ")"; 263 } 264 265 // UPDATE OBJECTS 266 for ( i1 = 0; i1 < objects.length; i1++ ) { 267 if ( 268 objects[ i1 ] instanceof fabric.Text || 269 objects[ i1 ] instanceof fabric.PathGroup || 270 objects[ i1 ] instanceof fabric.Triangle 271 ) { 272 if ( cfg.color || cfg.opacity ) { 273 objects[ i1 ].set( { 274 fill: cfg.color 275 } ); 276 } 277 if ( cfg.fontSize ) { 278 objects[ i1 ].set( { 279 fontSize: cfg.fontSize 280 } ); 281 } 282 } else if ( 283 objects[ i1 ] instanceof fabric.Path || 284 objects[ i1 ] instanceof fabric.Line 285 ) { 286 if ( current instanceof fabric.Group ) { 287 if ( cfg.color || cfg.opacity ) { 288 objects[ i1 ].set( { 289 stroke: cfg.color 290 } ); 291 } 292 } else { 293 if ( cfg.color || cfg.opacity ) { 294 objects[ i1 ].set( { 295 stroke: cfg.color 296 } ); 297 } 298 if ( cfg.width ) { 299 objects[ i1 ].set( { 300 strokeWidth: cfg.width 301 } ); 302 } 303 } 304 } 305 } 306 307 // ADD UNDO 308 if ( !skipped ) { 309 state = JSON.stringify( _this.deepMerge( current.saveState().originalState, { 310 cfg: { 311 color: cfg.color, 312 width: cfg.width, 313 opacity: cfg.opacity 314 } 315 } ) ); 316 current.recentState = state; 317 _this.drawing.redos = []; 318 _this.drawing.undos.push( { 319 action: "modified", 320 target: current, 321 state: state 322 } ); 323 } 324 325 _this.setup.fabric.renderAll(); 326 } 327 }, 328 text: function( options ) { 329 var cfg = _this.deepMerge( { 330 text: _this.i18l( "menu.label.draw.text" ), 331 top: _this.setup.fabric.height / 2, 332 left: _this.setup.fabric.width / 2, 333 fontSize: _this.drawing.fontSize, 334 fontFamily: _this.setup.chart.fontFamily || "Verdana", 335 fill: _this.drawing.color 336 }, options || {} ); 337 338 cfg.click = function() {}; 339 340 var text = new fabric.IText( cfg.text, cfg ); 341 342 _this.setup.fabric.add( text ); 343 _this.setup.fabric.setActiveObject( text ); 344 345 text.selectAll(); 346 text.enterEditing(); 347 348 return text; 349 }, 350 line: function( options ) { 351 var cfg = _this.deepMerge( { 352 x1: ( _this.setup.fabric.width / 2 ) - ( _this.setup.fabric.width / 10 ), 353 x2: ( _this.setup.fabric.width / 2 ) + ( _this.setup.fabric.width / 10 ), 354 y1: ( _this.setup.fabric.height / 2 ), 355 y2: ( _this.setup.fabric.height / 2 ), 356 angle: 90, 357 strokeLineCap: _this.drawing.lineCap, 358 arrow: _this.drawing.arrow, 359 color: _this.drawing.color, 360 width: _this.drawing.width, 361 group: [], 362 }, options || {} ); 363 var i1, arrow, arrowTop, arrowLeft; 364 var line = new fabric.Line( [ cfg.x1, cfg.y1, cfg.x2, cfg.y2 ], { 365 stroke: cfg.color, 366 strokeWidth: cfg.width, 367 strokeLineCap: cfg.strokeLineCap 368 } ); 369 370 cfg.group.push( line ); 371 372 if ( cfg.arrow ) { 373 cfg.angle = cfg.angle ? cfg.angle : _this.getAngle( cfg.x1, cfg.y1, cfg.x2, cfg.y2 ); 374 375 if ( cfg.arrow == "start" ) { 376 arrowTop = cfg.y1 + ( cfg.width / 2 ); 377 arrowLeft = cfg.x1 + ( cfg.width / 2 ); 378 } else if ( cfg.arrow == "middle" ) { 379 arrowTop = cfg.y2 + ( cfg.width / 2 ) - ( ( cfg.y2 - cfg.y1 ) / 2 ); 380 arrowLeft = cfg.x2 + ( cfg.width / 2 ) - ( ( cfg.x2 - cfg.x1 ) / 2 ); 381 } else { // arrow: end 382 arrowTop = cfg.y2 + ( cfg.width / 2 ); 383 arrowLeft = cfg.x2 + ( cfg.width / 2 ); 384 } 385 386 arrow = new fabric.Triangle( { 387 top: arrowTop, 388 left: arrowLeft, 389 fill: cfg.color, 390 height: cfg.width * 7, 391 width: cfg.width * 7, 392 angle: cfg.angle, 393 originX: "center", 394 originY: "bottom" 395 } ); 396 cfg.group.push( arrow ); 397 } 398 399 if ( cfg.action != "config" ) { 400 if ( cfg.arrow ) { 401 var group = new fabric.Group( cfg.group ); 402 group.set( { 403 cfg: cfg, 404 fill: cfg.color, 405 action: cfg.action, 406 selectable: true, 407 known: cfg.action == "change" 408 } ); 409 if ( cfg.action == "change" ) { 410 _this.setup.fabric.setActiveObject( group ); 411 } 412 _this.setup.fabric.add( group ); 413 return group; 414 } else { 415 _this.setup.fabric.add( line ); 416 return line; 417 } 418 } else { 419 for ( i1 = 0; i1 < cfg.group.length; i1++ ) { 420 cfg.group[ i1 ].noUndo = true; 421 _this.setup.fabric.add( cfg.group[ i1 ] ); 422 } 423 } 424 return cfg; 425 } 426 } 427 }, 428 defaults: { 429 position: "top-right", 430 fileName: "amCharts", 431 action: "download", 432 overflow: true, 433 path: ( ( chart.path || "" ) + "plugins/export/" ), 434 formats: { 435 JPG: { 436 mimeType: "image/jpg", 437 extension: "jpg", 438 capture: true 439 }, 440 PNG: { 441 mimeType: "image/png", 442 extension: "png", 443 capture: true 444 }, 445 SVG: { 446 mimeType: "text/xml", 447 extension: "svg", 448 capture: true 449 }, 450 PDF: { 451 mimeType: "application/pdf", 452 extension: "pdf", 453 capture: true 454 }, 455 CSV: { 456 mimeType: "text/plain", 457 extension: "csv" 458 }, 459 JSON: { 460 mimeType: "text/plain", 461 extension: "json" 462 }, 463 XLSX: { 464 mimeType: "application/octet-stream", 465 extension: "xlsx" 466 } 467 }, 468 fabric: { 469 backgroundColor: "#FFFFFF", 470 removeImages: true, 471 selection: false, 472 drawing: { 473 enabled: true, 474 arrow: "end", 475 lineCap: "butt", 476 mode: "pencil", 477 modes: [ "pencil", "line", "arrow" ], 478 color: "#000000", 479 colors: [ "#000000", "#FFFFFF", "#FF0000", "#00FF00", "#0000FF" ], 480 shapes: [ "11.svg", "14.svg", "16.svg", "17.svg", "20.svg", "27.svg" ], 481 width: 1, 482 fontSize: 11, 483 widths: [ 1, 5, 10, 15 ], 484 opacity: 1, 485 opacities: [ 1, 0.8, 0.6, 0.4, 0.2 ], 486 menu: undefined, 487 autoClose: true 488 } 489 }, 490 pdfMake: { 491 pageSize: "A4", 492 pageOrientation: "portrait", 493 images: {}, 494 content: [ "Saved from:", window.location.href, { 495 image: "reference", 496 fit: [ 523.28, 769.89 ] 497 } ] 498 }, 499 menu: undefined, 500 divId: null, 501 menuReviver: null, 502 menuWalker: null, 503 fallback: true, 504 keyListener: true, 505 fileListener: true 506 }, 507 508 /** 509 * Returns translated message, takes english as default 510 */ 511 i18l: function( key, language ) { 512 var lang = language ? langugage : _this.setup.chart.language ? _this.setup.chart.language : "en"; 513 var catalog = AmCharts.translations[ _this.name ][ lang ] || AmCharts.translations[ _this.name ][ "en" ]; 514 515 return catalog[ key ] || key; 516 }, 517 518 /** 519 * Generates download file; if unsupported offers fallback to save manually 520 */ 521 download: function( data, type, filename ) { 522 // SAVE 523 if ( window.saveAs && _this.setup.hasBlob ) { 524 var blob = _this.toBlob( { 525 data: data, 526 type: type 527 }, function( data ) { 528 saveAs( data, filename ); 529 } ); 530 531 // FALLBACK TEXTAREA 532 } else if ( _this.config.fallback && type == "text/plain" ) { 533 var div = document.createElement( "div" ); 534 var msg = document.createElement( "div" ); 535 var textarea = document.createElement( "textarea" ); 536 537 msg.innerHTML = _this.i18l( "fallback.save.text" ); 538 539 div.appendChild( msg ); 540 div.appendChild( textarea ); 541 msg.setAttribute( "class", "amcharts-export-fallback-message" ); 542 div.setAttribute( "class", "amcharts-export-fallback" ); 543 _this.setup.chart.containerDiv.appendChild( div ); 544 545 // FULFILL TEXTAREA AND PRESELECT 546 textarea.setAttribute( "readonly", "" ); 547 textarea.value = data; 548 textarea.focus(); 549 textarea.select(); 550 551 // UPDATE MENU 552 _this.createMenu( [ { 553 "class": "export-main export-close", 554 label: "Done", 555 click: function() { 556 _this.createMenu( _this.config.menu ); 557 _this.setup.chart.containerDiv.removeChild( div ); 558 } 559 } ] ); 560 561 // FALLBACK IMAGE 562 } else if ( _this.config.fallback && type.split( "/" )[ 0 ] == "image" ) { 563 var div = document.createElement( "div" ); 564 var msg = document.createElement( "div" ); 565 var img = _this.toImage( { 566 data: data 567 } ); 568 569 msg.innerHTML = _this.i18l( "fallback.save.image" ); 570 571 // FULFILL TEXTAREA AND PRESELECT 572 div.appendChild( msg ); 573 div.appendChild( img ); 574 msg.setAttribute( "class", "amcharts-export-fallback-message" ); 575 div.setAttribute( "class", "amcharts-export-fallback" ); 576 _this.setup.chart.containerDiv.appendChild( div ); 577 578 // UPDATE MENU 579 _this.createMenu( [ { 580 "class": "export-main export-close", 581 label: "Done", 582 click: function() { 583 _this.createMenu( _this.config.menu ); 584 _this.setup.chart.containerDiv.removeChild( div ); 585 } 586 } ] ); 587 588 // ERROR 589 } else { 590 throw new Error( "Unable to create file. Ensure saveAs (FileSaver.js) is supported." ); 591 } 592 return data; 593 }, 594 595 /** 596 * Generates script, links tags and places them into the document's head 597 * In case of reload it replaces the node to force the download 598 */ 599 loadResource: function( src, addons ) { 600 var i1, exist, node, item, check, type; 601 var url = src.indexOf( "//" ) != -1 ? src : [ _this.libs.path, src ].join( "" ); 602 603 function callback() { 604 if ( addons ) { 605 for ( i1 = 0; i1 < addons.length; i1++ ) { 606 _this.loadResource( addons[ i1 ] ); 607 } 608 } 609 } 610 611 if ( src.indexOf( ".js" ) != -1 ) { 612 node = document.createElement( "script" ); 613 node.setAttribute( "type", "text/javascript" ); 614 node.setAttribute( "src", url ); 615 if ( _this.libs.async ) { 616 node.setAttribute( "async", "" ); 617 } 618 619 } else if ( src.indexOf( ".css" ) != -1 ) { 620 node = document.createElement( "link" ); 621 node.setAttribute( "type", "text/css" ); 622 node.setAttribute( "rel", "stylesheet" ); 623 node.setAttribute( "href", url ); 624 } 625 626 // NODE CHECK 627 for ( i1 = 0; i1 < document.head.childNodes.length; i1++ ) { 628 item = document.head.childNodes[ i1 ]; 629 check = item ? ( item.src || item.href ) : false; 630 type = item ? item.tagName : false; 631 632 if ( item && check && check.indexOf( src ) != -1 ) { 633 if ( _this.libs.reload ) { 634 document.head.removeChild( item ); 635 } 636 exist = true; 637 break; 638 } 639 } 640 641 // NAMESPACE CHECK 642 for ( i1 in _this.libs.namespaces ) { 643 var namespace = _this.libs.namespaces[ i1 ]; 644 var check = src.toLowerCase(); 645 var item = i1.toLowerCase(); 646 if ( check.indexOf( item ) != -1 && window[ namespace ] !== undefined ) { 647 exist = true; 648 break; 649 } 650 } 651 652 if ( !exist || _this.libs.reload ) { 653 node.addEventListener( "load", callback ); 654 document.head.appendChild( node ); 655 } 656 657 }, 658 659 /** 660 * Walker to generate the script,link tags 661 */ 662 loadDependencies: function() { 663 var i1, i2; 664 if ( _this.libs.autoLoad ) { 665 for ( i1 = 0; i1 < _this.libs.resources.length; i1++ ) { 666 if ( _this.libs.resources[ i1 ] instanceof Object ) { 667 for ( i2 in _this.libs.resources[ i1 ] ) { 668 _this.loadResource( i2, _this.libs.resources[ i1 ][ i2 ] ); 669 } 670 } else { 671 _this.loadResource( _this.libs.resources[ i1 ] ); 672 } 673 } 674 } 675 }, 676 677 /** 678 * Converts string to number 679 */ 680 pxToNumber: function( attr, returnUndefined ) { 681 if ( !attr && returnUndefined ) { 682 return undefined; 683 } 684 return Number( String( attr ).replace( "px", "" ) ) || 0; 685 }, 686 687 /** 688 * Converts number to string 689 */ 690 numberToPx: function( attr ) { 691 return String( attr ) + "px"; 692 }, 693 694 /** 695 * Recursive method to merge the given objects together 696 * Overwrite flag replaces the value instead to crawl through 697 */ 698 deepMerge: function( a, b, overwrite ) { 699 var i1, v, type = b instanceof Array ? "array" : "object"; 700 701 for ( i1 in b ) { 702 // PREVENT METHODS 703 if ( type == "array" && isNaN( i1 ) ) { 704 continue; 705 } 706 707 v = b[ i1 ]; 708 709 // NEW 710 if ( a[ i1 ] == undefined || overwrite ) { 711 if ( v instanceof Array ) { 712 a[ i1 ] = new Array(); 713 } else if ( v instanceof Function ) { 714 a[ i1 ] = function() {}; 715 } else if ( v instanceof Date ) { 716 a[ i1 ] = new Date(); 717 } else if ( v instanceof Object ) { 718 a[ i1 ] = new Object(); 719 } else if ( v instanceof Number ) { 720 a[ i1 ] = new Number(); 721 } else if ( v instanceof String ) { 722 a[ i1 ] = new String(); 723 } 724 } 725 726 if ( 727 ( a instanceof Object || a instanceof Array ) && 728 ( v instanceof Object || v instanceof Array ) && 729 !( v instanceof Function || v instanceof Date || _this.isElement( v ) ) && 730 i1 != "chart" 731 ) { 732 _this.deepMerge( a[ i1 ], v, overwrite ); 733 } else { 734 if ( a instanceof Array && !overwrite ) { 735 a.push( v ); 736 } else { 737 a[ i1 ] = v; 738 } 739 } 740 } 741 return a; 742 }, 743 744 /** 745 * Checks if given argument is a valid node 746 */ 747 isElement: function( thingy ) { 748 return thingy instanceof Object && thingy && thingy.nodeType === 1; 749 }, 750 751 /** 752 * Checks if given argument contains a hashbang and returns it 753 */ 754 isHashbanged: function( thingy ) { 755 var str = String( thingy ).replace( /\"/g, "" ); 756 757 return str.slice( 0, 3 ) == "url" ? str.slice( str.indexOf( "#" ) + 1, str.length - 1 ) : false; 758 }, 759 760 /** 761 * Checks if given event has been thrown with pressed click / touch 762 */ 763 isPressed: function( event ) { 764 // IE EXCEPTION 765 if ( event.type == "mousemove" && event.which === 1 ) { 766 // IGNORE 767 768 // OTHERS 769 } else if ( 770 event.type == "touchmove" || 771 event.buttons === 1 || 772 event.button === 1 || 773 event.which === 1 774 ) { 775 _this.drawing.buffer.isPressed = true; 776 } else { 777 _this.drawing.buffer.isPressed = false; 778 } 779 return _this.drawing.buffer.isPressed; 780 }, 781 782 /** 783 * Checks if given source is within the current origin 784 */ 785 isTainted: function( source ) { 786 var origin = String( window.location.origin || window.location.protocol + "//" + window.location.hostname + ( window.location.port ? ':' + window.location.port : '' ) ); 787 788 // CHECK IF TAINTED 789 if ( 790 source && 791 source.indexOf( "//" ) != -1 && 792 source.indexOf( origin.replace( /.*:/, "" ) ) == -1 793 ) { 794 return true; 795 } 796 return false; 797 }, 798 799 /* 800 ** Checks several indicators for acceptance; 801 */ 802 isSupported: function() { 803 // CHECK CONFIG 804 if ( !_this.config.enabled ) { 805 return false; 806 } 807 808 // CHECK IE; ATTEMPT TO ACCESS HEAD ELEMENT 809 if ( AmCharts.isIE && AmCharts.IEversion <= 9 ) { 810 if ( !Array.prototype.indexOf || !document.head || _this.config.fallback === false ) { 811 return false; 812 } 813 } 814 return true; 815 }, 816 817 818 getAngle: function( x1, y1, x2, y2 ) { 819 var x = x2 - x1; 820 var y = y2 - y1; 821 var angle; 822 if ( x == 0 ) { 823 if ( y == 0 ) { 824 angle = 0; 825 } else if ( y > 0 ) { 826 angle = Math.PI / 2; 827 } else { 828 angle = Math.PI * 3 / 2; 829 } 830 } else if ( y == 0 ) { 831 if ( x > 0 ) { 832 angle = 0; 833 } else { 834 angle = Math.PI; 835 } 836 } else { 837 if ( x < 0 ) { 838 angle = Math.atan( y / x ) + Math.PI; 839 } else if ( y < 0 ) { 840 angle = Math.atan( y / x ) + ( 2 * Math.PI ); 841 } else { 842 angle = Math.atan( y / x ); 843 } 844 } 845 return angle * 180 / Math.PI; 846 }, 847 848 /** 849 * Recursive method which crawls upwards to gather the requested attribute 850 */ 851 gatherAttribute: function( elm, attr, limit, lvl ) { 852 var value, lvl = lvl ? lvl : 0, 853 limit = limit ? limit : 3; 854 if ( elm ) { 855 value = elm.getAttribute( attr ); 856 857 if ( !value && lvl < limit ) { 858 return _this.gatherAttribute( elm.parentNode, attr, limit, lvl + 1 ); 859 } 860 } 861 return value; 862 }, 863 864 /** 865 * Recursive method which crawls upwards to gather the requested classname 866 */ 867 gatherClassName: function( elm, className, limit, lvl ) { 868 var value, lvl = lvl ? lvl : 0, 869 limit = limit ? limit : 3; 870 871 if ( _this.isElement( elm ) ) { 872 value = ( elm.getAttribute( "class" ) || "" ).split( " " ).indexOf( className ) != -1; 873 874 if ( !value && lvl < limit ) { 875 return _this.gatherClassName( elm.parentNode, className, limit, lvl + 1 ); 876 } else if ( value ) { 877 value = elm; 878 } 879 } 880 return value; 881 }, 882 883 /** 884 * Collects the clip-paths and patterns 885 */ 886 gatherElements: function( group, cfg, images ) { 887 var i1, i2; 888 for ( i1 = 0; i1 < group.children.length; i1++ ) { 889 var childNode = group.children[ i1 ]; 890 891 // CLIPPATH 892 if ( childNode.tagName == "clipPath" ) { 893 var bbox = {}; 894 var transform = fabric.parseTransformAttribute( _this.gatherAttribute( childNode, "transform" ) ); 895 896 // HIDE SIBLINGS; GATHER IT'S DIMENSIONS 897 for ( i2 = 0; i2 < childNode.childNodes.length; i2++ ) { 898 childNode.childNodes[ i2 ].setAttribute( "fill", "transparent" ); 899 bbox = { 900 x: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "x" ) ), 901 y: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "y" ) ), 902 width: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "width" ) ), 903 height: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "height" ) ) 904 } 905 } 906 907 group.clippings[ childNode.id ] = { 908 svg: childNode, 909 bbox: bbox, 910 transform: transform 911 }; 912 913 // PATTERN 914 } else if ( childNode.tagName == "pattern" ) { 915 var props = { 916 node: childNode, 917 source: childNode.getAttribute( "xlink:href" ), 918 width: Number( childNode.getAttribute( "width" ) ), 919 height: Number( childNode.getAttribute( "height" ) ), 920 repeat: "repeat" 921 } 922 923 // GATHER BACKGROUND COLOR 924 for ( i2 = 0; i2 < childNode.childNodes.length; i2++ ) { 925 if ( childNode.childNodes[ i2 ].tagName == "rect" ) { 926 props.fill = childNode.childNodes[ i2 ].getAttribute( "fill" ); 927 } 928 } 929 930 // TAINTED 931 if ( cfg.removeImages && _this.isTainted( props.source ) ) { 932 group.patterns[ childNode.id ] = props.fill ? props.fill : "transparent"; 933 } else { 934 images.included++; 935 936 group.patterns[ props.node.id ] = props; 937 } 938 939 // IMAGES 940 } else if ( childNode.tagName == "image" ) { 941 images.included++; 942 943 // LOAD IMAGE MANUALLY; TO RERENDER THE CANVAS 944 fabric.Image.fromURL( childNode.getAttribute( "xlink:href" ), function( img ) { 945 images.loaded++; 946 } ); 947 } 948 } 949 return group; 950 }, 951 952 /* 953 ** GATHER MOUSE POSITION; 954 */ 955 gatherPosition: function( event, type ) { 956 var ref = _this.drawing.buffer.position; 957 var ivt = fabric.util.invertTransform( _this.setup.fabric.viewportTransform ); 958 var pos; 959 960 if ( event.type == "touchmove" ) { 961 if ( "touches" in event ) { 962 event = event.touches[ 0 ]; 963 } else if ( "changedTouches" in event ) { 964 event = event.changedTouches[ 0 ]; 965 } 966 } 967 968 pos = fabric.util.transformPoint( _this.setup.fabric.getPointer( event, true ), ivt ); 969 970 if ( type == 1 ) { 971 ref.x1 = pos.x; 972 ref.y1 = pos.y; 973 } 974 975 ref.x2 = pos.x; 976 ref.y2 = pos.y; 977 ref.xD = ( ref.x1 - ref.x2 ) < 0 ? ( ref.x1 - ref.x2 ) * -1 : ( ref.x1 - ref.x2 ); 978 ref.yD = ( ref.y1 - ref.y2 ) < 0 ? ( ref.y1 - ref.y2 ) * -1 : ( ref.y1 - ref.y2 ); 979 980 return ref; 981 }, 982 983 /** 984 * Method to capture the current state of the chart 985 */ 986 capture: function( options, callback ) { 987 var i1; 988 var cfg = _this.deepMerge( _this.deepMerge( {}, _this.config.fabric ), options || {} ); 989 var groups = []; 990 var offset = { 991 x: 0, 992 y: 0, 993 pX: 0, 994 pY: 0, 995 width: _this.setup.chart.divRealWidth, 996 height: _this.setup.chart.divRealHeight 997 }; 998 var images = { 999 loaded: 0, 1000 included: 0 1001 } 1002 1003 fabric.ElementsParser.prototype.resolveGradient = function( obj, property ) { 1004 1005 var instanceFillValue = obj.get( property ); 1006 if ( !( /^url\(/ ).test( instanceFillValue ) ) { 1007 return; 1008 } 1009 var gradientId = instanceFillValue.slice( instanceFillValue.indexOf( "#" ) + 1, instanceFillValue.length - 1 ); 1010 if ( fabric.gradientDefs[ this.svgUid ][ gradientId ] ) { 1011 obj.set( property, fabric.Gradient.fromElement( fabric.gradientDefs[ this.svgUid ][ gradientId ], obj ) ); 1012 } 1013 }; 1014 1015 // BEFORE CAPTURING 1016 _this.handleCallback( cfg.beforeCapture, cfg ); 1017 1018 // GATHER SVGS 1019 var svgs = _this.setup.chart.containerDiv.getElementsByTagName( "svg" ); 1020 for ( i1 = 0; i1 < svgs.length; i1++ ) { 1021 var group = { 1022 svg: svgs[ i1 ], 1023 parent: svgs[ i1 ].parentNode, 1024 children: svgs[ i1 ].getElementsByTagName( "*" ), 1025 offset: { 1026 x: 0, 1027 y: 0 1028 }, 1029 patterns: {}, 1030 clippings: {} 1031 } 1032 1033 // GATHER ELEMENTS 1034 group = _this.gatherElements( group, cfg, images ); 1035 1036 // APPEND GROUP 1037 groups.push( group ); 1038 } 1039 1040 // GATHER EXTERNAL LEGEND 1041 if ( _this.config.legend && _this.setup.chart.legend && _this.setup.chart.legend.position == "outside" ) { 1042 var group = { 1043 svg: _this.setup.chart.legend.container.container, 1044 parent: _this.setup.chart.legend.container.container.parentNode, 1045 children: _this.setup.chart.legend.container.container.getElementsByTagName( "*" ), 1046 offset: { 1047 x: 0, 1048 y: 0 1049 }, 1050 legend: { 1051 type: [ "top", "left" ].indexOf( _this.config.legend.position ) != -1 ? "unshift" : "push", 1052 position: _this.config.legend.position, 1053 width: _this.config.legend.width ? _this.config.legend.width : _this.setup.chart.legend.container.width, 1054 height: _this.config.legend.height ? _this.config.legend.height : _this.setup.chart.legend.container.height 1055 }, 1056 patterns: {}, 1057 clippings: {} 1058 } 1059 1060 // ADAPT CANVAS DIMENSIONS 1061 if ( [ "left", "right" ].indexOf( group.legend.position ) != -1 ) { 1062 offset.width += group.legend.width; 1063 offset.height = group.legend.height > offset.height ? group.legend.height : offset.height; 1064 } else if ( [ "top", "bottom" ].indexOf( group.legend.position ) != -1 ) { 1065 offset.height += group.legend.height; 1066 } 1067 1068 // GATHER ELEMENTS 1069 group = _this.gatherElements( group, cfg, images ); 1070 1071 // PRE/APPEND SVG 1072 groups[ group.legend.type ]( group ); 1073 } 1074 1075 // CLEAR IF EXIST 1076 _this.drawing.buffer.enabled = cfg.action == "draw"; 1077 1078 _this.setup.wrapper = document.createElement( "div" ); 1079 _this.setup.wrapper.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-canvas" ); 1080 _this.setup.chart.containerDiv.appendChild( _this.setup.wrapper ); 1081 1082 // STOCK CHART; SELECTOR OFFSET 1083 if ( _this.setup.chart.type == "stock" ) { 1084 var padding = { 1085 top: 0, 1086 right: 0, 1087 bottom: 0, 1088 left: 0 1089 } 1090 if ( _this.setup.chart.leftContainer ) { 1091 offset.width -= _this.setup.chart.leftContainer.offsetWidth; 1092 padding.left = _this.setup.chart.leftContainer.offsetWidth + ( _this.setup.chart.panelsSettings.panelSpacing * 2 ); 1093 } 1094 if ( _this.setup.chart.rightContainer ) { 1095 offset.width -= _this.setup.chart.rightContainer.offsetWidth; 1096 padding.right = _this.setup.chart.rightContainer.offsetWidth + ( _this.setup.chart.panelsSettings.panelSpacing * 2 ); 1097 } 1098 if ( _this.setup.chart.periodSelector && [ "top", "bottom" ].indexOf( _this.setup.chart.periodSelector.position ) != -1 ) { 1099 offset.height -= _this.setup.chart.periodSelector.offsetHeight + _this.setup.chart.panelsSettings.panelSpacing; 1100 padding[ _this.setup.chart.periodSelector.position ] += _this.setup.chart.periodSelector.offsetHeight + _this.setup.chart.panelsSettings.panelSpacing; 1101 } 1102 if ( _this.setup.chart.dataSetSelector && [ "top", "bottom" ].indexOf( _this.setup.chart.dataSetSelector.position ) != -1 ) { 1103 offset.height -= _this.setup.chart.dataSetSelector.offsetHeight; 1104 padding[ _this.setup.chart.dataSetSelector.position ] += _this.setup.chart.dataSetSelector.offsetHeight; 1105 } 1106 1107 // APPLY OFFSET ON WRAPPER 1108 _this.setup.wrapper.style.paddingTop = _this.numberToPx( padding.top ); 1109 _this.setup.wrapper.style.paddingRight = _this.numberToPx( padding.right ); 1110 _this.setup.wrapper.style.paddingBottom = _this.numberToPx( padding.bottom ); 1111 _this.setup.wrapper.style.paddingLeft = _this.numberToPx( padding.left ); 1112 } 1113 1114 // CREATE CANVAS 1115 _this.setup.canvas = document.createElement( "canvas" ); 1116 _this.setup.wrapper.appendChild( _this.setup.canvas ); 1117 _this.setup.fabric = new fabric.Canvas( _this.setup.canvas, _this.deepMerge( { 1118 width: offset.width, 1119 height: offset.height, 1120 isDrawingMode: true 1121 }, cfg ) ); 1122 1123 // REAPPLY FOR SOME REASON 1124 _this.deepMerge( _this.setup.fabric, cfg ); 1125 _this.deepMerge( _this.setup.fabric.freeDrawingBrush, cfg.drawing ); 1126 1127 // RELIABLE VARIABLES; UPDATE DRAWING 1128 _this.deepMerge( _this.drawing, cfg.drawing ); 1129 _this.drawing.handler.change( cfg.drawing ); 1130 1131 // OBSERVE MOUSE EVENTS 1132 _this.setup.fabric.on( "mouse:down", function( e ) { 1133 var p = _this.gatherPosition( e.e, 1 ); 1134 _this.drawing.buffer.pressedTS = Number( new Date() ); 1135 _this.isPressed( e.e ); 1136 } ); 1137 _this.setup.fabric.on( "mouse:move", function( e ) { 1138 var p = _this.gatherPosition( e.e, 2 ); 1139 _this.isPressed( e.e ); 1140 1141 // CREATE INITIAL LINE / ARROW; JUST ON LEFT CLICK 1142 if ( _this.drawing.buffer.isPressed && !_this.drawing.buffer.line ) { 1143 if ( !_this.drawing.buffer.isSelected && _this.drawing.mode != "pencil" && ( p.xD > 5 || p.xD > 5 ) ) { 1144 _this.drawing.buffer.hasLine = true; 1145 _this.setup.fabric.isDrawingMode = false; 1146 _this.setup.fabric._onMouseUpInDrawingMode( e ); 1147 _this.drawing.buffer.line = _this.drawing.handler.line( { 1148 x1: p.x1, 1149 y1: p.y1, 1150 x2: p.x2, 1151 y2: p.y2, 1152 arrow: _this.drawing.mode == "line" ? false : _this.drawing.arrow, 1153 action: "config" 1154 } ); 1155 } 1156 } 1157 1158 // UPDATE LINE / ARROW 1159 if ( _this.drawing.buffer.line ) { 1160 var obj, top, left; 1161 var l = _this.drawing.buffer.line; 1162 1163 l.x2 = p.x2; 1164 l.y2 = p.y2; 1165 1166 for ( i1 = 0; i1 < l.group.length; i1++ ) { 1167 obj = l.group[ i1 ]; 1168 1169 if ( obj instanceof fabric.Line ) { 1170 obj.set( { 1171 x2: l.x2, 1172 y2: l.y2 1173 } ); 1174 } else if ( obj instanceof fabric.Triangle ) { 1175 l.angle = ( _this.getAngle( l.x1, l.y1, l.x2, l.y2 ) + 90 ); 1176 1177 if ( l.arrow == "start" ) { 1178 top = l.y1 + ( l.width / 2 ); 1179 left = l.x1 + ( l.width / 2 ); 1180 } else if ( l.arrow == "middle" ) { 1181 top = l.y2 + ( l.width / 2 ) - ( ( l.y2 - l.y1 ) / 2 ); 1182 left = l.x2 + ( l.width / 2 ) - ( ( l.x2 - l.x1 ) / 2 ); 1183 } else { // arrow: end 1184 top = l.y2 + ( l.width / 2 ); 1185 left = l.x2 + ( l.width / 2 ); 1186 } 1187 1188 obj.set( { 1189 top: top, 1190 left: left, 1191 angle: l.angle 1192 } ); 1193 } 1194 } 1195 _this.setup.fabric.renderAll(); 1196 } 1197 } ); 1198 _this.setup.fabric.on( "mouse:up", function( e ) { 1199 // SELECT TARGET 1200 if ( Number( new Date() ) - _this.drawing.buffer.pressedTS < 200 ) { 1201 var target = _this.setup.fabric.findTarget( e.e ); 1202 if ( target && target.selectable ) { 1203 _this.setup.fabric.setActiveObject( target ); 1204 } 1205 } 1206 1207 // UPDATE LINE / ARROW 1208 if ( _this.drawing.buffer.line ) { 1209 for ( i1 = 0; i1 < _this.drawing.buffer.line.group.length; i1++ ) { 1210 _this.drawing.buffer.line.group[ i1 ].remove(); 1211 } 1212 delete _this.drawing.buffer.line.action; 1213 delete _this.drawing.buffer.line.group; 1214 _this.drawing.handler.line( _this.drawing.buffer.line ); 1215 } 1216 _this.drawing.buffer.line = false; 1217 _this.drawing.buffer.hasLine = false; 1218 _this.drawing.buffer.isPressed = false; 1219 } ); 1220 1221 // OBSERVE OBJECT SELECTION 1222 _this.setup.fabric.on( "object:selected", function( e ) { 1223 _this.drawing.buffer.isSelected = true; 1224 _this.drawing.buffer.target = e.target; 1225 _this.setup.fabric.isDrawingMode = false; 1226 } ); 1227 _this.setup.fabric.on( "selection:cleared", function( e ) { 1228 _this.drawing.buffer.onMouseDown = _this.setup.fabric.freeDrawingBrush.onMouseDown; 1229 _this.drawing.buffer.target = false; 1230 1231 // FREEHAND WORKAROUND 1232 if ( _this.drawing.buffer.isSelected ) { 1233 _this.setup.fabric._isCurrentlyDrawing = false; 1234 _this.setup.fabric.freeDrawingBrush.onMouseDown = function() {}; 1235 } 1236 1237 // DELAYED DESELECTION TO PREVENT DRAWING 1238 setTimeout( function() { 1239 _this.drawing.buffer.isSelected = false; 1240 _this.setup.fabric.isDrawingMode = true; 1241 _this.setup.fabric.freeDrawingBrush.onMouseDown = _this.drawing.buffer.onMouseDown; 1242 }, 10 ); 1243 } ); 1244 _this.setup.fabric.on( "path:created", function( e ) { 1245 var item = e.path; 1246 if ( Number( new Date() ) - _this.drawing.buffer.pressedTS < 200 || _this.drawing.buffer.hasLine ) { 1247 _this.setup.fabric.remove( item ); 1248 _this.setup.fabric.renderAll(); 1249 return; 1250 } 1251 } ); 1252 1253 // OBSERVE OBJECT MODIFICATIONS 1254 _this.setup.fabric.on( "object:added", function( e ) { 1255 var item = e.target; 1256 var state = _this.deepMerge( item.saveState().originalState, { 1257 cfg: { 1258 color: _this.drawing.color, 1259 width: _this.drawing.width, 1260 opacity: _this.drawing.opacity, 1261 fontSize: _this.drawing.fontSize 1262 } 1263 } ); 1264 1265 if ( Number( new Date() ) - _this.drawing.buffer.pressedTS < 200 && !item.noUndo ) { 1266 _this.setup.fabric.remove( item ); 1267 _this.setup.fabric.renderAll(); 1268 return; 1269 } 1270 1271 state = JSON.stringify( state ); 1272 item.recentState = state; 1273 1274 if ( item.selectable && !item.known && !item.noUndo ) { 1275 _this.drawing.undos.push( { 1276 action: "added", 1277 target: item, 1278 state: state 1279 } ); 1280 _this.drawing.undos.push( { 1281 action: "addified", 1282 target: item, 1283 state: state 1284 } ); 1285 _this.drawing.redos = []; 1286 } 1287 1288 item.known = true; 1289 _this.setup.fabric.isDrawingMode = true; 1290 } ); 1291 _this.setup.fabric.on( "object:modified", function( e ) { 1292 var item = e.target; 1293 var recentState = JSON.parse( item.recentState ); 1294 var state = _this.deepMerge( item.saveState().originalState, { 1295 cfg: recentState.cfg 1296 } ); 1297 1298 state = JSON.stringify( state ); 1299 item.recentState = state; 1300 1301 _this.drawing.undos.push( { 1302 action: "modified", 1303 target: item, 1304 state: state 1305 } ); 1306 1307 _this.drawing.redos = []; 1308 } ); 1309 _this.setup.fabric.on( "text:changed", function( e ) { 1310 var item = e.target; 1311 clearTimeout( item.timer ); 1312 item.timer = setTimeout( function() { 1313 var state = JSON.stringify( item.saveState().originalState ); 1314 1315 item.recentState = state; 1316 1317 _this.drawing.redos = []; 1318 _this.drawing.undos.push( { 1319 action: "modified", 1320 target: item, 1321 state: state 1322 } ); 1323 }, 250 ); 1324 } ); 1325 1326 // DRAWING 1327 if ( _this.drawing.buffer.enabled ) { 1328 _this.setup.wrapper.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-canvas active" ); 1329 _this.setup.wrapper.style.backgroundColor = cfg.backgroundColor; 1330 _this.setup.wrapper.style.display = "block"; 1331 1332 } else { 1333 _this.setup.wrapper.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-canvas" ); 1334 _this.setup.wrapper.style.display = "none"; 1335 } 1336 1337 for ( i1 = 0; i1 < groups.length; i1++ ) { 1338 var group = groups[ i1 ]; 1339 var isLegend = _this.gatherClassName( group.parent, _this.setup.chart.classNamePrefix + "-legend-div", 1 ); 1340 var isPanel = _this.gatherClassName( group.parent, _this.setup.chart.classNamePrefix + "-stock-panel-div" ); 1341 var isScrollbar = _this.gatherClassName( group.parent, _this.setup.chart.classNamePrefix + "-scrollbar-chart-div" ); 1342 1343 // STOCK CHART; SVG OFFSET;; SVG OFFSET 1344 if ( _this.setup.chart.type == "stock" && _this.setup.chart.legendSettings.position ) { 1345 1346 // TOP / BOTTOM 1347 if ( [ "top", "bottom" ].indexOf( _this.setup.chart.legendSettings.position ) != -1 ) { 1348 1349 // POSITION; ABSOLUTE 1350 if ( group.parent.style.top && group.parent.style.left ) { 1351 group.offset.y = _this.pxToNumber( group.parent.style.top ); 1352 group.offset.x = _this.pxToNumber( group.parent.style.left ); 1353 1354 // POSITION; RELATIVE 1355 } else { 1356 group.offset.x = offset.x; 1357 group.offset.y = offset.y; 1358 offset.y += _this.pxToNumber( group.parent.style.height ); 1359 1360 // LEGEND; OFFSET 1361 if ( isPanel ) { 1362 offset.pY = _this.pxToNumber( isPanel.style.marginTop ); 1363 group.offset.y += offset.pY; 1364 1365 // SCROLLBAR; OFFSET 1366 } else if ( isScrollbar ) { 1367 group.offset.y += offset.pY; 1368 } 1369 } 1370 1371 // LEFT / RIGHT 1372 } else if ( [ "left", "right" ].indexOf( _this.setup.chart.legendSettings.position ) != -1 ) { 1373 group.offset.y = _this.pxToNumber( group.parent.style.top ) + offset.pY; 1374 group.offset.x = _this.pxToNumber( group.parent.style.left ) + offset.pX; 1375 1376 // LEGEND; OFFSET 1377 if ( isLegend ) { 1378 offset.pY += _this.pxToNumber( isPanel.style.height ) + _this.setup.chart.panelsSettings.panelSpacing; 1379 1380 // SCROLLBAR; OFFSET 1381 } else if ( isScrollbar ) { 1382 group.offset.y -= _this.setup.chart.panelsSettings.panelSpacing; 1383 } 1384 } 1385 1386 // REGULAR CHARTS; SVG OFFSET 1387 } else { 1388 // POSITION; ABSOLUTE 1389 if ( group.parent.style.position == "absolute" ) { 1390 group.offset.absolute = true; 1391 group.offset.top = _this.pxToNumber( group.parent.style.top ); 1392 group.offset.right = _this.pxToNumber( group.parent.style.right, true ); 1393 group.offset.bottom = _this.pxToNumber( group.parent.style.bottom, true ); 1394 group.offset.left = _this.pxToNumber( group.parent.style.left ); 1395 group.offset.width = _this.pxToNumber( group.parent.style.width ); 1396 group.offset.height = _this.pxToNumber( group.parent.style.height ); 1397 1398 // POSITION; RELATIVE 1399 } else if ( group.parent.style.top && group.parent.style.left ) { 1400 group.offset.y = _this.pxToNumber( group.parent.style.top ); 1401 group.offset.x = _this.pxToNumber( group.parent.style.left ); 1402 1403 // POSITION; GENERIC 1404 } else { 1405 1406 // EXTERNAL LEGEND 1407 if ( group.legend ) { 1408 if ( group.legend.position == "left" ) { 1409 offset.x += group.legend.width; 1410 } else if ( group.legend.position == "right" ) { 1411 group.offset.x += offset.width - group.legend.width; 1412 } else if ( group.legend.position == "top" ) { 1413 offset.y += group.legend.height; 1414 } else if ( group.legend.position == "bottom" ) { 1415 group.offset.y += offset.height - group.legend.height; // OFFSET.Y 1416 } 1417 1418 // NORMAL 1419 } else { 1420 group.offset.x = offset.x; 1421 group.offset.y = offset.y + offset.pY; 1422 offset.y += _this.pxToNumber( group.parent.style.height ); 1423 } 1424 } 1425 1426 // PANEL 1427 if ( isLegend && isPanel && isPanel.style.marginTop ) { 1428 offset.y += _this.pxToNumber( isPanel.style.marginTop ); 1429 group.offset.y += _this.pxToNumber( isPanel.style.marginTop ); 1430 } 1431 } 1432 1433 // ADD TO CANVAS 1434 fabric.parseSVGDocument( group.svg, ( function( group ) { 1435 return function( objects, options ) { 1436 var i1; 1437 var g = fabric.util.groupSVGElements( objects, options ); 1438 var paths = []; 1439 var tmp = { 1440 selectable: false 1441 }; 1442 1443 // GROUP OFFSET; ABSOLUTE 1444 if ( group.offset.absolute ) { 1445 if ( group.offset.bottom !== undefined ) { 1446 tmp.top = offset.height - group.offset.height - group.offset.bottom; 1447 } else { 1448 tmp.top = group.offset.top; 1449 } 1450 1451 if ( group.offset.right !== undefined ) { 1452 tmp.left = offset.width - group.offset.width - group.offset.right; 1453 } else { 1454 tmp.left = group.offset.left; 1455 } 1456 1457 // GROUP OFFSET; REGULAR 1458 } else { 1459 tmp.top = group.offset.y; 1460 tmp.left = group.offset.x; 1461 } 1462 1463 // WALKTHROUGH ELEMENTS 1464 for ( i1 = 0; i1 < g.paths.length; i1++ ) { 1465 var PID = null; 1466 1467 // OPACITY; TODO: DISTINGUISH OPACITY TYPES 1468 if ( g.paths[ i1 ] ) { 1469 1470 // CHECK ORIGIN; REMOVE TAINTED 1471 if ( cfg.removeImages && _this.isTainted( g.paths[ i1 ][ "xlink:href" ] ) ) { 1472 continue; 1473 } 1474 1475 // SET OPACITY 1476 if ( g.paths[ i1 ].fill instanceof Object ) { 1477 1478 // MISINTERPRETATION OF FABRIC 1479 if ( g.paths[ i1 ].fill.type == "radial" ) { 1480 1481 // PIE EXCEPTION 1482 if ( _this.setup.chart.type == "pie" ) { 1483 var tmp_n = g.paths[ i1 ]; 1484 var tmp_c = tmp_n.getCenterPoint(); 1485 var tmp_cp = tmp_n.group.getCenterPoint(); 1486 var tmp_cd = { 1487 x: tmp_n.pathOffset.x - tmp_cp.x, 1488 y: tmp_n.pathOffset.y - tmp_cp.y 1489 }; 1490 1491 g.paths[ i1 ].fill.gradientTransform[ 4 ] = tmp_n.pathOffset.x - tmp_cd.x; 1492 g.paths[ i1 ].fill.gradientTransform[ 5 ] = tmp_n.pathOffset.y - tmp_cd.y; 1493 1494 // OTHERS 1495 } else { 1496 g.paths[ i1 ].fill.coords.r2 = g.paths[ i1 ].fill.coords.r1 * -1; 1497 g.paths[ i1 ].fill.coords.r1 = 0; 1498 g.paths[ i1 ].set( { 1499 opacity: g.paths[ i1 ].fillOpacity 1500 } ); 1501 } 1502 } 1503 1504 // FILLING; TODO: DISTINGUISH OPACITY TYPES 1505 } else if ( PID = _this.isHashbanged( g.paths[ i1 ].fill ) ) { 1506 1507 // PATTERN 1508 if ( group.patterns && group.patterns[ PID ] ) { 1509 1510 var props = group.patterns[ PID ]; 1511 1512 // LOAD IMAGE MANUALLY; TO RERENDER THE CANVAS 1513 fabric.Image.fromURL( props.source, ( function( props, i1 ) { 1514 return function( img ) { 1515 images.loaded++; 1516 1517 var pattern = null; 1518 var patternSourceCanvas = new fabric.StaticCanvas( undefined, { 1519 backgroundColor: props.fill 1520 } ); 1521 patternSourceCanvas.add( img ); 1522 1523 pattern = new fabric.Pattern( { 1524 source: function() { 1525 patternSourceCanvas.setDimensions( { 1526 width: props.width, 1527 height: props.height 1528 } ); 1529 return patternSourceCanvas.getElement(); 1530 }, 1531 repeat: 'repeat' 1532 } ); 1533 1534 g.paths[ i1 ].set( { 1535 fill: pattern, 1536 opacity: g.paths[ i1 ].fillOpacity 1537 } ); 1538 } 1539 } )( props, i1 ) ); 1540 } 1541 } 1542 1543 // CLIPPATH; 1544 if ( PID = _this.isHashbanged( g.paths[ i1 ].clipPath ) ) { 1545 1546 if ( group.clippings && group.clippings[ PID ] ) { 1547 1548 // TODO: WAIT UNTIL FABRICJS HANDLES CLIPPATH FOR SVG OUTPUT 1549 ( function( i1, PID ) { 1550 var toSVG = g.paths[ i1 ].toSVG; 1551 1552 g.paths[ i1 ].toSVG = function( original_reviver ) { 1553 return toSVG.apply(this, [ function( string ) { 1554 return original_reviver( string, group.clippings[ PID ] ); 1555 } ] ); 1556 } 1557 } )( i1, PID ); 1558 1559 g.paths[ i1 ].set( { 1560 clipTo: ( function( i1, PID ) { 1561 return function( ctx ) { 1562 var cp = group.clippings[ PID ]; 1563 var tm = this.transformMatrix || [ 1, 0, 0, 1, 0, 0 ]; 1564 var dim = { 1565 top: ( cp.bbox.y - tm[ 5 ] ) + cp.transform[ 5 ], 1566 left: ( cp.bbox.x - tm[ 4 ] ) + cp.transform[ 4 ], 1567 width: cp.bbox.width, 1568 height: cp.bbox.height 1569 } 1570 1571 ctx.rect( dim.left, dim.top, dim.width, dim.height ); 1572 } 1573 } )( i1, PID ) 1574 } ); 1575 } 1576 } 1577 1578 // TODO; WAIT FOR TSPAN SUPPORT FROM FABRICJS SIDE 1579 if ( g.paths[ i1 ].TSPANWORKAROUND ) { 1580 var parsedAttributes = fabric.parseAttributes( g.paths[ i1 ].svg, fabric.Text.ATTRIBUTE_NAMES ); 1581 var options = fabric.util.object.extend( {}, parsedAttributes ); 1582 1583 // CREATE NEW SET 1584 var tmpBuffer = []; 1585 for ( var i = 0; i < g.paths[ i1 ].svg.childNodes.length; i++ ) { 1586 var textNode = g.paths[ i1 ].svg.childNodes[ i ]; 1587 var textElement = fabric.Text.fromElement( textNode, options ); 1588 1589 textElement.set( { 1590 left: 0 1591 } ); 1592 1593 tmpBuffer.push( textElement ); 1594 } 1595 1596 // HIDE ORIGINAL ELEMENT 1597 g.paths[ i1 ].set( { 1598 opacity: 0 1599 } ); 1600 1601 // REPLACE BY GROUP AND CANCEL FIRST OFFSET 1602 var tmpGroup = new fabric.Group( tmpBuffer, { 1603 top: g.paths[ i1 ].top * -1 1604 } ); 1605 g.paths[ i1 ] = tmpGroup; 1606 } 1607 } 1608 paths.push( g.paths[ i1 ] ); 1609 } 1610 1611 // REPLACE WITH WHITELIST 1612 g.paths = paths; 1613 1614 // SET PROPS 1615 g.set( tmp ); 1616 1617 // ADD TO CANVAS 1618 _this.setup.fabric.add( g ); 1619 1620 // ADD BALLOONS 1621 if ( group.svg.parentNode && group.svg.parentNode.getElementsByTagName ) { 1622 var balloons = group.svg.parentNode.getElementsByClassName( _this.setup.chart.classNamePrefix + "-balloon-div" ); 1623 for ( i1 = 0; i1 < balloons.length; i1++ ) { 1624 if ( cfg.balloonFunction instanceof Function ) { 1625 cfg.balloonFunction.apply( _this, [ balloons[ i1 ], group ] ); 1626 } else { 1627 var elm_parent = balloons[ i1 ]; 1628 var style_parent = fabric.parseStyleAttribute( elm_parent ); 1629 var style_text = fabric.parseStyleAttribute( elm_parent.childNodes[ 0 ] ); 1630 var fabric_label = new fabric.Text( elm_parent.innerText || elm_parent.textContent || elm_parent.innerHTML, { 1631 selectable: false, 1632 top: style_parent.top + group.offset.y, 1633 left: style_parent.left + group.offset.x, 1634 fill: style_text[ "color" ], 1635 fontSize: style_text[ "fontSize" ], 1636 fontFamily: style_text[ "fontFamily" ], 1637 textAlign: style_text[ "text-align" ] 1638 } ); 1639 1640 _this.setup.fabric.add( fabric_label ); 1641 } 1642 } 1643 } 1644 1645 if ( group.svg.nextSibling && group.svg.nextSibling.tagName == "A" ) { 1646 var elm_parent = group.svg.nextSibling; 1647 var style_parent = fabric.parseStyleAttribute( elm_parent ); 1648 var fabric_label = new fabric.Text( elm_parent.innerText || elm_parent.textContent || elm_parent.innerHTML, { 1649 selectable: false, 1650 top: style_parent.top + group.offset.y, 1651 left: style_parent.left + group.offset.x, 1652 fill: style_parent[ "color" ], 1653 fontSize: style_parent[ "fontSize" ], 1654 fontFamily: style_parent[ "fontFamily" ], 1655 opacity: style_parent[ "opacity" ] 1656 } ); 1657 1658 _this.setup.fabric.add( fabric_label ); 1659 } 1660 1661 groups.pop(); 1662 1663 // TRIGGER CALLBACK WITH SAFETY DELAY 1664 if ( !groups.length ) { 1665 var timer = setInterval( function() { 1666 if ( images.loaded == images.included ) { 1667 clearTimeout( timer ); 1668 _this.handleCallback( cfg.afterCapture, cfg ); 1669 _this.setup.fabric.renderAll(); 1670 _this.handleCallback( callback, cfg ); 1671 } 1672 }, AmCharts.updateRate ); 1673 } 1674 } 1675 1676 // IDENTIFY ELEMENTS THROUGH CLASSNAMES 1677 } )( group ), function( svg, obj ) { 1678 var i1; 1679 var className = _this.gatherAttribute( svg, "class" ); 1680 var visibility = _this.gatherAttribute( svg, "visibility" ); 1681 var clipPath = _this.gatherAttribute( svg, "clip-path" ); 1682 1683 obj.className = String( className ); 1684 obj.classList = String( className ).split( " " ); 1685 obj.clipPath = clipPath; 1686 obj.svg = svg; 1687 1688 // TODO; WAIT FOR TSPAN SUPPORT FROM FABRICJS SIDE 1689 if ( svg.tagName == "text" && svg.childNodes.length > 1 ) { 1690 obj.TSPANWORKAROUND = true; 1691 } 1692 1693 // HIDE HIDDEN ELEMENTS; TODO: FIND A BETTER WAY TO HANDLE THAT 1694 if ( visibility == "hidden" ) { 1695 obj.opacity = 0; 1696 1697 // WALKTHROUGH ELEMENTS 1698 } else { 1699 1700 // TRANSPORT FILL/STROKE OPACITY 1701 var attrs = [ "fill", "stroke" ]; 1702 for ( i1 = 0; i1 < attrs.length; i1++ ) { 1703 var attr = attrs[ i1 ] 1704 var attrVal = String( svg.getAttribute( attr ) || "" ); 1705 var attrOpacity = Number( svg.getAttribute( attr + "-opacity" ) || "1" ); 1706 var attrRGBA = fabric.Color.fromHex( attrVal ).getSource(); 1707 1708 // EXCEPTION 1709 if ( obj.classList.indexOf( _this.setup.chart.classNamePrefix + "-guide-fill" ) != -1 && !attrVal ) { 1710 attrOpacity = 0; 1711 attrRGBA = fabric.Color.fromHex( "#000000" ).getSource(); 1712 } 1713 1714 if ( attrRGBA ) { 1715 attrRGBA.pop(); 1716 attrRGBA.push( attrOpacity ) 1717 obj[ attr ] = "rgba(" + attrRGBA.join() + ")"; 1718 obj[ attr + _this.capitalize( "opacity" ) ] = attrOpacity; 1719 } 1720 } 1721 } 1722 1723 // REVIVER 1724 _this.handleCallback( cfg.reviver, obj, svg ); 1725 } ); 1726 } 1727 }, 1728 1729 /** 1730 * Returns the current canvas 1731 */ 1732 toCanvas: function( options, callback ) { 1733 var cfg = _this.deepMerge( { 1734 // NUFFIN 1735 }, options || {} ); 1736 var data = _this.setup.canvas; 1737 1738 _this.handleCallback( callback, data ); 1739 1740 return data; 1741 }, 1742 1743 /** 1744 * Returns an image; by default PNG 1745 */ 1746 toImage: function( options, callback ) { 1747 var cfg = _this.deepMerge( { 1748 format: "png", 1749 quality: 1, 1750 multiplier: 1 1751 }, options || {} ); 1752 var data = cfg.data; 1753 var img = document.createElement( "img" ); 1754 1755 if ( !cfg.data ) { 1756 if ( cfg.lossless || cfg.format == "svg" ) { 1757 data = _this.toSVG( _this.deepMerge( cfg, { 1758 getBase64: true 1759 } ) ); 1760 } else { 1761 data = _this.setup.fabric.toDataURL( cfg ); 1762 } 1763 } 1764 1765 img.setAttribute( "src", data ); 1766 1767 _this.handleCallback( callback, img ); 1768 1769 return img; 1770 }, 1771 1772 /** 1773 * Generates a blob instance image; returns base64 datastring 1774 */ 1775 toBlob: function( options, callback ) { 1776 var cfg = _this.deepMerge( { 1777 data: "empty", 1778 type: "text/plain" 1779 }, options || {} ); 1780 var data; 1781 var isBase64 = /^data:.+;base64,(.*)$/.exec( cfg.data ); 1782 1783 // GATHER BODY 1784 if ( isBase64 ) { 1785 cfg.data = isBase64[ 0 ]; 1786 cfg.type = cfg.data.slice( 5, cfg.data.indexOf( "," ) - 7 ); 1787 cfg.data = _this.toByteArray( { 1788 data: cfg.data.slice( cfg.data.indexOf( "," ) + 1, cfg.data.length ) 1789 } ); 1790 } 1791 1792 if ( cfg.getByteArray ) { 1793 data = cfg.data; 1794 } else { 1795 data = new Blob( [ cfg.data ], { 1796 type: cfg.type 1797 } ); 1798 } 1799 1800 _this.handleCallback( callback, data ); 1801 1802 return data; 1803 }, 1804 1805 /** 1806 * Generates JPG image; returns base64 datastring 1807 */ 1808 toJPG: function( options, callback ) { 1809 var cfg = _this.deepMerge( { 1810 format: "jpeg", 1811 quality: 1, 1812 multiplier: 1 1813 }, options || {} ); 1814 cfg.format = cfg.format.toLowerCase(); 1815 var data = _this.setup.fabric.toDataURL( cfg ); 1816 1817 _this.handleCallback( callback, data ); 1818 1819 return data; 1820 }, 1821 1822 /** 1823 * Generates PNG image; returns base64 datastring 1824 */ 1825 toPNG: function( options, callback ) { 1826 var cfg = _this.deepMerge( { 1827 format: "png", 1828 quality: 1, 1829 multiplier: 1 1830 }, options || {} ); 1831 var data = _this.setup.fabric.toDataURL( cfg ); 1832 1833 _this.handleCallback( callback, data ); 1834 1835 return data; 1836 }, 1837 1838 /** 1839 * Generates SVG image; returns base64 datastring 1840 */ 1841 toSVG: function( options, callback ) { 1842 var clipPaths = []; 1843 var cfg = _this.deepMerge( { 1844 reviver: function( string, clipPath ) { 1845 var matcher = new RegExp( /\bstyle=(['"])(.*?)\1/ ); 1846 var match = matcher.exec( string )[ 0 ].slice( 7, -1 ); 1847 var styles = match.split( ";" ); 1848 var replacement = []; 1849 1850 // BEAUTIFY STYLES 1851 for ( i1 = 0; i1 < styles.length; i1++ ) { 1852 if ( styles[ i1 ] ) { 1853 var pair = styles[ i1 ].replace( /\s/g, "" ).split( ":" ); 1854 var key = pair[ 0 ]; 1855 var value = pair[ 1 ]; 1856 1857 if ( [ "fill", "stroke" ].indexOf( key ) != -1 ) { 1858 value = fabric.Color.fromRgba( value ); 1859 if ( value && value._source ) { 1860 var color = "#" + value.toHex(); 1861 var opacity = value._source[ 3 ]; 1862 1863 replacement.push( [ key, color ].join( ":" ) ); 1864 replacement.push( [ key + "-opacity", opacity ].join( ":" ) ); 1865 } else { 1866 replacement.push( styles[ i1 ] ); 1867 } 1868 } else if ( key != "opactiy" ) { 1869 replacement.push( styles[ i1 ] ); 1870 } 1871 } 1872 } 1873 string = string.replace( match, replacement.join( ";" ) ); 1874 1875 // TODO: WAIT UNTIL FABRICJS HANDLES CLIPPATH FOR SVG OUTPUT 1876 if ( clipPath ) { 1877 var sliceOffset = 2; 1878 var end = string.slice( - sliceOffset); 1879 1880 if ( end != "/>" ) { 1881 sliceOffset = 3; 1882 end = string.slice( - sliceOffset); 1883 } 1884 1885 var start = string.slice(0,string.length - sliceOffset); 1886 var clipPathAttr = " clip-path=\"url(#"+ clipPath.svg.id +")\" "; 1887 var clipPathString = new XMLSerializer().serializeToString(clipPath.svg); 1888 1889 string = start + clipPathAttr + end; 1890 1891 clipPaths.push(clipPathString); 1892 } 1893 1894 return string; 1895 } 1896 }, options || {} ); 1897 var data = _this.setup.fabric.toSVG( cfg, cfg.reviver ); 1898 1899 // TODO: WAIT UNTIL FABRICJS HANDLES CLIPPATH FOR SVG OUTPUT 1900 if ( clipPaths.length ) { 1901 var start = data.slice(0,data.length-6); 1902 var end = data.slice(-6); 1903 data = start + clipPaths.join("") + end; 1904 } 1905 1906 if ( cfg.getBase64 ) { 1907 data = "data:image/svg+xml;base64," + btoa( data ); 1908 } 1909 1910 _this.handleCallback( callback, data ); 1911 1912 return data; 1913 }, 1914 1915 /** 1916 * Generates PDF; returns base64 datastring 1917 */ 1918 toPDF: function( options, callback ) { 1919 var cfg = _this.deepMerge( _this.deepMerge( { 1920 multiplier: 2 1921 }, _this.config.pdfMake ), options || {}, true ); 1922 cfg.images.reference = _this.toPNG( cfg ); 1923 var data = new pdfMake.createPdf( cfg ); 1924 1925 if ( callback ) { 1926 data.getDataUrl( ( function( callback ) { 1927 return function() { 1928 callback.apply( _this, arguments ); 1929 } 1930 } )( callback ) ); 1931 } 1932 1933 return data; 1934 }, 1935 1936 /** 1937 * Generates an image; hides all elements on page to trigger native print method 1938 */ 1939 toPRINT: function( options, callback ) { 1940 var i1; 1941 var cfg = _this.deepMerge( { 1942 delay: 1, 1943 lossless: false 1944 }, options || {} ); 1945 var data = _this.toImage( cfg ); 1946 var states = []; 1947 var items = document.body.childNodes; 1948 1949 data.setAttribute( "style", "width: 100%; max-height: 100%;" ); 1950 1951 for ( i1 = 0; i1 < items.length; i1++ ) { 1952 if ( _this.isElement( items[ i1 ] ) ) { 1953 states[ i1 ] = items[ i1 ].style.display; 1954 items[ i1 ].style.display = "none"; 1955 } 1956 } 1957 1958 document.body.appendChild( data ); 1959 window.print(); 1960 1961 setTimeout( function() { 1962 for ( i1 = 0; i1 < items.length; i1++ ) { 1963 if ( _this.isElement( items[ i1 ] ) ) { 1964 items[ i1 ].style.display = states[ i1 ]; 1965 } 1966 } 1967 document.body.removeChild( data ); 1968 _this.handleCallback( callback, data ); 1969 }, cfg.delay ); 1970 1971 return data; 1972 }, 1973 1974 /** 1975 * Generates JSON string 1976 */ 1977 toJSON: function( options, callback ) { 1978 var cfg = _this.deepMerge( { 1979 dateFormat: _this.config.dateFormat || "dateObject", 1980 }, options || {}, true ); 1981 cfg.data = cfg.data ? cfg.data : _this.getChartData( cfg ); 1982 var data = JSON.stringify( cfg.data, undefined, "\t" ); 1983 1984 _this.handleCallback( callback, data ); 1985 1986 return data; 1987 }, 1988 1989 /** 1990 * Generates CSV string 1991 */ 1992 toCSV: function( options, callback ) { 1993 var row, col; 1994 var cfg = _this.deepMerge( { 1995 data: _this.getChartData( options ), 1996 delimiter: ",", 1997 quotes: true, 1998 escape: true, 1999 withHeader: true 2000 }, options || {}, true ); 2001 var data = ""; 2002 var cols = []; 2003 var buffer = []; 2004 2005 function enchant( value, column ) { 2006 2007 // WRAP IN QUOTES 2008 if ( typeof value === "string" ) { 2009 if ( cfg.escape ) { 2010 value = value.replace( '"', '""' ); 2011 } 2012 if ( cfg.quotes ) { 2013 value = [ '"', value, '"' ].join( "" ); 2014 } 2015 } 2016 2017 return value; 2018 } 2019 2020 // HEADER 2021 for ( value in cfg.data[ 0 ] ) { 2022 buffer.push( enchant( value ) ); 2023 cols.push( value ); 2024 } 2025 if ( cfg.withHeader ) { 2026 data += buffer.join( cfg.delimiter ) + "\n"; 2027 } 2028 2029 // BODY 2030 for ( row in cfg.data ) { 2031 buffer = []; 2032 if ( !isNaN( row ) ) { 2033 for ( col in cols ) { 2034 if ( !isNaN( col ) ) { 2035 var column = cols[ col ]; 2036 var value = cfg.data[ row ][ column ]; 2037 2038 buffer.push( enchant( value, column ) ); 2039 } 2040 } 2041 data += buffer.join( cfg.delimiter ) + "\n"; 2042 } 2043 } 2044 2045 _this.handleCallback( callback, data ); 2046 2047 return data; 2048 }, 2049 2050 /** 2051 * Generates excel sheet; returns base64 datastring 2052 */ 2053 toXLSX: function( options, callback ) { 2054 var cfg = _this.deepMerge( { 2055 name: "amCharts", 2056 dateFormat: _this.config.dateFormat || "dateObject", 2057 withHeader: true, 2058 stringify: false 2059 }, options || {}, true ); 2060 var data = ""; 2061 var wb = { 2062 SheetNames: [], 2063 Sheets: {} 2064 } 2065 2066 cfg.data = cfg.data ? cfg.data : _this.getChartData( cfg ); 2067 2068 function datenum( v, date1904 ) { 2069 if ( date1904 ) v += 1462; 2070 var epoch = Date.parse( v ); 2071 return ( epoch - new Date( Date.UTC( 1899, 11, 30 ) ) ) / ( 24 * 60 * 60 * 1000 ); 2072 } 2073 2074 function sheet_from_array_of_arrays( data, opts ) { 2075 var ws = {}; 2076 var range = { 2077 s: { 2078 c: 10000000, 2079 r: 10000000 2080 }, 2081 e: { 2082 c: 0, 2083 r: 0 2084 } 2085 }; 2086 for ( var R = 0; R != data.length; ++R ) { 2087 for ( var C = 0; C != data[ R ].length; ++C ) { 2088 if ( range.s.r > R ) range.s.r = R; 2089 if ( range.s.c > C ) range.s.c = C; 2090 if ( range.e.r < R ) range.e.r = R; 2091 if ( range.e.c < C ) range.e.c = C; 2092 var cell = { 2093 v: data[ R ][ C ] 2094 }; 2095 if ( cell.v == null ) continue; 2096 var cell_ref = XLSX.utils.encode_cell( { 2097 c: C, 2098 r: R 2099 } ); 2100 2101 if ( typeof cell.v === "number" ) cell.t = "n"; 2102 else if ( typeof cell.v === "boolean" ) cell.t = "b"; 2103 else if ( cell.v instanceof Date ) { 2104 cell.t = "n"; 2105 cell.z = XLSX.SSF._table[ 14 ]; 2106 cell.v = datenum( cell.v ); 2107 } else cell.t = "s"; 2108 2109 ws[ cell_ref ] = cell; 2110 } 2111 } 2112 if ( range.s.c < 10000000 ) ws[ "!ref" ] = XLSX.utils.encode_range( range ); 2113 return ws; 2114 } 2115 2116 wb.SheetNames.push( cfg.name ); 2117 wb.Sheets[ cfg.name ] = sheet_from_array_of_arrays( _this.toArray( cfg ) ); 2118 2119 data = XLSX.write( wb, { 2120 bookType: "xlsx", 2121 bookSST: true, 2122 type: "base64" 2123 } ); 2124 2125 data = "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," + data; 2126 2127 _this.handleCallback( callback, data ); 2128 2129 return data; 2130 }, 2131 2132 /** 2133 * Generates an array of arrays 2134 */ 2135 toArray: function( options, callback ) { 2136 var row, col; 2137 var cfg = _this.deepMerge( { 2138 data: _this.getChartData( options ), 2139 withHeader: false, 2140 stringify: true 2141 }, options || {}, true ); 2142 var data = []; 2143 var cols = []; 2144 2145 // HEADER 2146 for ( col in cfg.data[ 0 ] ) { 2147 cols.push( col ); 2148 } 2149 if ( cfg.withHeader ) { 2150 data.push( cols ); 2151 } 2152 2153 // BODY 2154 for ( row in cfg.data ) { 2155 var buffer = []; 2156 if ( !isNaN( row ) ) { 2157 for ( col in cols ) { 2158 if ( !isNaN( col ) ) { 2159 var col = cols[ col ]; 2160 var value = cfg.data[ row ][ col ]; 2161 if ( value == null ) { 2162 value = ""; 2163 } else if ( cfg.stringify ) { 2164 value = String( value ); 2165 } else { 2166 value = value; 2167 } 2168 buffer.push( value ); 2169 } 2170 } 2171 data.push( buffer ); 2172 } 2173 } 2174 2175 _this.handleCallback( callback, data ); 2176 2177 return data; 2178 }, 2179 2180 /** 2181 * Generates byte array with given base64 datastring; returns byte array 2182 */ 2183 toByteArray: function( options, callback ) { 2184 var cfg = _this.deepMerge( { 2185 // NUFFIN 2186 }, options || {} ); 2187 var Arr = ( typeof Uint8Array !== 'undefined' ) ? Uint8Array : Array 2188 var PLUS = '+'.charCodeAt( 0 ) 2189 var SLASH = '/'.charCodeAt( 0 ) 2190 var NUMBER = '0'.charCodeAt( 0 ) 2191 var LOWER = 'a'.charCodeAt( 0 ) 2192 var UPPER = 'A'.charCodeAt( 0 ) 2193 var data = b64ToByteArray( cfg.data ); 2194 2195 function decode( elt ) { 2196 var code = elt.charCodeAt( 0 ) 2197 if ( code === PLUS ) 2198 return 62 // '+' 2199 if ( code === SLASH ) 2200 return 63 // '/' 2201 if ( code < NUMBER ) 2202 return -1 //no match 2203 if ( code < NUMBER + 10 ) 2204 return code - NUMBER + 26 + 26 2205 if ( code < UPPER + 26 ) 2206 return code - UPPER 2207 if ( code < LOWER + 26 ) 2208 return code - LOWER + 26 2209 } 2210 2211 function b64ToByteArray( b64 ) { 2212 var i, j, l, tmp, placeHolders, arr 2213 2214 if ( b64.length % 4 > 0 ) { 2215 throw new Error( 'Invalid string. Length must be a multiple of 4' ) 2216 } 2217 2218 // THE NUMBER OF EQUAL SIGNS (PLACE HOLDERS) 2219 // IF THERE ARE TWO PLACEHOLDERS, THAN THE TWO CHARACTERS BEFORE IT 2220 // REPRESENT ONE BYTE 2221 // IF THERE IS ONLY ONE, THEN THE THREE CHARACTERS BEFORE IT REPRESENT 2 BYTES 2222 // THIS IS JUST A CHEAP HACK TO NOT DO INDEXOF TWICE 2223 var len = b64.length 2224 placeHolders = '=' === b64.charAt( len - 2 ) ? 2 : '=' === b64.charAt( len - 1 ) ? 1 : 0 2225 2226 // BASE64 IS 4/3 + UP TO TWO CHARACTERS OF THE ORIGINAL DATA 2227 arr = new Arr( b64.length * 3 / 4 - placeHolders ) 2228 2229 // IF THERE ARE PLACEHOLDERS, ONLY GET UP TO THE LAST COMPLETE 4 CHARS 2230 l = placeHolders > 0 ? b64.length - 4 : b64.length 2231 2232 var L = 0 2233 2234 function push( v ) { 2235 arr[ L++ ] = v 2236 } 2237 2238 for ( i = 0, j = 0; i < l; i += 4, j += 3 ) { 2239 tmp = ( decode( b64.charAt( i ) ) << 18 ) | ( decode( b64.charAt( i + 1 ) ) << 12 ) | ( decode( b64.charAt( i + 2 ) ) << 6 ) | decode( b64.charAt( i + 3 ) ) 2240 push( ( tmp & 0xFF0000 ) >> 16 ) 2241 push( ( tmp & 0xFF00 ) >> 8 ) 2242 push( tmp & 0xFF ) 2243 } 2244 2245 if ( placeHolders === 2 ) { 2246 tmp = ( decode( b64.charAt( i ) ) << 2 ) | ( decode( b64.charAt( i + 1 ) ) >> 4 ) 2247 push( tmp & 0xFF ) 2248 } else if ( placeHolders === 1 ) { 2249 tmp = ( decode( b64.charAt( i ) ) << 10 ) | ( decode( b64.charAt( i + 1 ) ) << 4 ) | ( decode( b64.charAt( i + 2 ) ) >> 2 ) 2250 push( ( tmp >> 8 ) & 0xFF ) 2251 push( tmp & 0xFF ) 2252 } 2253 2254 return arr 2255 } 2256 2257 _this.handleCallback( callback, data ); 2258 2259 return data; 2260 }, 2261 2262 /** 2263 * Callback handler; injects additional arguments to callback 2264 */ 2265 handleCallback: function( callback ) { 2266 var i1, data = Array(); 2267 if ( callback && callback instanceof Function ) { 2268 for ( i1 = 0; i1 < arguments.length; i1++ ) { 2269 if ( i1 > 0 ) { 2270 data.push( arguments[ i1 ] ); 2271 } 2272 } 2273 callback.apply( _this, data ); 2274 } 2275 }, 2276 2277 /** 2278 * Handles drag/drop events; loads given imagery 2279 */ 2280 handleDropbox: function( e ) { 2281 if ( _this.drawing.buffer.enabled ) { 2282 e.preventDefault(); 2283 e.stopPropagation(); 2284 2285 // DRAG OVER 2286 if ( e.type == "dragover" ) { 2287 _this.setup.wrapper.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-canvas active dropbox" ); 2288 2289 // DRAGLEAVE; DROP 2290 } else { 2291 _this.setup.wrapper.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-canvas active" ); 2292 2293 if ( e.type == "drop" && e.dataTransfer.files.length ) { 2294 for ( var i1 = 0; i1 < e.dataTransfer.files.length; i1++ ) { 2295 var reader = new FileReader(); 2296 reader.onloadend = ( function( index ) { 2297 return function() { 2298 _this.drawing.handler.add( { 2299 url: reader.result, 2300 top: e.layerY - ( index * 10 ), 2301 left: e.layerX - ( index * 10 ) 2302 } ); 2303 } 2304 } )( i1 ); 2305 reader.readAsDataURL( e.dataTransfer.files[ i1 ] ); 2306 } 2307 } 2308 } 2309 } 2310 }, 2311 2312 /** 2313 * Gathers chart data according to its type 2314 */ 2315 getChartData: function( options ) { 2316 var cfg = _this.deepMerge( { 2317 data: [], 2318 titles: {}, 2319 dateFields: [], 2320 dataFields: [], 2321 dataFieldsMap: {}, 2322 exportTitles: _this.config.exportTitles, 2323 exportFields: _this.config.exportFields, 2324 exportSelection: _this.config.exportSelection, 2325 columnNames: _this.config.columnNames 2326 }, options || {}, true ); 2327 var uid, i1, i2, i3; 2328 var lookupFields = [ "valueField", "openField", "closeField", "highField", "lowField", "xField", "yField" ]; 2329 2330 // HANDLE FIELDS 2331 function addField( field, title, type ) { 2332 2333 function checkExistance( field, type ) { 2334 if ( cfg.dataFields.indexOf( field ) != -1 ) { 2335 return checkExistance( [ field, ".", type ].join( "" ) ); 2336 } 2337 return field; 2338 } 2339 2340 if ( field && cfg.exportTitles && _this.setup.chart.type != "gantt" ) { 2341 uid = checkExistance( field, type ); 2342 cfg.dataFieldsMap[ uid ] = field; 2343 cfg.dataFields.push( uid ); 2344 cfg.titles[ uid ] = title || uid; 2345 } 2346 } 2347 2348 if ( cfg.data.length == 0 ) { 2349 // STOCK DATA; GATHER COMPARED GRAPHS 2350 if ( _this.setup.chart.type == "stock" ) { 2351 cfg.data = _this.setup.chart.mainDataSet.dataProvider; 2352 2353 // CATEGORY AXIS 2354 addField( _this.setup.chart.mainDataSet.categoryField ); 2355 cfg.dateFields.push( _this.setup.chart.mainDataSet.categoryField ); 2356 2357 // WALKTHROUGH GRAPHS 2358 for ( i1 = 0; i1 < _this.setup.chart.mainDataSet.fieldMappings.length; i1++ ) { 2359 var fieldMap = _this.setup.chart.mainDataSet.fieldMappings[ i1 ]; 2360 for ( i2 = 0; i2 < _this.setup.chart.panels.length; i2++ ) { 2361 var panel = _this.setup.chart.panels[ i2 ] 2362 for ( i3 = 0; i3 < panel.stockGraphs.length; i3++ ) { 2363 var graph = panel.stockGraphs[ i3 ]; 2364 2365 for ( i4 = 0; i4 < lookupFields.length; i4++ ) { 2366 if ( graph[ lookupFields[ i4 ] ] == fieldMap.toField ) { 2367 addField( fieldMap.fromField, graph.title, lookupFields[ i4 ] ); 2368 } 2369 } 2370 } 2371 } 2372 } 2373 2374 // WALKTHROUGH COMPARISON AND MERGE IT'S DATA 2375 for ( i1 = 0; i1 < _this.setup.chart.comparedGraphs.length; i1++ ) { 2376 var graph = _this.setup.chart.comparedGraphs[ i1 ]; 2377 for ( i2 = 0; i2 < graph.dataSet.dataProvider.length; i2++ ) { 2378 for ( i3 = 0; i3 < graph.dataSet.fieldMappings.length; i3++ ) { 2379 var fieldMap = graph.dataSet.fieldMappings[ i3 ]; 2380 var uid = graph.dataSet.id + "_" + fieldMap.toField; 2381 2382 if ( i2 < cfg.data.length ) { 2383 cfg.data[ i2 ][ uid ] = graph.dataSet.dataProvider[ i2 ][ fieldMap.fromField ]; 2384 2385 if ( !cfg.titles[ uid ] ) { 2386 addField( uid, graph.dataSet.title ) 2387 } 2388 } 2389 } 2390 } 2391 } 2392 2393 // GANTT DATA; FLATTEN SEGMENTS 2394 } else if ( _this.setup.chart.type == "gantt" ) { 2395 // CATEGORY AXIS 2396 addField( _this.setup.chart.categoryField ); 2397 cfg.dateFields.push( _this.setup.chart.categoryField ); 2398 2399 var field = _this.setup.chart.segmentsField; 2400 for ( i1 = 0; i1 < _this.setup.chart.dataProvider.length; i1++ ) { 2401 var dataItem = _this.setup.chart.dataProvider[ i1 ]; 2402 if ( dataItem[ field ] ) { 2403 for ( i2 = 0; i2 < dataItem[ field ].length; i2++ ) { 2404 dataItem[ field ][ i2 ][ _this.setup.chart.categoryField ] = dataItem[ _this.setup.chart.categoryField ]; 2405 cfg.data.push( dataItem[ field ][ i2 ] ); 2406 } 2407 } 2408 } 2409 2410 // GRAPHS 2411 for ( i1 = 0; i1 < _this.setup.chart.graphs.length; i1++ ) { 2412 var graph = _this.setup.chart.graphs[ i1 ]; 2413 2414 for ( i2 = 0; i2 < lookupFields.length; i2++ ) { 2415 var dataField = lookupFields[ i2 ]; 2416 var graphField = graph[ dataField ]; 2417 var title = graph.title; 2418 2419 addField( graphField, graph.title, dataField ); 2420 } 2421 } 2422 2423 // PIE/FUNNEL DATA; 2424 } else if ( [ "pie", "funnel" ].indexOf( _this.setup.chart.type ) != -1 ) { 2425 cfg.data = _this.setup.chart.dataProvider; 2426 2427 // CATEGORY AXIS 2428 addField( _this.setup.chart.titleField ); 2429 cfg.dateFields.push( _this.setup.chart.titleField ); 2430 2431 // VALUE 2432 addField( _this.setup.chart.valueField ); 2433 2434 // DEFAULT DATA; 2435 } else if ( _this.setup.chart.type != "map" ) { 2436 cfg.data = _this.setup.chart.dataProvider; 2437 2438 // CATEGORY AXIS 2439 if ( _this.setup.chart.categoryAxis ) { 2440 addField( _this.setup.chart.categoryField, _this.setup.chart.categoryAxis.title ); 2441 if ( _this.setup.chart.categoryAxis.parseDates !== false ) { 2442 cfg.dateFields.push( _this.setup.chart.categoryField ); 2443 } 2444 } 2445 2446 // GRAPHS 2447 for ( i1 = 0; i1 < _this.setup.chart.graphs.length; i1++ ) { 2448 var graph = _this.setup.chart.graphs[ i1 ]; 2449 2450 for ( i2 = 0; i2 < lookupFields.length; i2++ ) { 2451 var dataField = lookupFields[ i2 ]; 2452 var graphField = graph[ dataField ]; 2453 2454 addField( graphField, graph.title, dataField ); 2455 } 2456 } 2457 } 2458 } 2459 return _this.processData( cfg ); 2460 }, 2461 2462 /** 2463 * Walkthrough data to format dates and titles 2464 */ 2465 processData: function( options ) { 2466 var cfg = _this.deepMerge( { 2467 data: [], 2468 titles: {}, 2469 dateFields: [], 2470 dataFields: [], 2471 dataFieldsMap: {}, 2472 dataDateFormat: _this.setup.chart.dataDateFormat, 2473 dateFormat: _this.config.dateFormat || _this.setup.chart.dataDateFormat || "YYYY-MM-DD", 2474 exportTitles: _this.config.exportTitles, 2475 exportFields: _this.config.exportFields, 2476 exportSelection: _this.config.exportSelection, 2477 columnNames: _this.config.columnNames 2478 }, options || {}, true ); 2479 var i1, i2; 2480 2481 if ( cfg.data.length ) { 2482 // GATHER MISSING FIELDS 2483 for ( i1 = 0; i1 < cfg.data.length; i1++ ) { 2484 for ( i2 in cfg.data[ i1 ] ) { 2485 if ( cfg.dataFields.indexOf( i2 ) == -1 ) { 2486 cfg.dataFields.push( i2 ); 2487 cfg.dataFieldsMap[ i2 ] = i2; 2488 } 2489 } 2490 } 2491 2492 // REMOVE FIELDS SELECTIVELY 2493 if ( cfg.exportFields !== undefined ) { 2494 cfg.dataFields = cfg.dataFields.filter( function( n ) { 2495 return cfg.exportFields.indexOf( n ) != -1; 2496 } ); 2497 } 2498 2499 // REBUILD DATA 2500 var buffer = []; 2501 for ( i1 = 0; i1 < cfg.data.length; i1++ ) { 2502 var tmp = {}; 2503 var skip = false; 2504 for ( i2 = 0; i2 < cfg.dataFields.length; i2++ ) { 2505 var uniqueField = cfg.dataFields[ i2 ]; 2506 var dataField = cfg.dataFieldsMap[ uniqueField ]; 2507 var title = ( cfg.columnNames && cfg.columnNames[ uniqueField ] ) || cfg.titles[ uniqueField ] || uniqueField; 2508 var value = cfg.data[ i1 ][ dataField ]; 2509 if ( value == null ) { 2510 value = undefined; 2511 } 2512 2513 // TITLEFY 2514 if ( cfg.exportTitles && _this.setup.chart.type != "gantt" ) { 2515 if ( title in tmp ) { 2516 title += [ "( ", uniqueField, " )" ].join( "" ); 2517 } 2518 } 2519 2520 // PROCESS CATEGORY 2521 if ( cfg.dateFields.indexOf( dataField ) != -1 ) { 2522 2523 // CONVERT DATESTRING TO DATE OBJECT 2524 if ( cfg.dataDateFormat && ( value instanceof String || typeof value == "string" ) ) { 2525 value = AmCharts.stringToDate( value, cfg.dataDateFormat ); 2526 2527 // CONVERT TIMESTAMP TO DATE OBJECT 2528 } else if ( cfg.dateFormat && ( value instanceof Number || typeof value == "number" ) ) { 2529 value = new Date( value ); 2530 } 2531 2532 // CATEGORY RANGE 2533 if ( cfg.exportSelection ) { 2534 if ( value instanceof Date ) { 2535 if ( value < chart.startDate || value > chart.endDate ) { 2536 skip = true; 2537 } 2538 2539 } else if ( i1 < chart.startIndex || i1 > chart.endIndex ) { 2540 skip = true; 2541 } 2542 } 2543 2544 // CATEGORY FORMAT 2545 if ( cfg.dateFormat && cfg.dateFormat != "dateObject" && value instanceof Date ) { 2546 value = AmCharts.formatDate( value, cfg.dateFormat ); 2547 } 2548 } 2549 2550 tmp[ title ] = value; 2551 } 2552 if ( !skip ) { 2553 buffer.push( tmp ); 2554 } 2555 } 2556 cfg.data = buffer; 2557 } 2558 return cfg.data; 2559 }, 2560 2561 /** 2562 * Prettifies string 2563 */ 2564 capitalize: function( string ) { 2565 return string.charAt( 0 ).toUpperCase() + string.slice( 1 ).toLowerCase(); 2566 }, 2567 2568 /** 2569 * Generates export menu; returns UL node 2570 */ 2571 createMenu: function( list, container ) { 2572 var div; 2573 2574 function buildList( list, container ) { 2575 var i1, i2, ul = document.createElement( "ul" ); 2576 for ( i1 = 0; i1 < list.length; i1++ ) { 2577 var item = typeof list[ i1 ] === "string" ? { 2578 format: list[ i1 ] 2579 } : list[ i1 ]; 2580 var li = document.createElement( "li" ); 2581 var a = document.createElement( "a" ); 2582 var img = document.createElement( "img" ); 2583 var span = document.createElement( "span" ); 2584 var action = String( item.action ? item.action : item.format ).toLowerCase(); 2585 2586 item.format = String( item.format ).toUpperCase(); 2587 2588 // MERGE WITH GIVEN FORMAT 2589 if ( _this.config.formats[ item.format ] ) { 2590 item = _this.deepMerge( { 2591 label: item.icon ? "" : item.format, 2592 format: item.format, 2593 mimeType: _this.config.formats[ item.format ].mimeType, 2594 extension: _this.config.formats[ item.format ].extension, 2595 capture: _this.config.formats[ item.format ].capture, 2596 action: _this.config.action, 2597 fileName: _this.config.fileName 2598 }, item ); 2599 } else if ( !item.label ) { 2600 item.label = item.label ? item.label : _this.i18l( "menu.label." + action ); 2601 } 2602 2603 // FILTER; TOGGLE FLAG 2604 if ( [ "CSV", "JSON", "XLSX" ].indexOf( item.format ) != -1 && [ "map", "gauge" ].indexOf( _this.setup.chart.type ) != -1 ) { 2605 continue; 2606 2607 // BLOB EXCEPTION 2608 } else if ( !_this.setup.hasBlob && item.format != "UNDEFINED" ) { 2609 if ( item.mimeType && item.mimeType.split( "/" )[ 0 ] != "image" && item.mimeType != "text/plain" ) { 2610 continue; 2611 } 2612 } 2613 2614 // DRAWING 2615 if ( item.action == "draw" ) { 2616 if ( _this.config.fabric.drawing.enabled ) { 2617 item.menu = item.menu ? item.menu : _this.config.fabric.drawing.menu; 2618 item.click = ( function( item ) { 2619 return function() { 2620 this.capture( item, function() { 2621 this.createMenu( item.menu ); 2622 } ); 2623 } 2624 } )( item ); 2625 } else { 2626 item.menu = []; 2627 } 2628 2629 // DRAWING CHOICES 2630 } else if ( !item.populated && item.action && item.action.indexOf( "draw." ) != -1 ) { 2631 var type = item.action.split( "." )[ 1 ]; 2632 var items = item[ type ] || _this.config.fabric.drawing[ type ] || []; 2633 2634 item.menu = []; 2635 item.populated = true; 2636 2637 for ( i2 = 0; i2 < items.length; i2++ ) { 2638 var tmp = { 2639 "label": items[ i2 ] 2640 } 2641 2642 if ( type == "shapes" ) { 2643 var io = items[ i2 ].indexOf( "//" ) == -1; 2644 var url = ( io ? _this.config.path + "shapes/" : "" ) + items[ i2 ]; 2645 2646 tmp.action = "add"; 2647 tmp.url = url; 2648 tmp.icon = url; 2649 tmp.ignore = io; 2650 tmp[ "class" ] = "export-drawing-shape"; 2651 2652 } else if ( type == "colors" ) { 2653 tmp.style = "background-color: " + items[ i2 ]; 2654 tmp.action = "change"; 2655 tmp.color = items[ i2 ]; 2656 tmp[ "class" ] = "export-drawing-color"; 2657 2658 } else if ( type == "widths" ) { 2659 tmp.action = "change"; 2660 tmp.width = items[ i2 ]; 2661 tmp.label = document.createElement( "span" ); 2662 2663 tmp.label.style.width = _this.numberToPx( items[ i2 ] ); 2664 tmp.label.style.height = _this.numberToPx( items[ i2 ] ); 2665 tmp[ "class" ] = "export-drawing-width"; 2666 } else if ( type == "opacities" ) { 2667 tmp.style = "opacity: " + items[ i2 ]; 2668 tmp.action = "change"; 2669 tmp.opacity = items[ i2 ]; 2670 tmp.label = ( items[ i2 ] * 100 ) + "%"; 2671 tmp[ "class" ] = "export-drawing-opacity"; 2672 } else if ( type == "modes" ) { 2673 tmp.label = _this.i18l( "menu.label.draw.modes." + items[ i2 ] ); 2674 tmp.click = ( function( mode ) { 2675 return function() { 2676 _this.drawing.mode = mode; 2677 } 2678 } )( items[ i2 ] ); 2679 tmp[ "class" ] = "export-drawing-mode"; 2680 } 2681 2682 item.menu.push( tmp ); 2683 } 2684 2685 // ADD CLICK HANDLER 2686 } else if ( !item.click && !item.menu && !item.items ) { 2687 // DRAWING METHODS 2688 if ( _this.drawing.handler[ action ] instanceof Function ) { 2689 item.action = action; 2690 item.click = ( function( item ) { 2691 return function() { 2692 this.drawing.handler[ item.action ]( item ); 2693 } 2694 } )( item ); 2695 2696 // DRAWING 2697 } else if ( _this.drawing.buffer.enabled ) { 2698 item.click = ( function( item ) { 2699 return function() { 2700 if ( this.config.drawing.autoClose ) { 2701 this.drawing.handler.done(); 2702 } 2703 this[ "to" + item.format ]( item, function( data ) { 2704 if ( item.action == "download" ) { 2705 this.download( data, item.mimeType, [ item.fileName, item.extension ].join( "." ) ); 2706 } 2707 } ); 2708 } 2709 } )( item ); 2710 2711 // REGULAR 2712 } else if ( item.format != "UNDEFINED" ) { 2713 item.click = ( function( item ) { 2714 return function() { 2715 if ( item.capture || item.action == "print" || item.format == "PRINT" ) { 2716 this.capture( item, function() { 2717 if ( this.config.drawing.autoClose ) { 2718 this.drawing.handler.done(); 2719 } 2720 this[ "to" + item.format ]( item, function( data ) { 2721 if ( item.action == "download" ) { 2722 this.download( data, item.mimeType, [ item.fileName, item.extension ].join( "." ) ); 2723 } 2724 } ); 2725 } ) 2726 2727 } else if ( this[ "to" + item.format ] ) { 2728 this[ "to" + item.format ]( item, function( data ) { 2729 this.download( data, item.mimeType, [ item.fileName, item.extension ].join( "." ) ); 2730 } ); 2731 } else { 2732 throw new Error( 'Invalid format. Could not determine output type.' ); 2733 } 2734 } 2735 } )( item ); 2736 } 2737 } 2738 2739 // HIDE EMPTY ONES 2740 if ( item.menu !== undefined && !item.menu.length ) { 2741 continue; 2742 } 2743 2744 // ADD LINK ATTR 2745 a.setAttribute( "href", "#" ); 2746 a.addEventListener( "click", ( function( callback, item ) { 2747 return function( e ) { 2748 e.preventDefault(); 2749 var args = [ e, item ]; 2750 2751 // DELAYED 2752 if ( ( item.action == "draw" || item.format == "PRINT" || ( item.format != "UNDEFINED" && item.capture ) ) && !_this.drawing.enabled ) { 2753 item.delay = item.delay ? item.delay : _this.config.delay; 2754 if ( item.delay ) { 2755 _this.delay( item, callback ); 2756 return; 2757 } 2758 } 2759 2760 callback.apply( _this, args ); 2761 } 2762 } )( item.click || function( e ) { 2763 e.preventDefault(); 2764 }, item ) ); 2765 li.appendChild( a ); 2766 2767 // ADD LABEL 2768 if ( _this.isElement( item.label ) ) { 2769 span.appendChild( item.label ); 2770 } else { 2771 span.innerHTML = item.label; 2772 } 2773 2774 // APPEND ITEMS 2775 if ( item[ "class" ] ) { 2776 li.className = item[ "class" ]; 2777 } 2778 2779 if ( item.style ) { 2780 li.setAttribute( "style", item.style ); 2781 } 2782 2783 if ( item.icon ) { 2784 img.setAttribute( "src", ( !item.ignore && item.icon.slice( 0, 10 ).indexOf( "//" ) == -1 ? chart.pathToImages : "" ) + item.icon ); 2785 a.appendChild( img ); 2786 } 2787 if ( item.label ) { 2788 a.appendChild( span ); 2789 } 2790 if ( item.title ) { 2791 a.setAttribute( "title", item.title ); 2792 } 2793 2794 // CALLBACK; REVIVER FOR MENU ITEMS 2795 if ( _this.config.menuReviver ) { 2796 li = _this.config.menuReviver.apply( _this, [ item, li ] ); 2797 } 2798 2799 // ADD ELEMENTS FOR EASY ACCESS 2800 item.elements = { 2801 li: li, 2802 a: a, 2803 img: img, 2804 span: span 2805 } 2806 2807 // ADD SUBLIST; JUST WITH ENTRIES 2808 if ( ( item.menu || item.items ) && item.action != "draw" ) { 2809 if ( buildList( item.menu || item.items, li ).childNodes.length ) { 2810 ul.appendChild( li ); 2811 } 2812 } else { 2813 ul.appendChild( li ); 2814 } 2815 } 2816 2817 // JUST ADD THOSE WITH ENTRIES 2818 if ( ul.childNodes.length ) { 2819 container.appendChild( ul ); 2820 } 2821 2822 return ul; 2823 } 2824 2825 // DETERMINE CONTAINER 2826 if ( !container ) { 2827 if ( typeof _this.config.divId == "string" ) { 2828 _this.config.divId = container = document.getElementById( _this.config.divId ); 2829 } else if ( _this.isElement( _this.config.divId ) ) { 2830 container = _this.config.divId; 2831 } else { 2832 container = _this.setup.chart.containerDiv; 2833 } 2834 } 2835 2836 // CREATE / RESET MENU CONTAINER 2837 if ( _this.isElement( _this.setup.menu ) ) { 2838 _this.setup.menu.innerHTML = ""; 2839 } else { 2840 _this.setup.menu = document.createElement( "div" ); 2841 } 2842 _this.setup.menu.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-menu " + _this.setup.chart.classNamePrefix + "-export-menu-" + _this.config.position + " amExportButton" ); 2843 2844 // CALLBACK; REPLACES THE MENU WALKER 2845 if ( _this.config.menuWalker ) { 2846 buildList = _this.config.menuWalker; 2847 } 2848 buildList.apply( this, [ list, _this.setup.menu ] ); 2849 2850 // JUST ADD THOSE WITH ENTRIES 2851 if ( _this.setup.menu.childNodes.length ) { 2852 container.appendChild( _this.setup.menu ); 2853 } 2854 2855 return _this.setup.menu; 2856 }, 2857 2858 /** 2859 * Method to trigger the callback delayed 2860 */ 2861 delay: function( options, callback ) { 2862 var cfg = _this.deepMerge( { 2863 delay: 3, 2864 precision: 2 2865 }, options || {} ); 2866 var t1, t2, start = Number( new Date() ); 2867 var menu = _this.createMenu( [ { 2868 label: _this.i18l( "capturing.delayed.menu.label" ).replace( "{{duration}}", AmCharts.toFixed( cfg.delay, cfg.precision ) ), 2869 title: _this.i18l( "capturing.delayed.menu.title" ), 2870 "class": "export-delayed-capturing", 2871 click: function() { 2872 clearTimeout( t1 ); 2873 clearTimeout( t2 ); 2874 _this.createMenu( _this.config.menu ); 2875 } 2876 } ] ); 2877 var label = menu.getElementsByTagName( "a" )[ 0 ]; 2878 2879 // MENU UPDATE 2880 t1 = setInterval( function() { 2881 var diff = cfg.delay - ( Number( new Date() ) - start ) / 1000; 2882 if ( diff <= 0 ) { 2883 clearTimeout( t1 ); 2884 if ( cfg.action != "draw" ) { 2885 _this.createMenu( _this.config.menu ); 2886 } 2887 } else if ( label ) { 2888 label.innerHTML = _this.i18l( "capturing.delayed.menu.label" ).replace( "{{duration}}", AmCharts.toFixed( diff, 2 ) ); 2889 } 2890 }, 10 ); 2891 2892 // CALLBACK 2893 t2 = setTimeout( function() { 2894 callback.apply( _this, arguments ); 2895 }, cfg.delay * 1000 ); 2896 }, 2897 2898 /** 2899 * Migration method to support old export setup 2900 */ 2901 migrateSetup: function( setup ) { 2902 var cfg = { 2903 enabled: true, 2904 migrated: true, 2905 libs: { 2906 autoLoad: true 2907 }, 2908 menu: [] 2909 }; 2910 2911 function crawler( object ) { 2912 var key; 2913 for ( key in object ) { 2914 var value = object[ key ]; 2915 2916 if ( key.slice( 0, 6 ) == "export" && value ) { 2917 cfg.menu.push( key.slice( 6 ) ); 2918 } else if ( key == "userCFG" ) { 2919 crawler( value ); 2920 } else if ( key == "menuItems" ) { 2921 cfg.menu = value; 2922 } else if ( key == "libs" ) { 2923 cfg.libs = value; 2924 } else if ( typeof key == "string" ) { 2925 cfg[ key ] = value; 2926 } 2927 } 2928 } 2929 2930 crawler( setup ); 2931 2932 return cfg; 2933 }, 2934 2935 /* 2936 ** Add event listener 2937 */ 2938 loadListeners: function() { 2939 function handleClone( clone ) { 2940 if ( clone ) { 2941 clone.set( { 2942 top: clone.top + 10, 2943 left: clone.left + 10 2944 } ); 2945 _this.setup.fabric.add( clone ); 2946 } 2947 } 2948 2949 // OBSERVE; KEY LISTENER; DRAWING FEATURES 2950 if ( _this.config.keyListener && _this.config.keyListener != "attached" ) { 2951 _this.config.keyListener = "attached"; 2952 document.addEventListener( "keydown", function( e ) { 2953 var current = _this.drawing.buffer.target; 2954 2955 // REMOVE; key: BACKSPACE / DELETE 2956 if ( ( e.keyCode == 8 || e.keyCode == 46 ) && current ) { 2957 e.preventDefault(); 2958 _this.setup.fabric.remove( current ); 2959 2960 // ESCAPE DRAWIN MODE; key: escape 2961 } else if ( e.keyCode == 27 && _this.drawing.enabled ) { 2962 e.preventDefault(); 2963 _this.drawing.handler.done(); 2964 2965 // COPY; key: C 2966 } else if ( e.keyCode == 67 && ( e.metaKey || e.ctrlKey ) && current ) { 2967 _this.drawing.buffer.copy = current; 2968 2969 // CUT; key: X 2970 } else if ( e.keyCode == 88 && ( e.metaKey || e.ctrlKey ) && current ) { 2971 _this.drawing.buffer.copy = current; 2972 _this.setup.fabric.remove( current ); 2973 2974 // PASTE; key: V 2975 } else if ( e.keyCode == 86 && ( e.metaKey || e.ctrlKey ) ) { 2976 if ( _this.drawing.buffer.copy ) { 2977 handleClone( _this.drawing.buffer.copy.clone( handleClone ) ) 2978 } 2979 2980 // UNDO / REDO; key: Z 2981 } else if ( e.keyCode == 90 && ( e.metaKey || e.ctrlKey ) ) { 2982 e.preventDefault(); 2983 if ( e.shiftKey ) { 2984 _this.drawing.handler.redo(); 2985 } else { 2986 _this.drawing.handler.undo(); 2987 } 2988 } 2989 } ); 2990 } 2991 2992 // OBSERVE; DRAG AND DROP LISTENER; DRAWING FEATURE 2993 if ( _this.config.fileListener ) { 2994 _this.setup.chart.containerDiv.addEventListener( "dragover", _this.handleDropbox ); 2995 _this.setup.chart.containerDiv.addEventListener( "dragleave", _this.handleDropbox ); 2996 _this.setup.chart.containerDiv.addEventListener( "drop", _this.handleDropbox ); 2997 } 2998 }, 2999 3000 /** 3001 * Initiate export menu; waits for chart container to place menu 3002 */ 3003 init: function() { 3004 clearTimeout( _this.timer ); 3005 _this.timer = setInterval( function() { 3006 if ( _this.setup.chart.containerDiv ) { 3007 clearTimeout( _this.timer ); 3008 3009 if ( _this.config.enabled ) { 3010 // CREATE REFERENCE 3011 _this.setup.chart.AmExport = _this; 3012 3013 // OVERWRITE PARENT OVERFLOW 3014 if ( _this.config.overflow ) { 3015 _this.setup.chart.div.style.overflow = "visible"; 3016 } 3017 3018 // ATTACH EVENTS 3019 _this.loadListeners(); 3020 3021 // CREATE MENU 3022 _this.createMenu( _this.config.menu ); 3023 } 3024 } 3025 }, AmCharts.updateRate ); 3026 3027 }, 3028 3029 /** 3030 * Initiates export instance; merges given config; attaches event listener 3031 */ 3032 construct: function() { 3033 // ANNOTATION; MAP "DONE" 3034 _this.drawing.handler.cancel = _this.drawing.handler.done; 3035 3036 // CHECK BLOB CONSTRUCTOR 3037 try { 3038 _this.setup.hasBlob = !!new Blob; 3039 } catch ( e ) {} 3040 3041 // WORK AROUND TO BYPASS FILESAVER CHECK TRYING TO OPEN THE BLOB URL IN SAFARI BROWSER 3042 window.safari = window.safari ? window.safari : {}; 3043 3044 // OVERTAKE CHART FONTSIZE IF GIVEN 3045 _this.defaults.fabric.drawing.fontSize = _this.setup.chart.fontSize || 11; 3046 3047 // MERGE SETTINGS 3048 _this.config.drawing = _this.deepMerge( _this.defaults.fabric.drawing, _this.config.drawing || {}, true ); 3049 _this.deepMerge( _this.defaults.fabric, _this.config, true ); 3050 _this.deepMerge( _this.defaults.fabric, _this.config.fabric || {}, true ); 3051 _this.deepMerge( _this.defaults.pdfMake, _this.config, true ); 3052 _this.deepMerge( _this.defaults.pdfMake, _this.config.pdfMake || {}, true ); 3053 _this.deepMerge( _this.libs, _this.config.libs || {}, true ); 3054 3055 // UPDATE CONFIG 3056 _this.config.drawing = _this.defaults.fabric.drawing; 3057 _this.config.fabric = _this.defaults.fabric; 3058 _this.config.pdfMake = _this.defaults.pdfMake; 3059 _this.config = _this.deepMerge( _this.defaults, _this.config, true ); 3060 3061 // MERGE; SETUP DRAWING MENU 3062 if ( _this.config.fabric.drawing.enabled ) { 3063 if ( _this.config.fabric.drawing.menu === undefined ) { 3064 _this.config.fabric.drawing.menu = []; 3065 _this.deepMerge( _this.config.fabric.drawing.menu, [ { 3066 "class": "export-drawing", 3067 menu: [ { 3068 label: _this.i18l( "menu.label.draw.add" ), 3069 menu: [ { 3070 label: _this.i18l( "menu.label.draw.shapes" ), 3071 action: "draw.shapes" 3072 }, { 3073 label: _this.i18l( "menu.label.draw.text" ), 3074 action: "text" 3075 } ] 3076 }, { 3077 label: _this.i18l( "menu.label.draw.change" ), 3078 menu: [ { 3079 label: _this.i18l( "menu.label.draw.modes" ), 3080 action: "draw.modes" 3081 }, { 3082 label: _this.i18l( "menu.label.draw.colors" ), 3083 action: "draw.colors" 3084 }, { 3085 label: _this.i18l( "menu.label.draw.widths" ), 3086 action: "draw.widths" 3087 }, { 3088 label: _this.i18l( "menu.label.draw.opacities" ), 3089 action: "draw.opacities" 3090 }, "UNDO", "REDO" ] 3091 }, { 3092 label: _this.i18l( "menu.label.save.image" ), 3093 menu: [ "PNG", "JPG", "SVG", "PDF" ] 3094 }, "PRINT", "CANCEL" ] 3095 } ] ); 3096 } 3097 } 3098 3099 // MERGE; SETUP MAIN MENU 3100 if ( _this.config.menu === undefined ) { 3101 _this.config.menu = []; 3102 // PARENT MENU 3103 _this.deepMerge( _this.config, { 3104 menu: [ { 3105 "class": "export-main", 3106 menu: [ { 3107 label: _this.i18l( "menu.label.save.image" ), 3108 menu: [ "PNG", "JPG", "SVG", "PDF" ] 3109 }, { 3110 label: _this.i18l( "menu.label.save.data" ), 3111 menu: [ "CSV", "XLSX", "JSON" ] 3112 }, { 3113 label: _this.i18l( "menu.label.draw" ), 3114 action: "draw", 3115 menu: _this.config.fabric.drawing.menu 3116 }, { 3117 format: "PRINT", 3118 label: _this.i18l( "menu.label.print" ) 3119 } ] 3120 } ] 3121 } ); 3122 } 3123 3124 // ADD MISSING PATH 3125 if ( !_this.libs.path ) { 3126 _this.libs.path = _this.config.path + "libs/"; 3127 } 3128 3129 // CHECK ACCEPTANCE 3130 if ( _this.isSupported() ) { 3131 // LOAD DEPENDENCIES 3132 _this.loadDependencies( _this.libs.resources, _this.libs.reload ); 3133 // ADD CLASSNAMES 3134 _this.setup.chart.addClassNames = true; 3135 // REFERENCE 3136 _this.setup.chart[ _this.name ] = _this; 3137 // INIT MENU; WAIT FOR CHART INSTANCE 3138 _this.init(); 3139 } 3140 } 3141 } 3142 3143 // USE GIVEN CONFIG 3144 if ( config ) { 3145 _this.config = config; 3146 3147 // USE CHART EXPORT CONFIG 3148 } else if ( _this.setup.chart[ _this.name ] ) { 3149 _this.config = _this.setup.chart[ _this.name ]; 3150 3151 // MIGRATE OLD EXPORT CHART CONFIG 3152 } else if ( _this.setup.chart.amExport || _this.setup.chart.exportConfig ) { 3153 _this.config = _this.migrateSetup( _this.setup.chart.amExport || _this.setup.chart.exportConfig ); 3154 3155 // EXIT; NO CONFIG 3156 } else { 3157 return; 3158 } 3159 3160 // CONSTRUCT INSTANCE 3161 _this.construct(); 3162 3163 // EXPORT SCOPE 3164 return _this.deepMerge( this, _this ); 3165 } 3166} )(); 3167 3168/** 3169 * Set init handler 3170 */ 3171AmCharts.addInitHandler( function( chart ) { 3172 new AmCharts[ "export" ]( chart ); 3173 3174}, [ "pie", "serial", "xy", "funnel", "radar", "gauge", "stock", "map", "gantt" ] );