Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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