(function(factory){
if(typeof define==='function'&&define.amd){
define(['jquery'], factory);
}else if(typeof module==='object'&&module.exports){
var $=require('jquery');
factory($);
module.exports=$;
}else{
factory(jQuery);
}})(function($){
function CircleProgress(config){
this.init(config);
}
CircleProgress.prototype={
value: 0.0,
size: 100.0,
startAngle: -Math.PI,
thickness: 'auto',
fill: {
gradient: ['#3aeabb', '#fdd250']
},
emptyFill: 'rgba(0, 0, 0, .1)',
animation: {
duration: 1200,
easing: 'circleProgressEasing'
},
animationStartValue: 0.0,
reverse: false,
lineCap: 'butt',
insertMode: 'prepend',
constructor: CircleProgress,
el: null,
canvas: null,
ctx: null,
radius: 0.0,
arcFill: null,
lastFrameValue: 0.0,
init: function(config){
$.extend(this, config);
this.radius=this.size / 2;
this.initWidget();
this.initFill();
this.draw();
this.el.trigger('circle-inited');
},
initWidget: function(){
if(!this.canvas)
this.canvas=$('<canvas>')[this.insertMode=='prepend' ? 'prependTo':'appendTo'](this.el)[0];
var canvas=this.canvas;
canvas.width=this.size;
canvas.height=this.size;
this.ctx=canvas.getContext('2d');
if(window.devicePixelRatio > 1){
var scaleBy=window.devicePixelRatio;
canvas.style.width=canvas.style.height=this.size + 'px';
canvas.width=canvas.height=this.size * scaleBy;
this.ctx.scale(scaleBy, scaleBy);
}},
initFill: function(){
var self=this,
fill=this.fill,
ctx=this.ctx,
size=this.size;
if(!fill)
throw Error("The fill is not specified!");
if(typeof fill=='string')
fill={color: fill};
if(fill.color)
this.arcFill=fill.color;
if(fill.gradient){
var gr=fill.gradient;
if(gr.length==1){
this.arcFill=gr[0];
}else if(gr.length > 1){
var ga=fill.gradientAngle||0,
gd=fill.gradientDirection||[
size / 2 * (1 - Math.cos(ga)),
size / 2 * (1 + Math.sin(ga)),
size / 2 * (1 + Math.cos(ga)),
size / 2 * (1 - Math.sin(ga))
];
var lg=ctx.createLinearGradient.apply(ctx, gd);
for (var i=0; i < gr.length; i++){
var color=gr[i],
pos=i / (gr.length - 1);
if($.isArray(color)){
pos=color[1];
color=color[0];
}
lg.addColorStop(pos, color);
}
this.arcFill=lg;
}}
if(fill.image){
var img;
if(fill.image instanceof Image){
img=fill.image;
}else{
img=new Image();
img.src=fill.image;
}
if(img.complete)
setImageFill();
else
img.onload=setImageFill;
}
function setImageFill(){
var bg=$('<canvas>')[0];
bg.width=self.size;
bg.height=self.size;
bg.getContext('2d').drawImage(img, 0, 0, size, size);
self.arcFill=self.ctx.createPattern(bg, 'no-repeat');
self.drawFrame(self.lastFrameValue);
}},
draw: function(){
if(this.animation)
this.drawAnimated(this.value);
else
this.drawFrame(this.value);
},
drawFrame: function(v){
this.lastFrameValue=v;
this.ctx.clearRect(0, 0, this.size, this.size);
this.drawEmptyArc(v);
this.drawArc(v);
},
drawArc: function(v){
if(v===0)
return;
var ctx=this.ctx,
r=this.radius,
t=this.getThickness(),
a=this.startAngle;
ctx.save();
ctx.beginPath();
if(!this.reverse){
ctx.arc(r, r, r - t / 2, a, a + Math.PI * 2 * v);
}else{
ctx.arc(r, r, r - t / 2, a - Math.PI * 2 * v, a);
}
ctx.lineWidth=t;
ctx.lineCap=this.lineCap;
ctx.strokeStyle=this.arcFill;
ctx.stroke();
ctx.restore();
},
drawEmptyArc: function(v){
var ctx=this.ctx,
r=this.radius,
t=this.getThickness(),
a=this.startAngle;
if(v < 1){
ctx.save();
ctx.beginPath();
if(v <=0){
ctx.arc(r, r, r - t / 2, 0, Math.PI * 2);
}else{
if(!this.reverse){
ctx.arc(r, r, r - t / 2, a + Math.PI * 2 * v, a);
}else{
ctx.arc(r, r, r - t / 2, a, a - Math.PI * 2 * v);
}}
ctx.lineWidth=t;
ctx.strokeStyle=this.emptyFill;
ctx.stroke();
ctx.restore();
}},
drawAnimated: function(v){
var self=this,
el=this.el,
canvas=$(this.canvas);
canvas.stop(true, false);
el.trigger('circle-animation-start');
canvas
.css({animationProgress: 0})
.animate({animationProgress: 1}, $.extend({}, this.animation, {
step: function(animationProgress){
var stepValue=self.animationStartValue * (1 - animationProgress) + v * animationProgress;
self.drawFrame(stepValue);
el.trigger('circle-animation-progress', [animationProgress, stepValue]);
}}))
.promise()
.always(function(){
el.trigger('circle-animation-end');
});
},
getThickness: function(){
return $.isNumeric(this.thickness) ? this.thickness:this.size / 14;
},
getValue: function(){
return this.value;
},
setValue: function(newValue){
if(this.animation)
this.animationStartValue=this.lastFrameValue;
this.value=newValue;
this.draw();
}};
$.circleProgress={
defaults: CircleProgress.prototype
};
$.easing.circleProgressEasing=function(x){
if(x < 0.5){
x=2 * x;
return 0.5 * x * x * x;
}else{
x=2 - 2 * x;
return 1 - 0.5 * x * x * x;
}};
$.fn.circleProgress=function(configOrCommand, commandArgument){
var dataName='circle-progress',
firstInstance=this.data(dataName);
if(configOrCommand=='widget'){
if(!firstInstance)
throw Error('Calling "widget" method on not initialized instance is forbidden');
return firstInstance.canvas;
}
if(configOrCommand=='value'){
if(!firstInstance)
throw Error('Calling "value" method on not initialized instance is forbidden');
if(typeof commandArgument=='undefined'){
return firstInstance.getValue();
}else{
var newValue=arguments[1];
return this.each(function(){
$(this).data(dataName).setValue(newValue);
});
}}
return this.each(function(){
var el=$(this),
instance=el.data(dataName),
config=$.isPlainObject(configOrCommand) ? configOrCommand:{};
if(instance){
instance.init(config);
}else{
var initialConfig=$.extend({}, el.data());
if(typeof initialConfig.fill=='string')
initialConfig.fill=JSON.parse(initialConfig.fill);
if(typeof initialConfig.animation=='string')
initialConfig.animation=JSON.parse(initialConfig.animation);
config=$.extend(initialConfig, config);
config.el=el;
instance=new CircleProgress(config);
el.data(dataName, instance);
}});
};});