You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
6.9 KiB
195 lines
6.9 KiB
// circles
|
|
// copyright Artan Sinani
|
|
// https://github.com/lugolabs/circles
|
|
|
|
/*
|
|
Lightwheight JavaScript library that generates circular graphs in SVG.
|
|
|
|
Call Circles.create(options) with the following options:
|
|
|
|
id - the DOM element that will hold the graph
|
|
percentage - the percentage dictating the smaller circle
|
|
radius - the radius of the circles
|
|
width - the width of the ring (optional, has value 10, if not specified)
|
|
number - the number to display at the centre of the graph (optional, the percentage will show if not specified)
|
|
text - the text to display after the number (optional, nothing will show if not specified)
|
|
colors - an array of colors, with the first item coloring the full circle
|
|
(optional, it will be `['#EEE', '#F00']` if not specified)
|
|
duration - value in ms of animation duration; (optional, defaults to 500);
|
|
if `null` is passed, the animation will not run
|
|
|
|
*/
|
|
|
|
(function() {
|
|
var Circles = window.Circles = function(options) {
|
|
var elId = options.id;
|
|
this._el = document.getElementById(elId);
|
|
|
|
if (this._el === null) return;
|
|
|
|
var endAngleRad = Math.PI / 180 * 270;
|
|
|
|
this._radius = options.radius;
|
|
this._percentage = options.percentage;
|
|
this._text = options.text; // #3
|
|
this._number = options.number || this._percentage;
|
|
this._strokeWidth = options.width || 10;
|
|
this._colors = options.colors || ['#EEE', '#F00'];
|
|
this._interval = 16;
|
|
this._textWrpClass = 'circles-text-wrp';
|
|
this._textClass = 'circles-text';
|
|
this._numberClass = 'circles-number';
|
|
|
|
this._confirmAnimation(options.duration);
|
|
|
|
this._svgSize = this._radius * 2;
|
|
this._radiusAdjusted = this._radius - (this._strokeWidth / 2);
|
|
this._start = -Math.PI / 180 * 90;
|
|
this._startPrecise = this._precise(this._start);
|
|
this._circ = endAngleRad - this._start;
|
|
this._el.innerHTML = this._wrap(this._generateSvg() + this._generateText());
|
|
|
|
if (this._canAnimate) this._animate();
|
|
};
|
|
|
|
Circles.prototype = {
|
|
VERSION: '0.0.3',
|
|
|
|
_confirmAnimation: function(duration) {
|
|
if (duration === null) {
|
|
this._canAnimate = false;
|
|
return;
|
|
}
|
|
|
|
duration = duration || 500;
|
|
|
|
var step = duration / this._interval,
|
|
pathFactor = this._percentage / step,
|
|
numberFactor = this._number / step;
|
|
|
|
if (this._percentage <= (1 + pathFactor)) {
|
|
this._canAnimate = false;
|
|
} else {
|
|
this._canAnimate = true;
|
|
this._pathFactor = pathFactor;
|
|
this._numberFactor = numberFactor;
|
|
}
|
|
},
|
|
|
|
_animate: function() {
|
|
var i = 1,
|
|
self = this,
|
|
path = this._el.getElementsByTagName('path')[1],
|
|
numberEl = this._getNumberElement(),
|
|
|
|
isInt = this._number % 1 === 0,
|
|
|
|
requestAnimFrame = window.requestAnimationFrame ||
|
|
window.webkitRequestAnimationFrame ||
|
|
window.mozRequestAnimationFrame ||
|
|
window.oRequestAnimationFrame ||
|
|
window.msRequestAnimationFrame ||
|
|
function (callback) {
|
|
setTimeout(callback, self._interval);
|
|
},
|
|
|
|
animate = function() {
|
|
var percentage = self._pathFactor * i,
|
|
nextPercentage = self._pathFactor * (i + 1),
|
|
number = self._numberFactor * i,
|
|
canContinue = true;
|
|
if (isInt) {
|
|
number = Math.round(number);
|
|
}
|
|
if (nextPercentage > self._percentage) {
|
|
percentage = self._percentage;
|
|
number = self._number;
|
|
canContinue = false;
|
|
}
|
|
if (percentage > self._percentage) return;
|
|
path.setAttribute('d', self._calculatePath(percentage, true));
|
|
numberEl.innerHTML = self._calculateNumber(number);
|
|
i++;
|
|
if (canContinue) requestAnimFrame(animate);
|
|
};
|
|
|
|
requestAnimFrame(animate);
|
|
},
|
|
|
|
_getNumberElement: function() {
|
|
var divs = this._el.getElementsByTagName('span');
|
|
for (var i = 0, l = divs.length; i < l; i++) {
|
|
if (divs[i].className === this._numberClass) return divs[i];
|
|
}
|
|
},
|
|
|
|
_wrap: function(content) {
|
|
return '<div class="circles-wrp" style="position:relative; display:inline-block;">' + content + '</div>';
|
|
},
|
|
|
|
_generateText: function() {
|
|
var html = '<div class="' + this._textWrpClass + '" style="position:absolute; top:0; left:0; text-align:center; width:100%;' +
|
|
' font-size:' + this._radius * .5 + 'px; height:' + this._svgSize + 'px; line-height:' + this._svgSize + 'px;">' +
|
|
this._calculateNumber(this._canAnimate ? 0 : this._number);
|
|
if (this._text) {
|
|
html += '<span class="' + this._textClass + '">' + this._text + '</span>';
|
|
}
|
|
html += '</div>';
|
|
return html;
|
|
},
|
|
|
|
_calculateNumber: function(number) {
|
|
var parts = (number + '').split('.'),
|
|
html = '<span class="' + this._numberClass + '">' + parts[0];
|
|
if (parts.length > 1) {
|
|
html += '.<span style="font-size:.4em">' + parts[1].substring(0, 2) + '</span>';
|
|
}
|
|
return html + '</span>';
|
|
},
|
|
|
|
_generateSvg: function() {
|
|
return '<svg width="' + this._svgSize + '" height="' + this._svgSize + '">' +
|
|
this._generatePath(100, false, this._colors[0]) +
|
|
this._generatePath(this._canAnimate ? 1 : this._percentage, true, this._colors[1]) +
|
|
'</svg>';
|
|
},
|
|
|
|
_generatePath: function(percentage, open, color) {
|
|
return '<path fill="transparent" stroke="' + color + '" stroke-width="' + this._strokeWidth + '" d="' + this._calculatePath(percentage, open) + '"/>';
|
|
},
|
|
|
|
_calculatePath: function(percentage, open) {
|
|
var end = this._start + ((percentage / 100) * this._circ),
|
|
endPrecise = this._precise(end);
|
|
return this._arc(endPrecise, open);
|
|
},
|
|
|
|
_arc: function(end, open) {
|
|
var endAdjusted = end - 0.001,
|
|
longArc = end - this._startPrecise < Math.PI ? 0 : 1;
|
|
|
|
return [
|
|
'M',
|
|
this._radius + this._radiusAdjusted * Math.cos(this._startPrecise),
|
|
this._radius + this._radiusAdjusted * Math.sin(this._startPrecise),
|
|
'A', // arcTo
|
|
this._radiusAdjusted, // x radius
|
|
this._radiusAdjusted, // y radius
|
|
0, // slanting
|
|
longArc, // long or short arc
|
|
1, // clockwise
|
|
this._radius + this._radiusAdjusted * Math.cos(endAdjusted),
|
|
this._radius + this._radiusAdjusted * Math.sin(endAdjusted),
|
|
open ? '' : 'Z' // close
|
|
].join(' ');
|
|
},
|
|
|
|
_precise: function(value) {
|
|
return Math.round(value * 1000) / 1000;
|
|
}
|
|
};
|
|
|
|
Circles.create = function(options) {
|
|
return new Circles(options);
|
|
};
|
|
})(); |