angular
.module('angular-doughnut-chart', [])
.directive('doughnutChart', ['$animateCss', '$interval', function ($animateCss, $interval) {
'use strict';
var config = {
stroke: 14
},
service = {
index: 0,
// credits to http://modernizr.com/ for the feature test
isSupported: !!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg', "svg").createSVGRect),
getPercent: function (percent, length) {
return (100 - percent) * length / 100;
},
getLengthCircle: function (radius) {
return Math.floor(Math.PI * 2 * radius);
},
setCircleAttrs: function (radius, stroke) {
return {
r: radius,
cx: -(radius + stroke / 2),
cy: radius + stroke / 2
};
},
getClass: function () {
this.index += 1;
return 'doughnut-chart-' + this.index;
}
},
base = {
restrict: "E",
scope: {
percentage: "=",
stroke: "="
}
};
//message for browsers which don't support svg
if (!service.isSupported) {
return angular.extend(base, {
template: '
Not supported
'
});
}
return angular.extend(base, {
link: function (scope, element) {
scope.stroke = scope.stroke || config.stroke;
function setDashOffset(resize) {
$interval.cancel(scope.intervalText);
if (scope.percentage !== scope.curPercent) {
scope.oldPecentage = scope.oldPecentage || 0;
scope.curPercent = scope.oldPecentage;
var diff = scope.percentage - scope.oldPecentage;
if (resize) {
scope.curPercent = scope.percentage;
} else {
scope.intervalText = $interval(
function () {
scope.curPercent += diff > 0 ? 1 : -1;
if (scope.curPercent === scope.percentage) {
$interval.cancel(scope.intervalText);
}
},
1000 / Math.abs(diff)
);
}
}
scope.dashOffset = service.getPercent(scope.percentage, scope.length);
if (resize) {
scope.$apply();
} else {
element.find('circle').eq(1).addClass('doughnut-allow-animation');
}
}
function firstAnimate() {
scope.animate = true;
$animateCss(element.find('circle').eq(1), {
addClass: 'doughnut-allow-animation'
}).start().then(setDashOffset);
}
function callbackChangeDOffset(resize) {
scope.drawed = true;
//set cx, cy and r for circles
scope.circles = element.find('circle').attr(
service.setCircleAttrs(scope.radius, scope.stroke)
);
if (resize) {
setDashOffset(true);
}
//for animation on load
if (scope.percentage && !scope.animate) {
firstAnimate();
}
}
function drawDoughNut(resize) {
scope.radius = (document.querySelectorAll('.' + scope.class)[0].offsetWidth - scope.stroke) / 2;
scope.length = service.getLengthCircle(scope.radius);
scope.dashOffset = scope.length;
if (resize) {
callbackChangeDOffset(true);
} else {
$animateCss(element.find('circle').eq(1), {
to: {'stroke-dashoffset': scope.length}
}).start().then(callbackChangeDOffset);
}
}
scope.class = service.getClass();
$animateCss(element.children(), {
addClass: scope.class
}).start().then(drawDoughNut);
//watch for percentage
scope.$watch('percentage', function (newValue, oldValue) {
if (newValue !== oldValue) {
scope.percentage = newValue;
scope.oldPecentage = oldValue;
if (scope.animate) {
scope.circles.eq(1).addClass('doughnut-allow-animation');
setDashOffset();
} else if (scope.drawed) {
//for first animation when percent gets asynchronously
firstAnimate();
}
}
});
angular.element(window).on('resize', function () {
scope.circles.eq(1).removeClass('doughnut-allow-animation');
$interval.cancel(scope.intervalText);
drawDoughNut(true);
});
},
template: '' +
'
{{curPercent}}%
' +
'
' +
'
'
});
}]);