// 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
radius - the radius of the circles
width - the width of the ring (optional, has value 10, if not specified)
value - init value of the circle (optional, defaults to 0)
maxvalue - maximum value of the circle (optional, defaults to 100)
text - the text to display at the centre of the graph (optional, the current "htmlified" value will be shown if not specified)
if `null` or an empty string, no text will be displayed
can also be a function: the returned value will be the displayed text
ex1. function(currentvalue) {
return '$'+currentvalue;
}
ex2. function() {
return this.getpercent() + '%';
}
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 0 or `null` is passed, the animation will not run
wrpclass - class name to apply on the generated element wrapping the whole circle.
textclass: - class name to apply on the generated element wrapping the text content.
api:
updateradius(radius) - regenerates the circle with the given radius (see spec/responsive.html for an example hot to create a responsive circle)
updatewidth(width) - regenerates the circle with the given stroke width
updatecolors(colors) - change colors used to draw the circle
update(value, duration) - update value of circle. if value is set to true, force the update of displaying
getpercent() - returns the percentage value of the circle, based on its current value and its max value
getvalue() - returns the value of the circle
getmaxvalue() - returns the max value of the circle
getvaluefrompercent(percentage) - returns the corresponding value of the circle based on its max value and given percentage
htmlifynumber(number, integerpartclass, decimalpartclass) - returned html representation of given number with given classes names applied on tags
*/
(function(root, factory) {
if(typeof exports === 'object') {
module.exports = factory();
}
else if(typeof define === 'function' && define.amd) {
define([], factory);
}
else {
root.circles = factory();
}
}(this, function() {
"use strict";
var requestanimframe = window.requestanimationframe ||
window.webkitrequestanimationframe ||
window.mozrequestanimationframe ||
window.orequestanimationframe ||
window.msrequestanimationframe ||
function (callback) {
settimeout(callback, 1000 / 60);
},
circles = function(options) {
var elid = options.id;
this._el = document.getelementbyid(elid);
if (this._el === null) return;
this._radius = options.radius || 10;
this._duration = options.duration === undefined ? 500 : options.duration;
this._value = 0;
this._maxvalue = options.maxvalue || 100;
this._text = options.text === undefined ? function(value){return this.htmlifynumber(value);} : options.text;
this._strokewidth = options.width || 10;
this._colors = options.colors || ['#eee', '#f00'];
this._svg = null;
this._movingpath = null;
this._wrapcontainer = null;
this._textcontainer = null;
this._wrpclass = options.wrpclass || 'circles-wrp';
this._textclass = options.textclass || 'circles-text';
this._valclass = options.valuestrokeclass || 'circles-valuestroke';
this._maxvalclass = options.maxvaluestrokeclass || 'circles-maxvaluestroke';
this._stylewrapper = options.stylewrapper === false ? false : true;
this._styletext = options.styletext === false ? false : true;
var endanglerad = math.pi / 180 * 270;
this._start = -math.pi / 180 * 90;
this._startprecise = this._precise(this._start);
this._circ = endanglerad - this._start;
this._generate().update(options.value || 0);
if (elid == 'circles-2') {
this._text = options.text === undefined ? function(value){return this.htmlifynumber1(value);} : options.text;
}
if (elid == 'circles-3') {
this._text = options.text === undefined ? function(value){return this.htmlifynumber2(value);} : options.text;
}
if (elid == 'circles-4') {
this._text = options.text === undefined ? function(value){return this.htmlifynumber3(value);} : options.text;
}
if (elid == 'circles-5') {
this._text = options.text === undefined ? function(value){return this.htmlifynumber4(value);} : options.text;
}
};
circles.prototype = {
version: '0.0.6',
_generate: function() {
this._svgsize = this._radius * 2;
this._radiusadjusted = this._radius - (this._strokewidth / 2);
this._generatesvg()._generatetext()._generatewrapper();
this._el.innerhtml = '';
this._el.appendchild(this._wrapcontainer);
return this;
},
_setpercentage: function(percentage) {
this._movingpath.setattribute('d', this._calculatepath(percentage, true));
this._textcontainer.innerhtml = this._gettext(this.getvaluefrompercent(percentage));
},
_generatewrapper: function() {
this._wrapcontainer = document.createelement('div');
this._wrapcontainer.classname = this._wrpclass;
if (this._stylewrapper) {
this._wrapcontainer.style.position = 'relative';
this._wrapcontainer.style.display = 'inline-block';
}
this._wrapcontainer.appendchild(this._svg);
this._wrapcontainer.appendchild(this._textcontainer);
return this;
},
_generatetext: function() {
this._textcontainer = document.createelement('div');
this._textcontainer.classname = this._textclass;
if (this._styletext) {
var style = {
position: 'absolute',
top: 0,
left: 0,
textalign: 'center',
width: '100%',
fontsize: (this._radius * .7) + 'px',
height: this._svgsize + 'px',
lineheight: this._svgsize-3 + 'px'
};
for(var prop in style) {
this._textcontainer.style[prop] = style[prop];
}
}
this._textcontainer.innerhtml = this._gettext(0);
return this;
},
_gettext: function(value) {
if (!this._text) return '';
if (value === undefined) value = this._value;
value = parsefloat(value.tofixed(2));
return typeof this._text === 'function' ? this._text.call(this, value) : this._text;
},
_generatesvg: function() {
this._svg = document.createelementns('http://www.w3.org/2000/svg', 'svg');
this._svg.setattribute('xmlns', 'http://www.w3.org/2000/svg');
this._svg.setattribute('width', this._svgsize);
this._svg.setattribute('height', this._svgsize);
this._generatepath(100, false, this._colors[0], this._maxvalclass)._generatepath(1, true, this._colors[1], this._valclass);
this._movingpath = this._svg.getelementsbytagname('path')[1];
return this;
},
_generatepath: function(percentage, open, color, pathclass) {
var path = document.createelementns('http://www.w3.org/2000/svg', 'path');
path.setattribute('fill', 'transparent');
path.setattribute('stroke', color);
path.setattribute('stroke-width', this._strokewidth);
path.setattribute('d', this._calculatepath(percentage, open));
path.setattribute('class', pathclass);
this._svg.appendchild(path);
return this;
},
_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 * 100) / 100;
},
/*== public methods ==*/
htmlifynumber: function(number, integerpartclass, decimalpartclass) {
integerpartclass = integerpartclass || 'circles-integer';
decimalpartclass = decimalpartclass || 'circles-decimals';
var parts = (number + '').split('.'),
html = '' + math.round(parts[0]*28.4)+'';
// if (parts.length > 1) {
html += '
';
// }
return html;
},
htmlifynumber1: function(number, integerpartclass, decimalpartclass) {
integerpartclass = integerpartclass || 'circles-integer';
decimalpartclass = decimalpartclass || 'circles-decimals';
var parts = (number + '').split('.'),
html = '' + math.round(parts[0]*8.34)+'';
// if (parts.length > 1) {
html += '
';
// }
return html;
},
htmlifynumber2: function(number, integerpartclass, decimalpartclass) {
integerpartclass = integerpartclass || 'circles-integer';
decimalpartclass = decimalpartclass || 'circles-decimals';
var parts = (number + '').split('.'),
html = '' + math.round(parts[0]*0.25)+'';
// if (parts.length > 1) {
html += '
';
// }
return html;
},
htmlifynumber3: function(number, integerpartclass, decimalpartclass) {
integerpartclass = integerpartclass || 'circles-integer';
decimalpartclass = decimalpartclass || 'circles-decimals';
var parts = (number + '').split('.'),
html = '' + math.round(parts[0]*1.25)+'';
// if (parts.length > 1) {
html += '
';
// }
return html;
},
htmlifynumber4: function(number, integerpartclass, decimalpartclass) {
integerpartclass = integerpartclass || 'circles-integer';
decimalpartclass = decimalpartclass || 'circles-decimals';
var parts = (number + '').split('.'),
html = '' + math.round(parts[0]*8750)+'';
// if (parts.length > 1) {
html += '
';
// }
return html;
},
updateradius: function(radius) {
this._radius = radius;
return this._generate().update(true);
},
updatewidth: function(width) {
this._strokewidth = width;
return this._generate().update(true);
},
updatecolors: function(colors) {
this._colors = colors;
var paths = this._svg.getelementsbytagname('path');
paths[0].setattribute('stroke', colors[0]);
paths[1].setattribute('stroke', colors[1]);
return this;
},
getpercent: function() {
return (this._value * 100) / this._maxvalue;
},
getvaluefrompercent: function(percentage) {
return (this._maxvalue * percentage) / 100;
},
getvalue: function()
{
return this._value;
},
getmaxvalue: function()
{
return this._maxvalue;
},
update: function(value, duration) {
if (value === true) {//force update with current value
this._setpercentage(this.getpercent());
return this;
}
if (this._value == value || isnan(value)) return this;
if (duration === undefined) duration = this._duration;
var self = this,
oldpercentage = self.getpercent(),
delta = 1,
newpercentage, isgreater, steps, stepduration;
this._value = math.min(this._maxvalue, math.max(0, value));
if (!duration) {//no duration, we can't skip the animation
this._setpercentage(this.getpercent());
return this;
}
newpercentage = self.getpercent();
isgreater = newpercentage > oldpercentage;
delta += newpercentage % 1; //if new percentage is not an integer, we add the decimal part to the delta
steps = math.floor(math.abs(newpercentage - oldpercentage) / delta);
stepduration = duration / steps;
(function animate(lastframe) {
if (isgreater)
oldpercentage += delta;
else
oldpercentage -= delta;
if ((isgreater && oldpercentage >= newpercentage) || (!isgreater && oldpercentage <= newpercentage))
{
requestanimframe(function(){ self._setpercentage(newpercentage); });
return;
}
requestanimframe(function() { self._setpercentage(oldpercentage); });
var now = date.now(),
deltatime = now - lastframe;
if (deltatime >= stepduration) {
animate(now);
} else {
settimeout(function() {
animate(date.now());
}, stepduration - deltatime);
}
})(date.now());
return this;
}
};
circles.create = function(options) {
return new circles(options);
};
return circles;
}));