123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- /**
- * jquery-circle-progress - jQuery Plugin to draw animated circular progress bars:
- * {@link http://kottenator.github.io/jquery-circle-progress/}
- *
- * @author Rostyslav Bryzgunov <kottenator@gmail.com>
- * @version 1.2.2
- * @licence MIT
- * @preserve
- */
- // UMD factory - https://github.com/umdjs/umd/blob/d31bb6ee7098715e019f52bdfe27b3e4bfd2b97e/templates/jqueryPlugin.js
- // Uses AMD, CommonJS or browser globals to create a jQuery plugin.
- (function(factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD - register as an anonymous module
- define(['jquery'], factory);
- } else if (typeof module === 'object' && module.exports) {
- // Node/CommonJS
- var $ = require('jquery');
- factory($);
- module.exports = $;
- } else {
- // Browser globals
- factory(jQuery);
- }
- })(function($) {
- /**
- * Inner implementation of the circle progress bar.
- * The class is not exposed _yet_ but you can create an instance through jQuery method call.
- *
- * @param {object} config - You can customize any class member (property or method).
- * @class
- * @alias CircleProgress
- */
- 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}
- * @default 0.0
- */
- value: 0.0,
- /**
- * Size of the canvas in pixels.
- * It's a square so we need only one dimension.
- * @type {number}
- * @default 100.0
- */
- size: 100.0,
- /**
- * Initial angle for `0.0` value in radians.
- * @type {number}
- * @default -Math.PI
- */
- startAngle: -Math.PI,
- /**
- * Width of the arc in pixels.
- * If it's `'auto'` - the value is calculated as `[this.size]{@link CircleProgress#size} / 14`.
- * @type {number|string}
- * @default 'auto'
- */
- thickness: 'auto',
- /**
- * Fill of the arc. You may set it to:
- *
- * - solid color:
- * - `'#3aeabb'`
- * - `{ 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] }`
- * - `{ gradient: [["red", .2], ["green", .3], ["blue", .8]] }`
- * - 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
- *
- * @default {gradient: ['#3aeabb', '#fdd250']}
- */
- fill: {
- gradient: ['#3aeabb', '#fdd250']
- },
- /**
- * Color of the "empty" arc. Only a color fill supported by now.
- * @type {string}
- * @default 'rgba(0, 0, 0, .1)'
- */
- emptyFill: 'rgba(0, 0, 0, .1)',
- /**
- * jQuery Animation config.
- * You can pass `false` to disable the animation.
- * @see http://api.jquery.com/animate/
- * @type {object|boolean}
- * @default {duration: 1200, easing: 'circleProgressEasing'}
- */
- 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_ - set `animationStartValue: 1.0`.
- * Also you may specify any other value from `0.0` to `1.0`.
- * @type {number}
- * @default 0.0
- */
- animationStartValue: 0.0,
- /**
- * Reverse animation and arc draw.
- * By default, the arc is filled from `0.0` to `value`, _clockwise_.
- * With `reverse: true` the arc is filled from `1.0` to `value`, _counter-clockwise_.
- * @type {boolean}
- * @default false
- */
- reverse: false,
- /**
- * Arc line cap: `'butt'`, `'round'` or `'square'` -
- * [read more]{@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap}.
- * @type {string}
- * @default 'butt'
- */
- lineCap: 'butt',
- /**
- * Canvas insertion mode: append or prepend it into the parent element?
- * @type {string}
- * @default 'prepend'
- */
- insertMode: 'prepend',
- //------------------------------ protected properties and methods ------------------------------
- /**
- * Link to {@link CircleProgress} constructor.
- * @protected
- */
- constructor: CircleProgress,
- /**
- * Container element. Should be passed into constructor config.
- * @protected
- * @type {jQuery}
- */
- el: null,
- /**
- * Canvas element. Automatically generated and prepended to [this.el]{@link CircleProgress#el}.
- * @protected
- * @type {HTMLCanvasElement}
- */
- canvas: null,
- /**
- * 2D-context of [this.canvas]{@link CircleProgress#canvas}.
- * @protected
- * @type {CanvasRenderingContext2D}
- */
- ctx: null,
- /**
- * Radius of the outer circle. Automatically calculated as `[this.size]{@link CircleProgress#size} / 2`.
- * @protected
- * @type {number}
- */
- radius: 0.0,
- /**
- * Fill of the main arc. Automatically calculated, depending on [this.fill]{@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.
- *
- * Throws a jQuery event:
- *
- * - `circle-inited(jqEvent)`
- *
- * @param {object} config - You can customize any class member (property or method).
- */
- init: function(config) {
- $.extend(this, config);
- this.radius = this.size / 2;
- this.initWidget();
- this.initFill();
- this.draw();
- this.el.trigger('circle-inited');
- },
- /**
- * Initialize `<canvas>`.
- * @protected
- */
- initWidget: function() {
- if (!this.canvas)
- this.canvas = $('<canvas>')[this.insertMode == 'prepend' ? 'prependTo' : 'appendTo'](this.el)[0];
- var canvas = this.canvas;
- canvas.width = this.size;
- canvas.height = this.size;
- this.ctx = canvas.getContext('2d');
- if (window.devicePixelRatio > 1) {
- var scaleBy = window.devicePixelRatio;
- canvas.style.width = canvas.style.height = this.size + 'px';
- canvas.width = canvas.height = this.size * scaleBy;
- this.ctx.scale(scaleBy, scaleBy);
- }
- },
- /**
- * This method sets [this.arcFill]{@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 (typeof fill == 'string')
- fill = {color: fill};
- 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 = $('<canvas>')[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 the circle.
- * @protected
- */
- draw: function() {
- if (this.animation)
- this.drawAnimated(this.value);
- else
- this.drawFrame(this.value);
- },
- /**
- * Draw a single animation frame.
- * @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);
- },
- /**
- * Draw the arc (part of the circle).
- * @protected
- * @param {number} v - Frame value.
- */
- drawArc: function(v) {
- if (v === 0)
- return;
- 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();
- },
- /**
- * Draw the _empty (background)_ arc (part of the circle).
- * @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();
- }
- },
- /**
- * Animate the progress bar.
- *
- * Throws 3 jQuery 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)`
- *
- * @protected
- * @param {number} v - Final value.
- */
- drawAnimated: function(v) {
- var self = this,
- el = this.el,
- canvas = $(this.canvas);
- // stop previous animation before new "start" event is triggered
- canvas.stop(true, false);
- el.trigger('circle-animation-start');
- canvas
- .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]);
- }
- }))
- .promise()
- .always(function() {
- // trigger on both successful & failure animation end
- el.trigger('circle-animation-end');
- });
- },
- /**
- * Get the circle thickness.
- * @see CircleProgress#thickness
- * @protected
- * @returns {number}
- */
- getThickness: function() {
- return $.isNumeric(this.thickness) ? this.thickness : this.size / 14;
- },
- /**
- * Get current value.
- * @protected
- * @return {number}
- */
- getValue: function() {
- return this.value;
- },
- /**
- * Set current value (with smooth animation transition).
- * @protected
- * @param {number} newValue
- */
- setValue: function(newValue) {
- if (this.animation)
- this.animationStartValue = this.lastFrameValue;
- this.value = newValue;
- this.draw();
- }
- };
- //----------------------------------- Initiating jQuery plugin -----------------------------------
- $.circleProgress = {
- // Default options (you may override them)
- defaults: CircleProgress.prototype
- };
- // ease-in-out-cubic
- $.easing.circleProgressEasing = function(x) {
- if (x < 0.5) {
- x = 2 * x;
- return 0.5 * x * x * x;
- } else {
- x = 2 - 2 * x;
- return 1 - 0.5 * x * x * x;
- }
- };
- /**
- * Creates an instance of {@link CircleProgress}.
- * Produces [init event]{@link CircleProgress#init} and [animation events]{@link CircleProgress#drawAnimated}.
- *
- * @param {object} [configOrCommand] - Config object or command name.
- *
- * Config example (you can specify any {@link CircleProgress} property):
- *
- * ```js
- * { value: 0.75, size: 50, animation: false }
- * ```
- *
- * Commands:
- *
- * ```js
- * el.circleProgress('widget'); // get the <canvas>
- * el.circleProgress('value'); // get the value
- * el.circleProgress('value', newValue); // update the value
- * el.circleProgress('redraw'); // redraw the circle
- * el.circleProgress(); // the same as 'redraw'
- * ```
- *
- * @param {string} [commandArgument] - Some commands (like `'value'`) may require an argument.
- * @see CircleProgress
- * @alias "$(...).circleProgress"
- */
- $.fn.circleProgress = function(configOrCommand, commandArgument) {
- var dataName = 'circle-progress',
- firstInstance = this.data(dataName);
- if (configOrCommand == 'widget') {
- if (!firstInstance)
- throw Error('Calling "widget" method on not initialized instance is forbidden');
- return firstInstance.canvas;
- }
- if (configOrCommand == 'value') {
- if (!firstInstance)
- throw Error('Calling "value" method on not initialized instance is forbidden');
- if (typeof commandArgument == 'undefined') {
- return firstInstance.getValue();
- } else {
- var newValue = arguments[1];
- return this.each(function() {
- $(this).data(dataName).setValue(newValue);
- });
- }
- }
- return this.each(function() {
- var el = $(this),
- instance = el.data(dataName),
- config = $.isPlainObject(configOrCommand) ? configOrCommand : {};
- if (instance) {
- instance.init(config);
- } else {
- var initialConfig = $.extend({}, el.data());
- if (typeof initialConfig.fill == 'string')
- initialConfig.fill = JSON.parse(initialConfig.fill);
- if (typeof initialConfig.animation == 'string')
- initialConfig.animation = JSON.parse(initialConfig.animation);
- config = $.extend(initialConfig, config);
- config.el = el;
- instance = new CircleProgress(config);
- el.data(dataName, instance);
- }
- });
- };
- });
|