// 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 '
' + content + '
'; }, _generateText: function() { var html = '
' + this._calculateNumber(this._canAnimate ? 0 : this._number); if (this._text) { html += '' + this._text + ''; } html += '
'; return html; }, _calculateNumber: function(number) { var parts = (number + '').split('.'), html = '' + parts[0]; if (parts.length > 1) { html += '.' + parts[1].substring(0, 2) + ''; } return html + ''; }, _generateSvg: function() { return '' + this._generatePath(100, false, this._colors[0]) + this._generatePath(this._canAnimate ? 1 : this._percentage, true, this._colors[1]) + ''; }, _generatePath: function(percentage, open, color) { return ''; }, _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); }; })();