amcharts-jschart
Version:
A go-to library for data visualization. When you don’t have time to learn new technologies. When you need a simple yet powerful and flexible drop-in data visualization solution.
1,624 lines (1,426 loc) • 125 kB
JavaScript
/*
Plugin Name: amCharts Export
Description: Adds export capabilities to amCharts products
Author: Benjamin Maertz, amCharts
Version: 1.4.56
Author URI: http://www.amcharts.com/
Copyright 2016 amCharts
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Please note that the above license covers only this plugin. It by all means does
not apply to any other amCharts products that are covered by different licenses.
*/
/*
** Polyfill translation
*/
if ( !AmCharts.translations[ "export" ] ) {
AmCharts.translations[ "export" ] = {}
}
if ( !AmCharts.translations[ "export" ][ "en" ] ) {
AmCharts.translations[ "export" ][ "en" ] = {
"fallback.save.text": "CTRL + C to copy the data into the clipboard.",
"fallback.save.image": "Rightclick -> Save picture as... to save the image.",
"capturing.delayed.menu.label": "{{duration}}",
"capturing.delayed.menu.title": "Click to cancel",
"menu.label.print": "Print",
"menu.label.undo": "Undo",
"menu.label.redo": "Redo",
"menu.label.cancel": "Cancel",
"menu.label.save.image": "Download as ...",
"menu.label.save.data": "Save as ...",
"menu.label.draw": "Annotate ...",
"menu.label.draw.change": "Change ...",
"menu.label.draw.add": "Add ...",
"menu.label.draw.shapes": "Shape ...",
"menu.label.draw.colors": "Color ...",
"menu.label.draw.widths": "Size ...",
"menu.label.draw.opacities": "Opacity ...",
"menu.label.draw.text": "Text",
"menu.label.draw.modes": "Mode ...",
"menu.label.draw.modes.pencil": "Pencil",
"menu.label.draw.modes.line": "Line",
"menu.label.draw.modes.arrow": "Arrow",
"label.saved.from": "Saved from: "
}
}
/*
** Polyfill export class
*/
( function() {
AmCharts[ "export" ] = function( chart, config ) {
var _timer;
var _this = {
name: "export",
version: "1.4.56",
libs: {
async: true,
autoLoad: true,
reload: false,
resources: [ "fabric.js/fabric.min.js", "FileSaver.js/FileSaver.min.js", {
"jszip/jszip.min.js": [ "xlsx/xlsx.min.js" ],
"pdfmake/pdfmake.min.js": [ "pdfmake/vfs_fonts.js" ]
} ],
namespaces: {
"pdfmake.min.js": "pdfMake",
"jszip.min.js": "JSZip",
"xlsx.min.js": "XLSX",
"fabric.min.js": "fabric",
"FileSaver.min.js": "saveAs"
},
loadTimeout: 10000
},
config: {},
setup: {
chart: chart,
hasBlob: false,
wrapper: false,
isIE: !!window.document.documentMode,
IEversion: window.document.documentMode,
hasTouch: typeof window.Touch == "object"
},
drawing: {
enabled: false,
undos: [],
redos: [],
buffer: {
position: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
xD: 0,
yD: 0
}
},
handler: {
undo: function( options, skipped ) {
var item = _this.drawing.undos.pop();
if ( item ) {
item.selectable = true;
_this.drawing.redos.push( item );
if ( item.action == "added" ) {
_this.setup.fabric.remove( item.target );
}
var state = JSON.parse( item.state );
item.target.set( state );
if ( item.target instanceof fabric.Group ) {
_this.drawing.handler.change( {
color: state.cfg.color,
width: state.cfg.width,
opacity: state.cfg.opacity
}, true, item.target );
}
_this.setup.fabric.renderAll();
// RECALL
if ( item.state == item.target.recentState && !skipped ) {
_this.drawing.handler.undo( item, true );
}
}
},
redo: function( options, skipped ) {
var item = _this.drawing.redos.pop();
if ( item ) {
item.selectable = true;
_this.drawing.undos.push( item );
if ( item.action == "added" ) {
_this.setup.fabric.add( item.target );
}
var state = JSON.parse( item.state );
item.target.recentState = item.state;
item.target.set( state );
if ( item.target instanceof fabric.Group ) {
_this.drawing.handler.change( {
color: state.cfg.color,
width: state.cfg.width,
opacity: state.cfg.opacity
}, true, item.target );
}
_this.setup.fabric.renderAll();
// RECALL
if ( item.action == "addified" ) {
_this.drawing.handler.redo();
}
}
},
done: function( options ) {
_this.drawing.enabled = false;
_this.drawing.buffer.enabled = false;
_this.drawing.undos = [];
_this.drawing.redos = [];
_this.createMenu( _this.config.menu );
_this.setup.fabric.deactivateAll();
if ( _this.setup.wrapper ) {
_this.setup.chart.containerDiv.removeChild( _this.setup.wrapper );
_this.setup.wrapper = false;
}
},
add: function( options ) {
var cfg = _this.deepMerge( {
top: _this.setup.fabric.height / 2,
left: _this.setup.fabric.width / 2
}, options || {} );
var method = cfg.url.indexOf( ".svg" ) != -1 ? fabric.loadSVGFromURL : fabric.Image.fromURL;
method( cfg.url, function( objects, options ) {
var group = options !== undefined ? fabric.util.groupSVGElements( objects, options ) : objects;
var ratio = false;
// RESCALE ONLY IF IT EXCEEDS THE CANVAS
if ( group.height > _this.setup.fabric.height || group.width > _this.setup.fabric.width ) {
ratio = ( _this.setup.fabric.height / 2 ) / group.height;
}
if ( cfg.top > _this.setup.fabric.height ) {
cfg.top = _this.setup.fabric.height / 2;
}
if ( cfg.left > _this.setup.fabric.width ) {
cfg.left = _this.setup.fabric.width / 2;
}
// SET DRAWING FLAG
_this.drawing.buffer.isDrawing = true;
group.set( {
originX: "center",
originY: "center",
top: cfg.top,
left: cfg.left,
width: ratio ? group.width * ratio : group.width,
height: ratio ? group.height * ratio : group.height,
fill: _this.drawing.color
} );
_this.setup.fabric.add( group );
} );
},
change: function( options, skipped, target ) {
var cfg = _this.deepMerge( {}, options || {} );
var state, i1, rgba;
var current = target || _this.drawing.buffer.target;
var objects = current ? current._objects ? current._objects : [ current ] : null;
// UPDATE DRAWING OBJECT
if ( cfg.mode ) {
_this.drawing.mode = cfg.mode;
}
if ( cfg.width ) {
_this.drawing.width = cfg.width;
_this.drawing.fontSize = cfg.fontSize = cfg.width * 3;
// BACK TO DEFAULT
if ( _this.drawing.width == 1 ) {
_this.drawing.fontSize = cfg.fontSize = _this.defaults.fabric.drawing.fontSize;
}
}
if ( cfg.fontSize ) {
_this.drawing.fontSize = cfg.fontSize;
}
if ( cfg.color ) {
_this.drawing.color = cfg.color;
}
if ( cfg.opacity ) {
_this.drawing.opacity = cfg.opacity;
}
// APPLY OPACITY ON CURRENT COLOR
rgba = _this.getRGBA( _this.drawing.color );
rgba.pop();
rgba.push( _this.drawing.opacity );
_this.drawing.color = "rgba(" + rgba.join() + ")";
_this.setup.fabric.freeDrawingBrush.color = _this.drawing.color;
_this.setup.fabric.freeDrawingBrush.width = _this.drawing.width;
// UPDATE CURRENT SELECTION
if ( current ) {
state = JSON.parse( current.recentState ).cfg;
// UPDATE GIVE OPTIONS ONLY
if ( state ) {
cfg.color = cfg.color || state.color;
cfg.width = cfg.width || state.width;
cfg.opacity = cfg.opacity || state.opacity;
cfg.fontSize = cfg.fontSize || state.fontSize;
rgba = _this.getRGBA( cfg.color );
rgba.pop();
rgba.push( cfg.opacity );
cfg.color = "rgba(" + rgba.join() + ")";
}
// UPDATE OBJECTS
for ( i1 = 0; i1 < objects.length; i1++ ) {
if (
objects[ i1 ] instanceof fabric.Text ||
objects[ i1 ] instanceof fabric.PathGroup ||
objects[ i1 ] instanceof fabric.Triangle
) {
if ( cfg.color || cfg.opacity ) {
objects[ i1 ].set( {
fill: cfg.color
} );
}
if ( cfg.fontSize ) {
objects[ i1 ].set( {
fontSize: cfg.fontSize
} );
}
} else if (
objects[ i1 ] instanceof fabric.Path ||
objects[ i1 ] instanceof fabric.Line
) {
if ( current instanceof fabric.Group ) {
if ( cfg.color || cfg.opacity ) {
objects[ i1 ].set( {
stroke: cfg.color
} );
}
} else {
if ( cfg.color || cfg.opacity ) {
objects[ i1 ].set( {
stroke: cfg.color
} );
}
if ( cfg.width ) {
objects[ i1 ].set( {
strokeWidth: cfg.width
} );
}
}
}
}
// ADD UNDO
if ( !skipped ) {
state = JSON.stringify( _this.deepMerge( current.saveState().originalState, {
cfg: {
color: cfg.color,
width: cfg.width,
opacity: cfg.opacity
}
} ) );
current.recentState = state;
_this.drawing.redos = [];
_this.drawing.undos.push( {
action: "modified",
target: current,
state: state
} );
}
_this.setup.fabric.renderAll();
}
},
text: function( options ) {
var cfg = _this.deepMerge( {
text: _this.i18l( "menu.label.draw.text" ),
top: _this.setup.fabric.height / 2,
left: _this.setup.fabric.width / 2,
fontSize: _this.drawing.fontSize,
fontFamily: _this.setup.chart.fontFamily || "Verdana",
fill: _this.drawing.color
}, options || {} );
cfg.click = function() {};
var text = new fabric.IText( cfg.text, cfg );
// SET DRAWING FLAG
_this.drawing.buffer.isDrawing = true;
_this.setup.fabric.add( text );
_this.setup.fabric.setActiveObject( text );
text.selectAll();
text.enterEditing();
return text;
},
line: function( options ) {
var cfg = _this.deepMerge( {
x1: ( _this.setup.fabric.width / 2 ) - ( _this.setup.fabric.width / 10 ),
x2: ( _this.setup.fabric.width / 2 ) + ( _this.setup.fabric.width / 10 ),
y1: ( _this.setup.fabric.height / 2 ),
y2: ( _this.setup.fabric.height / 2 ),
angle: 90,
strokeLineCap: _this.drawing.lineCap,
arrow: _this.drawing.arrow,
color: _this.drawing.color,
width: _this.drawing.width,
group: [],
}, options || {} );
var i1, arrow, arrowTop, arrowLeft;
var line = new fabric.Line( [ cfg.x1, cfg.y1, cfg.x2, cfg.y2 ], {
stroke: cfg.color,
strokeWidth: cfg.width,
strokeLineCap: cfg.strokeLineCap
} );
cfg.group.push( line );
if ( cfg.arrow ) {
cfg.angle = cfg.angle ? cfg.angle : _this.getAngle( cfg.x1, cfg.y1, cfg.x2, cfg.y2 );
if ( cfg.arrow == "start" ) {
arrowTop = cfg.y1 + ( cfg.width / 2 );
arrowLeft = cfg.x1 + ( cfg.width / 2 );
} else if ( cfg.arrow == "middle" ) {
arrowTop = cfg.y2 + ( cfg.width / 2 ) - ( ( cfg.y2 - cfg.y1 ) / 2 );
arrowLeft = cfg.x2 + ( cfg.width / 2 ) - ( ( cfg.x2 - cfg.x1 ) / 2 );
} else { // arrow: end
arrowTop = cfg.y2 + ( cfg.width / 2 );
arrowLeft = cfg.x2 + ( cfg.width / 2 );
}
arrow = new fabric.Triangle( {
top: arrowTop,
left: arrowLeft,
fill: cfg.color,
height: cfg.width * 7,
width: cfg.width * 7,
angle: cfg.angle,
originX: "center",
originY: "bottom"
} );
cfg.group.push( arrow );
}
// SET DRAWING FLAG
_this.drawing.buffer.isDrawing = true;
if ( cfg.action != "config" ) {
if ( cfg.arrow ) {
var group = new fabric.Group( cfg.group );
group.set( {
cfg: cfg,
fill: cfg.color,
action: cfg.action,
selectable: true,
known: cfg.action == "change"
} );
if ( cfg.action == "change" ) {
_this.setup.fabric.setActiveObject( group );
}
_this.setup.fabric.add( group );
return group;
} else {
_this.setup.fabric.add( line );
return line;
}
} else {
for ( i1 = 0; i1 < cfg.group.length; i1++ ) {
_this.setup.fabric.add( cfg.group[ i1 ] );
}
}
return cfg;
}
}
},
defaults: {
position: "top-right",
fileName: "amCharts",
action: "download",
overflow: true,
path: ( ( chart.path || "" ) + "plugins/export/" ),
formats: {
JPG: {
mimeType: "image/jpg",
extension: "jpg",
capture: true
},
PNG: {
mimeType: "image/png",
extension: "png",
capture: true
},
SVG: {
mimeType: "text/xml",
extension: "svg",
capture: true
},
PDF: {
mimeType: "application/pdf",
extension: "pdf",
capture: true
},
CSV: {
mimeType: "text/plain",
extension: "csv"
},
JSON: {
mimeType: "text/plain",
extension: "json"
},
XLSX: {
mimeType: "application/octet-stream",
extension: "xlsx"
}
},
fabric: {
backgroundColor: "#FFFFFF",
removeImages: true,
forceRemoveImages: false,
selection: false,
loadTimeout: 5000,
drawing: {
enabled: true,
arrow: "end",
lineCap: "butt",
mode: "pencil",
modes: [ "pencil", "line", "arrow" ],
color: "#000000",
colors: [ "#000000", "#FFFFFF", "#FF0000", "#00FF00", "#0000FF" ],
shapes: [ "11.svg", "14.svg", "16.svg", "17.svg", "20.svg", "27.svg" ],
width: 1,
fontSize: 11,
widths: [ 1, 5, 10, 15 ],
opacity: 1,
opacities: [ 1, 0.8, 0.6, 0.4, 0.2 ],
menu: undefined,
autoClose: true
},
border: {
fill: "",
fillOpacity: 0,
stroke: "#000000",
strokeWidth: 1,
strokeOpacity: 1
}
},
pdfMake: {
images: {},
pageOrientation: "portrait",
pageMargins: 40,
pageOrigin: true,
pageSize: "A4",
pageSizes: {
"4A0": [ 4767.87, 6740.79 ],
"2A0": [ 3370.39, 4767.87 ],
"A0": [ 2383.94, 3370.39 ],
"A1": [ 1683.78, 2383.94 ],
"A2": [ 1190.55, 1683.78 ],
"A3": [ 841.89, 1190.55 ],
"A4": [ 595.28, 841.89 ],
"A5": [ 419.53, 595.28 ],
"A6": [ 297.64, 419.53 ],
"A7": [ 209.76, 297.64 ],
"A8": [ 147.40, 209.76 ],
"A9": [ 104.88, 147.40 ],
"A10": [ 73.70, 104.88 ],
"B0": [ 2834.65, 4008.19 ],
"B1": [ 2004.09, 2834.65 ],
"B2": [ 1417.32, 2004.09 ],
"B3": [ 1000.63, 1417.32 ],
"B4": [ 708.66, 1000.63 ],
"B5": [ 498.90, 708.66 ],
"B6": [ 354.33, 498.90 ],
"B7": [ 249.45, 354.33 ],
"B8": [ 175.75, 249.45 ],
"B9": [ 124.72, 175.75 ],
"B10": [ 87.87, 124.72 ],
"C0": [ 2599.37, 3676.54 ],
"C1": [ 1836.85, 2599.37 ],
"C2": [ 1298.27, 1836.85 ],
"C3": [ 918.43, 1298.27 ],
"C4": [ 649.13, 918.43 ],
"C5": [ 459.21, 649.13 ],
"C6": [ 323.15, 459.21 ],
"C7": [ 229.61, 323.15 ],
"C8": [ 161.57, 229.61 ],
"C9": [ 113.39, 161.57 ],
"C10": [ 79.37, 113.39 ],
"RA0": [ 2437.80, 3458.27 ],
"RA1": [ 1729.13, 2437.80 ],
"RA2": [ 1218.90, 1729.13 ],
"RA3": [ 864.57, 1218.90 ],
"RA4": [ 609.45, 864.57 ],
"SRA0": [ 2551.18, 3628.35 ],
"SRA1": [ 1814.17, 2551.18 ],
"SRA2": [ 1275.59, 1814.17 ],
"SRA3": [ 907.09, 1275.59 ],
"SRA4": [ 637.80, 907.09 ],
"EXECUTIVE": [ 521.86, 756.00 ],
"FOLIO": [ 612.00, 936.00 ],
"LEGAL": [ 612.00, 1008.00 ],
"LETTER": [ 612.00, 792.00 ],
"TABLOID": [ 792.00, 1224.00 ]
}
},
menu: undefined,
divId: null,
menuReviver: null,
menuWalker: null,
fallback: true,
keyListener: true,
fileListener: true,
compress: true,
debug: false
},
/**
* Returns translated message, takes english as default
*/
i18l: function( key, language ) {
var lang = language ? language : _this.setup.chart.language ? _this.setup.chart.language : "en";
var catalog = AmCharts.translations[ _this.name ][ lang ] || AmCharts.translations[ _this.name ][ "en" ];
return catalog[ key ] || key;
},
/**
* Generates download file; if unsupported offers fallback to save manually
*/
download: function( data, type, filename ) {
// SAVE
if ( window.saveAs && _this.setup.hasBlob ) {
var blob = _this.toBlob( {
data: data,
type: type
}, function( data ) {
saveAs( data, filename );
} );
// FALLBACK TEXTAREA
} else if ( _this.config.fallback && type == "text/plain" ) {
var div = document.createElement( "div" );
var msg = document.createElement( "div" );
var textarea = document.createElement( "textarea" );
msg.innerHTML = _this.i18l( "fallback.save.text" );
div.appendChild( msg );
div.appendChild( textarea );
msg.setAttribute( "class", "amcharts-export-fallback-message" );
div.setAttribute( "class", "amcharts-export-fallback" );
_this.setup.chart.containerDiv.appendChild( div );
// FULFILL TEXTAREA AND PRESELECT
textarea.setAttribute( "readonly", "" );
textarea.value = data;
textarea.focus();
textarea.select();
// UPDATE MENU
_this.createMenu( [ {
"class": "export-main export-close",
label: "Done",
click: function() {
_this.createMenu( _this.config.menu );
_this.setup.chart.containerDiv.removeChild( div );
}
} ] );
// FALLBACK IMAGE
} else if ( _this.config.fallback && type.split( "/" )[ 0 ] == "image" ) {
var div = document.createElement( "div" );
var msg = document.createElement( "div" );
var img = _this.toImage( {
data: data
} );
msg.innerHTML = _this.i18l( "fallback.save.image" );
// FULFILL TEXTAREA AND PRESELECT
div.appendChild( msg );
div.appendChild( img );
msg.setAttribute( "class", "amcharts-export-fallback-message" );
div.setAttribute( "class", "amcharts-export-fallback" );
_this.setup.chart.containerDiv.appendChild( div );
// UPDATE MENU
_this.createMenu( [ {
"class": "export-main export-close",
label: "Done",
click: function() {
_this.createMenu( _this.config.menu );
_this.setup.chart.containerDiv.removeChild( div );
}
} ] );
// ERROR
} else {
throw new Error( "Unable to create file. Ensure saveAs (FileSaver.js) is supported." );
}
return data;
},
/**
* Generates script, links tags and places them into the document's head
* In case of reload it replaces the node to force the download
*/
loadResource: function( src, addons ) {
var i1, exist, node, item, check, type;
var url = src.indexOf( "//" ) != -1 ? src : [ _this.libs.path, src ].join( "" );
var loadCallback = function callback() {
if ( addons ) {
for ( i1 = 0; i1 < addons.length; i1++ ) {
_this.loadResource( addons[ i1 ] );
}
}
}
if ( src.indexOf( ".js" ) != -1 ) {
node = document.createElement( "script" );
node.setAttribute( "type", "text/javascript" );
node.setAttribute( "src", url );
if ( _this.libs.async ) {
node.setAttribute( "async", "" );
}
} else if ( src.indexOf( ".css" ) != -1 ) {
node = document.createElement( "link" );
node.setAttribute( "type", "text/css" );
node.setAttribute( "rel", "stylesheet" );
node.setAttribute( "href", url );
}
// NODE CHECK
for ( i1 = 0; i1 < document.head.childNodes.length; i1++ ) {
item = document.head.childNodes[ i1 ];
check = item ? ( item.src || item.href ) : false;
type = item ? item.tagName : false;
if ( item && check && check.indexOf( src ) != -1 ) {
if ( _this.libs.reload ) {
document.head.removeChild( item );
}
exist = true;
break;
}
}
// NAMESPACE CHECK
for ( i1 in _this.libs.namespaces ) {
var namespace = _this.libs.namespaces[ i1 ];
var check = src.toLowerCase();
var item = i1.toLowerCase();
if ( check.indexOf( item ) != -1 && window[ namespace ] !== undefined ) {
exist = true;
break;
}
}
if ( !exist || _this.libs.reload ) {
node.addEventListener( "load", loadCallback );
node.addEventListener( "error", function() {
_this.handleLog( [ "amCharts[export]: Loading error on ", this.src || this.href ].join( "" ) );
} );
document.head.appendChild( node );
if ( !_this.listenersToRemove ) {
_this.listenersToRemove = [];
}
_this.listenersToRemove.push( {
node: node,
method: loadCallback,
event: "load"
} );
}
},
/**
* Walker to generate the script,link tags
*/
loadDependencies: function() {
var i1, i2;
if ( _this.libs.autoLoad ) {
for ( i1 = 0; i1 < _this.libs.resources.length; i1++ ) {
if ( _this.libs.resources[ i1 ] instanceof Object ) {
for ( i2 in _this.libs.resources[ i1 ] ) {
_this.loadResource( i2, _this.libs.resources[ i1 ][ i2 ] );
}
} else {
_this.loadResource( _this.libs.resources[ i1 ] );
}
}
}
},
/**
* Converts string to number
*/
pxToNumber: function( attr, returnUndefined ) {
if ( !attr && returnUndefined ) {
return undefined;
}
return Number( String( attr ).replace( "px", "" ) ) || 0;
},
/**
* Converts number to string
*/
numberToPx: function( attr ) {
return String( attr ) + "px";
},
/**
* Referenceless copy of object type variables
*/
cloneObject: function( o ) {
var clone, v, k, isObject, isDate;
clone = Array.isArray( o ) ? [] : {};
// Walkthrough values
for ( k in o ) {
v = o[ k ];
isObject = typeof v === "object";
isDate = v instanceof Date;
// Set value; call recursivly if value is an object
clone[ k ] = isObject && !isDate ? _this.cloneObject( v ) : v;
}
return clone;
},
/**
* Recursive method to merge the given objects together
* Overwrite flag replaces the value instead to crawl through
*/
deepMerge: function( a, b, overwrite ) {
var i1, v, type = b instanceof Array ? "array" : "object";
// SKIP; OBJECTS AND ARRAYS ONLY
if ( !( a instanceof Object || a instanceof Array ) ) {
return a;
}
// WALKTHOUGH SOURCE
for ( i1 in b ) {
// PREVENT METHODS
if ( type == "array" && isNaN( i1 ) ) {
continue;
}
// ASSIGN VALUE
v = b[ i1 ];
// NEW INSTANCE
if ( a[ i1 ] == undefined || overwrite ) {
if ( v instanceof Array ) {
a[ i1 ] = new Array();
} else if ( v instanceof Function ) {
a[ i1 ] = function() {};
} else if ( v instanceof Date ) {
a[ i1 ] = new Date();
} else if ( v instanceof Object ) {
a[ i1 ] = new Object();
} else if ( v instanceof Number ) {
a[ i1 ] = new Number();
} else if ( v instanceof String ) {
a[ i1 ] = new String();
}
}
// WALKTHROUGH RECURSIVLY
if (
( v instanceof Object || v instanceof Array ) &&
!( v instanceof Function || v instanceof Date || _this.isElement( v ) ) &&
i1 != "chart" &&
i1 != "scope"
) {
_this.deepMerge( a[ i1 ], v, overwrite );
// ASSIGN
} else {
if ( a instanceof Array && !overwrite ) {
a.push( v );
} else {
a[ i1 ] = v;
}
}
}
return a;
},
/**
* Checks if given argument is a valid node
*/
isElement: function( thingy ) {
return thingy instanceof Object && thingy && thingy.nodeType === 1;
},
/**
* Checks if given argument contains a hashbang and returns it
*/
isHashbanged: function( thingy ) {
var str = String( thingy ).replace( /\"/g, "" );
return str.slice( 0, 3 ) == "url" ? str.slice( str.indexOf( "#" ) + 1, str.length - 1 ) : false;
},
/**
* Checks if given event has been thrown with pressed click / touch
*/
isPressed: function( event ) {
// IE EXCEPTION
if ( event.type == "mousemove" && event.which === 1 ) {
// IGNORE
// OTHERS
} else if (
event.type == "touchmove" ||
event.buttons === 1 ||
event.button === 1 ||
event.which === 1
) {
_this.drawing.buffer.isPressed = true;
} else {
_this.drawing.buffer.isPressed = false;
}
return _this.drawing.buffer.isPressed;
},
/**
* Checks if given source needs to be removed
*/
removeImage: function( source ) {
if ( source ) {
// FORCE REMOVAL
if ( _this.config.fabric.forceRemoveImages ) {
return true;
// REMOVE TAINTED
} else if ( _this.config.fabric.removeImages && _this.isTainted( source ) ) {
return true;
// IE 10 internal bug handling SVG images in canvas context
} else if ( _this.setup.isIE && ( _this.setup.IEversion == 10 || _this.setup.IEversion == 11 ) && source.toLowerCase().indexOf( ".svg" ) != -1 ) {
return true;
}
}
return false
},
/**
* Checks if given source is within the current origin
*/
isTainted: function( source ) {
var origin = String( window.location.origin || window.location.protocol + "//" + window.location.hostname + ( window.location.port ? ':' + window.location.port : '' ) );
// CHECK GIVEN SOURCE
if ( source ) {
// LOCAL FILES ARE ALWAYS TAINTED
if (
origin.indexOf( ":\\" ) != -1 || source.indexOf( ":\\" ) != -1 ||
origin.indexOf( "file://" ) != -1 || source.indexOf( "file://" ) != -1
) {
return true
// MISMATCHING ORIGIN
} else if ( source.indexOf( "//" ) != -1 && source.indexOf( origin.replace( /.*:/, "" ) ) == -1 ) {
return true;
}
}
return false;
},
/*
** Checks several indicators for acceptance;
*/
isSupported: function() {
// CHECK CONFIG
if ( !_this.config.enabled ) {
return false;
}
// CHECK IE; ATTEMPT TO ACCESS HEAD ELEMENT
if ( _this.setup.isIE && _this.setup.IEversion <= 9 ) {
if ( !Array.prototype.indexOf || !document.head || _this.config.fallback === false ) {
return false;
}
}
return true;
},
getAngle: function( x1, y1, x2, y2 ) {
var x = x2 - x1;
var y = y2 - y1;
var angle;
if ( x == 0 ) {
if ( y == 0 ) {
angle = 0;
} else if ( y > 0 ) {
angle = Math.PI / 2;
} else {
angle = Math.PI * 3 / 2;
}
} else if ( y == 0 ) {
if ( x > 0 ) {
angle = 0;
} else {
angle = Math.PI;
}
} else {
if ( x < 0 ) {
angle = Math.atan( y / x ) + Math.PI;
} else if ( y < 0 ) {
angle = Math.atan( y / x ) + ( 2 * Math.PI );
} else {
angle = Math.atan( y / x );
}
}
return angle * 180 / Math.PI;
},
/**
* Recursive method which crawls upwards to gather the requested attribute
*/
gatherAttribute: function( elm, attr, limit, lvl ) {
var value, lvl = lvl ? lvl : 0,
limit = limit ? limit : 3;
if ( elm ) {
value = elm.getAttribute( attr );
if ( !value && lvl < limit ) {
return _this.gatherAttribute( elm.parentNode, attr, limit, lvl + 1 );
}
}
return value;
},
/**
* Recursive method which crawls upwards to gather the requested classname
*/
gatherClassName: function( elm, className, limit, lvl ) {
var value, lvl = lvl ? lvl : 0,
limit = limit ? limit : 3;
if ( _this.isElement( elm ) ) {
value = ( elm.getAttribute( "class" ) || "" ).split( " " ).indexOf( className ) != -1;
if ( !value && lvl < limit ) {
return _this.gatherClassName( elm.parentNode, className, limit, lvl + 1 );
} else if ( value ) {
value = elm;
}
}
return value;
},
/**
* Collects the clip-paths and patterns
*/
gatherElements: function( group, cfg, images ) {
var i1, i2;
for ( i1 = 0; i1 < group.children.length; i1++ ) {
var childNode = group.children[ i1 ];
// CLIPPATH
if ( childNode.tagName == "clipPath" ) {
var bbox = {};
var transform = fabric.parseTransformAttribute( _this.gatherAttribute( childNode, "transform" ) );
// HIDE SIBLINGS; GATHER IT'S DIMENSIONS
for ( i2 = 0; i2 < childNode.childNodes.length; i2++ ) {
childNode.childNodes[ i2 ].setAttribute( "fill", "transparent" );
bbox = {
x: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "x" ) ),
y: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "y" ) ),
width: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "width" ) ),
height: _this.pxToNumber( childNode.childNodes[ i2 ].getAttribute( "height" ) )
}
}
group.clippings[ childNode.id ] = {
svg: childNode,
bbox: bbox,
transform: transform
};
// PATTERN
} else if ( childNode.tagName == "pattern" ) {
var props = {
node: childNode,
source: childNode.getAttribute( "xlink:href" ),
width: Number( childNode.getAttribute( "width" ) ),
height: Number( childNode.getAttribute( "height" ) ),
repeat: "repeat",
offsetX: 0,
offsetY: 0
}
// GATHER BACKGROUND
for ( i2 = 0; i2 < childNode.childNodes.length; i2++ ) {
// RECT; COLOR
if ( childNode.childNodes[ i2 ].tagName == "rect" ) {
props.fill = childNode.childNodes[ i2 ].getAttribute( "fill" );
// IMAGE
} else if ( childNode.childNodes[ i2 ].tagName == "image" ) {
var attrs = fabric.parseAttributes( childNode.childNodes[ i2 ], fabric.SHARED_ATTRIBUTES );
if ( attrs.transformMatrix ) {
props.offsetX = attrs.transformMatrix[ 4 ];
props.offsetY = attrs.transformMatrix[ 5 ];
}
}
}
// TAINTED
if ( _this.removeImage( props.source ) ) {
group.patterns[ childNode.id ] = props.fill ? props.fill : "transparent";
} else {
group.patterns[ props.node.id ] = props;
}
// IMAGES
} else if ( childNode.tagName == "image" ) {
images.included++;
// LOAD IMAGE MANUALLY; TO RERENDER THE CANVAS
fabric.Image.fromURL( childNode.getAttribute( "xlink:href" ), function( img ) {
images.loaded++;
} );
// FILL STROKE POLYFILL ON EVERY ELEMENT
} else {
var attrs = [ "fill", "stroke" ];
for ( i2 = 0; i2 < attrs.length; i2++ ) {
var attr = attrs[ i2 ];
var attrVal = childNode.getAttribute( attr );
var attrRGBA = _this.getRGBA( attrVal );
var isHashbanged = _this.isHashbanged( attrVal );
// VALIDATE AND RESET UNKNOWN COLORS (avoids fabric to crash)
if ( attrVal && !attrRGBA && !isHashbanged ) {
childNode.setAttribute( attr, "none" );
childNode.setAttribute( attr + "-opacity", "0" );
}
}
}
}
return group;
},
/*
** GET RGBA COLOR ARRAY FROM INPUT
*/
getRGBA: function( source, returnInstance ) {
if ( source != "none" && source != "transparent" && !_this.isHashbanged( source ) ) {
source = new fabric.Color( source );
if ( source._source ) {
return returnInstance ? source : source.getSource();
}
}
return false;
},
/*
** GATHER MOUSE POSITION;
*/
gatherPosition: function( event, type ) {
var ref = _this.drawing.buffer.position;
var ivt = fabric.util.invertTransform( _this.setup.fabric.viewportTransform );
var pos;
if ( event.type == "touchmove" ) {
if ( "touches" in event ) {
event = event.touches[ 0 ];
} else if ( "changedTouches" in event ) {
event = event.changedTouches[ 0 ];
}
}
pos = fabric.util.transformPoint( _this.setup.fabric.getPointer( event, true ), ivt );
if ( type == 1 ) {
ref.x1 = pos.x;
ref.y1 = pos.y;
}
ref.x2 = pos.x;
ref.y2 = pos.y;
ref.xD = ( ref.x1 - ref.x2 ) < 0 ? ( ref.x1 - ref.x2 ) * -1 : ( ref.x1 - ref.x2 );
ref.yD = ( ref.y1 - ref.y2 ) < 0 ? ( ref.y1 - ref.y2 ) * -1 : ( ref.y1 - ref.y2 );
return ref;
},
modifyFabric: function() {
// ADAPTED THE WAY TO RECEIVE THE GRADIENTID
fabric.ElementsParser.prototype.resolveGradient = function( obj, property ) {
var instanceFillValue = obj.get( property );
if ( !( /^url\(/ ).test( instanceFillValue ) ) {
return;
}
var gradientId = instanceFillValue.slice( instanceFillValue.indexOf( "#" ) + 1, instanceFillValue.length - 1 );
if ( fabric.gradientDefs[ this.svgUid ][ gradientId ] ) {
var tmp = fabric.Gradient.fromElement( fabric.gradientDefs[ this.svgUid ][ gradientId ], obj );
// WORKAROUND FOR VERTICAL GRADIENT ISSUE; FOR NONE PIE CHARTS
if ( tmp.coords.y1 && _this.setup.chart.type != "pie" ) {
tmp.coords.y2 = tmp.coords.y1 * -1;
tmp.coords.y1 = 0;
}
obj.set( property, tmp );
}
};
// MULTILINE SUPPORT; TODO: BETTER POSITIONING
fabric.Text.fromElement = function( element, options ) {
if ( !element ) {
return null;
}
var parsedAttributes = fabric.parseAttributes( element, fabric.Text.ATTRIBUTE_NAMES );
options = fabric.util.object.extend( ( options ? fabric.util.object.clone( options ) : {} ), parsedAttributes );
options.top = options.top || 0;
options.left = options.left || 0;
if ( 'dx' in parsedAttributes ) {
options.left += parsedAttributes.dx;
}
if ( 'dy' in parsedAttributes ) {
options.top += parsedAttributes.dy;
}
if ( !( 'fontSize' in options ) ) {
options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
}
if ( !options.originX ) {
options.originX = 'left';
}
var textContent = '';
var textBuffer = [];
// The XML is not properly parsed in IE9 so a workaround to get
// textContent is through firstChild.data. Another workaround would be
// to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does)
if ( !( 'textContent' in element ) ) {
if ( 'firstChild' in element && element.firstChild !== null ) {
if ( 'data' in element.firstChild && element.firstChild.data !== null ) {
textBuffer.push( element.firstChild.data );
}
}
} else if ( element.childNodes ) {
for ( var i1 = 0; i1 < element.childNodes.length; i1++ ) {
textBuffer.push( element.childNodes[ i1 ].textContent );
}
} else {
textBuffer.push( element.textContent );
}
textContent = textBuffer.join( "\n" );
//textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' ');
var text = new fabric.Text( textContent, options ),
/*
Adjust positioning:
x/y attributes in SVG correspond to the bottom-left corner of text bounding box
top/left properties in Fabric correspond to center point of text bounding box
*/
offX = 0;
if ( text.originX === 'left' ) {
offX = text.getWidth() / 2;
}
if ( text.originX === 'right' ) {
offX = -text.getWidth() / 2;
}
if ( textBuffer.length > 1 ) {
text.set( {
left: text.getLeft() + offX,
top: text.getTop() + text.fontSize * ( textBuffer.length - 1 ) * ( 0.18 + text._fontSizeFraction ),
textAlign: options.originX,
lineHeight: textBuffer.length > 1 ? 0.965 : 1.16,
} );
} else {
text.set( {
left: text.getLeft() + offX,
top: text.getTop() - text.getHeight() / 2 + text.fontSize * ( 0.18 + text._fontSizeFraction ) /* 0.3 is the old lineHeight */
} );
}
return text;
};
},
/**
* Method to capture the current state of the chart
*/
capture: function( options, callback ) {
var i1;
var cfg = _this.deepMerge( _this.deepMerge( {}, _this.config.fabric ), options || {} );
var groups = [];
var offset = {
x: 0,
y: 0,
pX: 0,
pY: 0,
lX: 0,
lY: 0,
width: _this.setup.chart.divRealWidth,
height: _this.setup.chart.divRealHeight
};
var images = {
loaded: 0,
included: 0
}
var legends = {
items: [],
width: 0,
height: 0,
maxWidth: 0,
maxHeight: 0
}
// NAMESPACE CHECK
if ( !_this.handleNamespace( "fabric", {
scope: this,
cb: _this.capture,
args: arguments
} ) ) {
return false;
}
// MODIFY FABRIC UNTIL IT'S OFFICIALLY SUPPORTED
_this.modifyFabric();
// BEFORE CAPTURING
_this.handleCallback( cfg.beforeCapture, cfg );
// GATHER SVGS
var svgs = _this.setup.chart.containerDiv.getElementsByTagName( "svg" );
for ( i1 = 0; i1 < svgs.length; i1++ ) {
var group = {
svg: svgs[ i1 ],
parent: svgs[ i1 ].parentNode,
children: svgs[ i1 ].getElementsByTagName( "*" ),
offset: {
x: 0,
y: 0
},
patterns: {},
clippings: {},
has: {
legend: false,
panel: false,
scrollbar: false
}
}
// CHECK IT'S SURROUNDINGS
group.has.legend = _this.gatherClassName( group.parent, _this.setup.chart.classNamePrefix + "-legend-div", 1 );
group.has.panel = _this.gatherClassName( group.parent, _this.setup.chart.classNamePrefix + "-stock-panel-div" );
group.has.scrollbar = _this.gatherClassName( group.parent, _this.setup.chart.classNamePrefix + "-scrollbar-chart-div" );
// GATHER ELEMENTS
group = _this.gatherElements( group, cfg, images );
// APPEND GROUP
groups.push( group );
}
// GATHER EXTERNAL LEGEND
if ( _this.config.legend ) {
// STOCK
if ( _this.setup.chart.type == "stock" ) {
for ( i1 = 0; i1 < _this.setup.chart.panels.length; i1++ ) {
if ( _this.setup.chart.panels[ i1 ].stockLegend && _this.setup.chart.panels[ i1 ].stockLegend.divId ) {
legends.items.push( _this.setup.chart.panels[ i1 ].stockLegend );
}
}
// NORMAL
} else if ( _this.setup.chart.legend && _this.setup.chart.legend.divId ) {
legends.items.push( _this.setup.chart.legend );
}
// WALKTHROUGH
for ( i1 = 0; i1 < legends.items.length; i1++ ) {
var legend = legends.items[ i1 ];
var group = {
svg: legend.container.container,
parent: legend.container.container.parentNode,
children: legend.container.container.getElementsByTagName( "*" ),
offset: {
x: 0,
y: 0
},
legend: {
id: i1,
type: [ "top", "left" ].indexOf( _this.config.legend.position ) != -1 ? "unshift" : "push",
position: _this.config.legend.position,
width: _this.config.legend.width ? _this.config.legend.width : legend.container.div.offsetWidth,
height: _this.config.legend.height ? _this.config.legend.height : legend.container.div.offsetHeight
},
patterns: {},
clippings: {},
has: {
legend: false,
panel: false,
scrollbar: false
}
}
// GATHER DIMENSIONS
legends.width += group.legend.width;
legends.height += group.legend.height;
legends.maxWidth = group.legend.width > legends.maxWidth ? group.legend.width : legends.maxWidth;
legends.maxHeight = group.legend.height > legends.maxHeight ? group.legend.height : legends.maxHeight;
// GATHER ELEMENTS
group = _this.gatherElements( group, cfg, images );
// PRE/APPEND SVG
groups[ group.legend.type ]( group );
}
// ADAPT WIDTH IF NEEDED; EXPAND HEIGHT
if ( [ "top", "bottom" ].indexOf( _this.config.legend.position ) != -1 ) {
offset.width = legends.maxWidth > offset.width ? legends.maxWidth : offset.width;
offset.height += legends.height;
// EXPAND WIDTH; ADAPT HEIGHT IF NEEDED
} else if ( [ "left", "right" ].indexOf( _this.config.legend.position ) != -1 ) {
offset.width += legends.maxWidth;
offset.height = legends.height > offset.height ? legends.height : offset.height;
// SIMPLY EXPAND CANVAS
} else {
offset.height += legends.height;
offset.width += legends.maxWidth;
}
}
// CLEAR IF EXIST
_this.drawing.enabled = cfg.drawing.enabled = cfg.action == "draw";
_this.drawing.buffer.enabled = _this.drawing.enabled; // history reasons
_this.setup.wrapper = document.createElement( "div" );
_this.setup.wrapper.setAttribute( "class", _this.setup.chart.classNamePrefix + "-export-canvas" );
_this.setup.chart.containerDiv.appendChild( _this.setup.wrapper );
// STOCK CHART; SELECTOR OFFSET
if ( _this.setup.chart.type == "stock" ) {
var padding = {
top: 0,
right: 0,
bottom: 0,
left: 0
}
if ( _this.setup.chart.leftContainer ) {
offset.width -= _this.setup.chart.leftContainer.offsetWidth;
padding.left = _this.setup.chart.leftContainer.offsetWidth + ( _this.setup.chart.panelsSettings.panelSpacing * 2 );
}
if ( _this.setup.chart.rightContainer ) {
offset.width -= _this.setup.chart.rightContainer.offsetWidth;
padding.right = _this.setup.chart.rightContainer.offsetWidth + ( _this.setup.chart.panelsSettings.panelSpacing * 2 );
}
if ( _this.setup.chart.periodSelector && [ "top", "bottom" ].indexOf( _this.setup.chart.periodSelector.position ) != -1 ) {
offset.height -= _this.setup.chart.periodSelector.offsetHeight + _this.setup.chart.panelsSettings.panelSpacing;
padding[ _this.setup.chart.periodSelector.position ] += _this.setup.chart.periodSelector.offsetHeight + _this.setup.chart.panelsSettings.panelSpacing;
}
if ( _this.setup.chart.dataSetSelector && [ "top", "bottom" ].indexOf( _this.setup.chart.dataSetSelector.position ) != -1 ) {
offset.height -= _this.setup.chart.dataSetSelector.offsetHeight;
padding[ _this.setup.chart.dataSetSelector.position ] += _this.setup.chart.dataSetSelector.offsetHeight;
}
// APPLY OFFSET ON WRAPPER
_this.setup.wrapper.style.paddingTop = _this.numberToPx( padding.top );
_this.setup.wrapper.style.paddingRight = _this.numberToPx( padding.right );
_this.setup.wrapper.style.paddingBottom = _this.numberToPx( padding.bottom );
_this.setup.wrapper.style.paddingLeft = _this.numberToPx( padding.left );
}
// CREATE CANVAS
_this.setup.canvas = document.createElement( "canvas" );
_this.setup.wrapper.appendChild( _this.setup.canvas );
_this.setup.fabric = new fabric.Canvas( _this.setup.canvas, _this.deepMerge( {
width: offset.width,
height: offset.height,
isDrawingMode: true
}, cfg ) );
// REAPPLY FOR SOME REASON
_this.deepMerge( _this.setup.fabric, cfg );
_this.deepMerge( _this.setup.fabric.freeDrawingBrush, cfg.drawing );
// RELIABLE VARIABLES; UPDATE DRAWING
_this.deepMerge( _this.drawing, cfg.drawing );
_this.drawing.handler.change( cfg.drawing );
// OBSERVE MOUSE EVENTS
_this.setup.fabric.on( "mouse:down", function( e ) {
var p = _this.gatherPosition( e.e, 1 );
_this.drawing.buffer.pressedTS = Number( new Date() );
_this.isPressed( e.e );
// FLAG ISDRAWING
_this.drawing.buffer.isDrawing = false;
_this.drawing.buffer.isDrawingTimer = setTimeout( function() {
if ( !_this.drawing.buffer.isSelected ) {
_this.drawing.buffer.isDrawing = true;
}
}, 200 );
} );
_this.setup.fabric.on( "mouse:move", function( e ) {
var p = _this.gatherPosition( e.e, 2 );
_this.isPressed( e.e );
// IS PRESSED BUT UNSELECTED
if ( _this.drawing.buffer.isPressed && !_this.drawing.buffer.isSelected ) {
// FLAG ISDRAWING
_this.drawing.buffer.isDrawing = true;
// CREATE INITIAL LINE / ARROW; JUST ON LEFT CLICK
if ( !_this.drawing.buffer.line && _this.drawing.mode != "pencil" && ( p.xD > 5 || p.yD > 5 ) ) {
// FORCE FABRIC TO DISABLE DRAWING MODE WHILE PRESSED / MOVEING MOUSE INPUT
_this.setup.fabric.isDrawingMode = false;
_this.setup.fabric._isCurrentlyDrawing = false;
_this.setup.fabric.freeDrawingBrush.onMouseUp();
_this.setup.fabric.remove( _this.setup.fabric._objects.pop() );
// INITIAL POINT
_this.drawing.buffer.line = _this.drawing.handler.line( {
x1: p.x1,
y1: p.y1,
x2: p.x2,
y2: p.y2,
arrow: _this.drawing.mode == "line" ? false : _this.drawing.arrow,
action: "config"
} );
}
}
if ( _this.drawing.buffer.isSelected ) {
_this.setup.fabric.isDrawingMode = false;
}
// UPDATE LINE / ARROW
if ( _this.drawing.buffer.line ) {
var obj, top, left;
var l = _this.drawing.buffer.line;
l.x2 = p.x2;
l.y2 = p.y2;
// // RESET INTERNAL FLAGS
// _this.drawing.buffer.isDrawing = true;
// _this.drawing.buffer.isPressed = true;
// _this.drawing.buffer.hasLine = true;
for ( i1 = 0; i1 < l.group.length; i1++ ) {
obj = l.group[ i1 ];
if ( obj instanceof fabric.Line ) {
obj.set( {
x2: l.x2,
y2: l.y2
} );
} else if ( obj instanceof fabric.Triangle ) {
l.angle = ( _this.getAngle( l.x1, l.y1, l.x2, l.y2 ) + 90 );
if ( l.arrow == "start" ) {
top = l.y1 + ( l.width / 2 );
left = l.x1 + ( l.width / 2 );
} else if ( l.arrow == "middle" ) {
top = l.y2 + ( l.width / 2 ) - ( ( l.y2 - l.y1 ) / 2 );
left = l.x2 + ( l.width / 2 ) - ( ( l.x2 - l.x1 ) / 2 );
} else { // arrow: end
top = l.y2 + ( l.width / 2 );
left = l.x2 + ( l.width / 2 );
}
obj.set( {
top: top,
left: left,
angle: l.angle
} );
}
}
_this.setup.fabric.renderAll();
}
} );
_this.setup.fabric.on( "mouse:up", function( e ) {
// SELECT TARGET
if ( !_this.drawing.buffer.isDrawing ) {
var target = _this.setup.fabric.findTarget( e.e );
if ( target && target.selectable ) {
_this.setup.fabric.setActiveObject( target );
}
}
// UPDATE LINE / ARROW
if ( _this.drawing.buffer.line ) {
for ( i1 = 0; i1 < _this.drawing.buffer.line.group.length; i1++ ) {
_this.drawing.buffer.line.group[ i1 ].remove();
}
delete _this.drawing.buffer.line.action;
delete _this.drawing.buffer.line.group;
_this.drawing.handler.line( _this.drawing.buffer.line );
}
_this.drawing.buffer.line = false;
_this.drawing.buffer.hasLine = false;
_this.drawing.buffer.isPressed = false;
// RESET ISDRAWING FLAG
clearTimeout( _this.drawing.buffer.isDrawingTimer );
_this.drawing.buffer.isDrawing = false;
} );
// OBSERVE OBJECT SELECTION
_this.setup.fabric.on( "object:selected", function( e ) {
_this.drawing.buffer.isSelected = true;
_this.drawing.buffer.target = e.target;
_this.setup.fabric.isDrawingMode = false;
} );
_this.setup.fabric.on( "selection:cleared", function( e ) {
_this.drawing.buffer.target = false;
// FREEHAND WORKAROUND
if ( _this.drawing.buffer.isSe