/* jquery-circle-progress - jquery plugin to draw animated circular progress bars url: http://kottenator.github.io/jquery-circle-progress/ author: rostyslav bryzgunov version: 1.1.2 license: mit */ (function($) { function circleprogress(config) { this.init(config); } circleprogress.prototype = { //----------------------------------------------- public options ----------------------------------------------- /** * this is the only required option. it should be from 0.0 to 1.0 * @type {number} */ value: 0.0, /** * size of the circle / canvas in pixels * @type {number} */ size: 100.0, /** * initial angle for 0.0 value in radians * @type {number} */ startangle: -math.pi, /** * width of the arc. by default it's auto-calculated as 1/14 of size, but you may set it explicitly in pixels * @type {number|string} */ thickness: 'auto', /** * fill of the arc. you may set it to: * - solid color: * - { color: '#3aeabb' } * - { color: 'rgba(255, 255, 255, .3)' } * - linear gradient (left to right): * - { gradient: ['#3aeabb', '#fdd250'], gradientangle: math.pi / 4 } * - { gradient: ['red', 'green', 'blue'], gradientdirection: [x0, y0, x1, y1] } * - image: * - { image: 'http://i.imgur.com/pt0i89v.png' } * - { image: imageobject } * - { color: 'lime', image: 'http://i.imgur.com/pt0i89v.png' } - color displayed until the image is loaded */ fill: { gradient: ['#3aeabb', '#fdd250'] }, /** * color of the "empty" arc. only a color fill supported by now * @type {string} */ emptyfill: 'rgba(0, 0, 0, .1)', /** * animation config (see jquery animations: http://api.jquery.com/animate/) */ animation: { duration: 1200, easing: 'circleprogresseasing' }, /** * default animation starts at 0.0 and ends at specified `value`. let's call this direct animation. * if you want to make reversed animation then you should set `animationstartvalue` to 1.0. * also you may specify any other value from 0.0 to 1.0 * @type {number} */ animationstartvalue: 0.0, /** * reverse animation and arc draw * @type {boolean} */ reverse: false, /** * arc line cap ('butt' (default), 'round' and 'square') * read more: https://developer.mozilla.org/en-us/docs/web/api/canvasrenderingcontext2d.linecap * @type {string} */ linecap: 'butt', //-------------------------------------- protected properties and methods -------------------------------------- /** * @protected */ constructor: circleprogress, /** * container element. should be passed into constructor config * @protected * @type {jquery} */ el: null, /** * canvas element. automatically generated and prepended to the {@link circleprogress.el container} * @protected * @type {htmlcanvaselement} */ canvas: null, /** * 2d-context of the {@link circleprogress.canvas canvas} * @protected * @type {canvasrenderingcontext2d} */ ctx: null, /** * radius of the outer circle. automatically calculated as {@link circleprogress.size} / 2 * @protected * @type {number} */ radius: 0.0, /** * fill of the main arc. automatically calculated, depending on {@link circleprogress.fill} option * @protected * @type {string|canvasgradient|canvaspattern} */ arcfill: null, /** * last rendered frame value * @protected * @type {number} */ lastframevalue: 0.0, /** * init/re-init the widget * @param {object} config config */ init: function(config) { $.extend(this, config); this.radius = this.size / 2; this.initwidget(); this.initfill(); this.draw(); }, /** * @protected */ initwidget: function() { var canvas = this.canvas = this.canvas || $('').prependto(this.el)[0]; canvas.width = this.size; canvas.height = this.size; this.ctx = canvas.getcontext('2d'); }, /** * this method sets {@link circleprogress.arcfill} * it could do this async (on image load) * @protected */ initfill: function() { var self = this, fill = this.fill, ctx = this.ctx, size = this.size; if (!fill) throw error("the fill is not specified!"); if (fill.color) this.arcfill = fill.color; if (fill.gradient) { var gr = fill.gradient; if (gr.length == 1) { this.arcfill = gr[0]; } else if (gr.length > 1) { var ga = fill.gradientangle || 0, // gradient direction angle; 0 by default gd = fill.gradientdirection || [ size / 2 * (1 - math.cos(ga)), // x0 size / 2 * (1 + math.sin(ga)), // y0 size / 2 * (1 + math.cos(ga)), // x1 size / 2 * (1 - math.sin(ga)) // y1 ]; var lg = ctx.createlineargradient.apply(ctx, gd); for (var i = 0; i < gr.length; i++) { var color = gr[i], pos = i / (gr.length - 1); if ($.isarray(color)) { pos = color[1]; color = color[0]; } lg.addcolorstop(pos, color); } this.arcfill = lg; } } if (fill.image) { var img; if (fill.image instanceof image) { img = fill.image; } else { img = new image(); img.src = fill.image; } if (img.complete) setimagefill(); else img.onload = setimagefill; } function setimagefill() { var bg = $('')[0]; bg.width = self.size; bg.height = self.size; bg.getcontext('2d').drawimage(img, 0, 0, size, size); self.arcfill = self.ctx.createpattern(bg, 'no-repeat'); self.drawframe(self.lastframevalue); } }, draw: function() { if (this.animation) this.drawanimated(this.value); else this.drawframe(this.value); }, /** * @protected * @param {number} v frame value */ drawframe: function(v) { this.lastframevalue = v; this.ctx.clearrect(0, 0, this.size, this.size); this.drawemptyarc(v); this.drawarc(v); }, /** * @protected * @param {number} v frame value */ drawarc: function(v) { var ctx = this.ctx, r = this.radius, t = this.getthickness(), a = this.startangle; ctx.save(); ctx.beginpath(); if (!this.reverse) { ctx.arc(r, r, r - t / 2, a, a + math.pi * 2 * v); } else { ctx.arc(r, r, r - t / 2, a - math.pi * 2 * v, a); } ctx.linewidth = t; ctx.linecap = this.linecap; ctx.strokestyle = this.arcfill; ctx.stroke(); ctx.restore(); }, /** * @protected * @param {number} v frame value */ drawemptyarc: function(v) { var ctx = this.ctx, r = this.radius, t = this.getthickness(), a = this.startangle; if (v < 1) { ctx.save(); ctx.beginpath(); if (v <= 0) { ctx.arc(r, r, r - t / 2, 0, math.pi * 2); } else { if (!this.reverse) { ctx.arc(r, r, r - t / 2, a + math.pi * 2 * v, a); } else { ctx.arc(r, r, r - t / 2, a, a - math.pi * 2 * v); } } ctx.linewidth = t; ctx.strokestyle = this.emptyfill; ctx.stroke(); ctx.restore(); } }, /** * @protected * @param {number} v value */ drawanimated: function(v) { var self = this, el = this.el; el.trigger('circle-animation-start'); $(this.canvas) .stop(true, true) .css({ animationprogress: 0 }) .animate({ animationprogress: 1 }, $.extend({}, this.animation, { step: function(animationprogress) { var stepvalue = self.animationstartvalue * (1 - animationprogress) + v * animationprogress; self.drawframe(stepvalue); el.trigger('circle-animation-progress', [animationprogress, stepvalue]); }, complete: function() { el.trigger('circle-animation-end'); } })); }, /** * @protected * @returns {number} */ getthickness: function() { return $.isnumeric(this.thickness) ? this.thickness : this.size / 14; } }; //-------------------------------------------- initiating jquery plugin -------------------------------------------- $.circleprogress = { // default options (you may override them) defaults: circleprogress.prototype }; // ease-in-out-cubic $.easing.circleprogresseasing = function(x, t, b, c, d) { if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; return c / 2 * ((t -= 2) * t * t + 2) + b; }; /** * draw animated circular progress bar. * * appends to the element or updates already appended one. * * if animated, throws 3 events: * * - circle-animation-start(jqevent) * - circle-animation-progress(jqevent, animationprogress, stepvalue) - multiple event; * animationprogress: from 0.0 to 1.0; * stepvalue: from 0.0 to value * - circle-animation-end(jqevent) * * @param config example: { value: 0.75, size: 50, animation: false }; * you may set any of public options; * `animation` may be set to false; * you may also use .circleprogress('widget') to get the canvas */ $.fn.circleprogress = function(config) { var dataname = 'circle-progress'; if (config == 'widget') { var data = this.data(dataname); return data && data.canvas; } return this.each(function() { var el = $(this), instance = el.data(dataname), cfg = $.isplainobject(config) ? config : {}; if (instance) { instance.init(cfg); } else { cfg.el = el; instance = new circleprogress(cfg); el.data(dataname, instance); } }); }; })(jquery);