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