// 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; }));