Subversion Repositories php-qbpwcf

Rev

Rev 23 | Details | Compare with Previous | Last modification | View Log | RSS feed

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