1/** 2 * jquery-circle-progress - jQuery Plugin to draw animated circular progress bars: 3 * {@link http://kottenator.github.io/jquery-circle-progress/} 4 * 5 * @author Rostyslav Bryzgunov <[email protected]> 6 * @version 1.2.2 7 * @licence MIT 8 * @preserve 9 */ 10// UMD factory - https://github.com/umdjs/umd/blob/d31bb6ee7098715e019f52bdfe27b3e4bfd2b97e/templates/jqueryPlugin.js 11// Uses AMD, CommonJS or browser globals to create a jQuery plugin. 12(function(factory) { 13 if (typeof define === 'function' && define.amd) { 14 // AMD - register as an anonymous module 15 define(['jquery'], factory); 16 } else if (typeof module === 'object' && module.exports) { 17 // Node/CommonJS 18 var $ = require('jquery'); 19 factory($); 20 module.exports = $; 21 } else { 22 // Browser globals 23 factory(jQuery); 24 } 25})(function($) { 26 /** 27 * Inner implementation of the circle progress bar. 28 * The class is not exposed _yet_ but you can create an instance through jQuery method call. 29 * 30 * @param {object} config - You can customize any class member (property or method). 31 * @class 32 * @alias CircleProgress 33 */ 34 function CircleProgress(config) { 35 this.init(config); 36 } 37 38 CircleProgress.prototype = { 39 //--------------------------------------- public options --------------------------------------- 40 /** 41 * This is the only required option. It should be from `0.0` to `1.0`. 42 * @type {number} 43 * @default 0.0 44 */ 45 value: 0.0, 46 47 /** 48 * Size of the canvas in pixels. 49 * It's a square so we need only one dimension. 50 * @type {number} 51 * @default 100.0 52 */ 53 size: 100.0, 54 55 /** 56 * Initial angle for `0.0` value in radians. 57 * @type {number} 58 * @default -Math.PI 59 */ 60 startAngle: -Math.PI, 61 62 /** 63 * Width of the arc in pixels. 64 * If it's `'auto'` - the value is calculated as `[this.size]{@link CircleProgress#size} / 14`. 65 * @type {number|string} 66 * @default 'auto' 67 */ 68 thickness: 'auto', 69 70 /** 71 * Fill of the arc. You may set it to: 72 * 73 * - solid color: 74 * - `'#3aeabb'` 75 * - `{ color: '#3aeabb' }` 76 * - `{ color: 'rgba(255, 255, 255, .3)' }` 77 * - linear gradient _(left to right)_: 78 * - `{ gradient: ['#3aeabb', '#fdd250'], gradientAngle: Math.PI / 4 }` 79 * - `{ gradient: ['red', 'green', 'blue'], gradientDirection: [x0, y0, x1, y1] }` 80 * - `{ gradient: [["red", .2], ["green", .3], ["blue", .8]] }` 81 * - image: 82 * - `{ image: 'http://i.imgur.com/pT0i89v.png' }` 83 * - `{ image: imageObject }` 84 * - `{ color: 'lime', image: 'http://i.imgur.com/pT0i89v.png' }` - 85 * color displayed until the image is loaded 86 * 87 * @default {gradient: ['#3aeabb', '#fdd250']} 88 */ 89 fill: { 90 gradient: ['#3aeabb', '#fdd250'] 91 }, 92 93 /** 94 * Color of the "empty" arc. Only a color fill supported by now. 95 * @type {string} 96 * @default 'rgba(0, 0, 0, .1)'
97 */ 98 emptyFill: 'rgba(0, 0, 0, .1)', 99 100 /** 101 * jQuery Animation config. 102 * You can pass `false` to disable the animation. 103 * @see http://api.jquery.com/animate/ 104 * @type {object|boolean} 105 * @default {duration: 1200, easing: 'circleProgressEasing'} 106 */ 107 animation: { 108 duration: 1200, 109 easing: 'circleProgressEasing' 110 }, 111 112 /** 113 * Default animation starts at `0.0` and ends at specified `value`. Let's call this _direct animation_. 114 * If you want to make _reversed animation_ - set `animationStartValue: 1.0`. 115 * Also you may specify any other value from `0.0` to `1.0`. 116 * @type {number} 117 * @default 0.0 118 */ 119 animationStartValue: 0.0, 120 121 /** 122 * Reverse animation and arc draw. 123 * By default, the arc is filled from `0.0` to `value`, _clockwise_. 124 * With `reverse: true` the arc is filled from `1.0` to `value`, _counter-clockwise_. 125 * @type {boolean} 126 * @default false 127 */ 128 reverse: false, 129 130 /** 131 * Arc line cap: `'butt'`, `'round'` or `'square'` - 132 * [read more]{@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap}. 133 * @type {string} 134 * @default 'butt' 135 */ 136 lineCap: 'butt', 137 138 /** 139 * Canvas insertion mode: append or prepend it into the parent element? 140 * @type {string} 141 * @default 'prepend' 142 */ 143 insertMode: 'prepend', 144 145 //------------------------------ protected properties and methods ------------------------------ 146 /** 147 * Link to {@link CircleProgress} constructor. 148 * @protected 149 */ 150 constructor: CircleProgress, 151 152 /** 153 * Container element. Should be passed into constructor config. 154 * @protected 155 * @type {jQuery} 156 */ 157 el: null, 158 159 /** 160 * Canvas element. Automatically generated and prepended to [this.el]{@link CircleProgress#el}. 161 * @protected 162 * @type {HTMLCanvasElement} 163 */ 164 canvas: null, 165 166 /** 167 * 2D-context of [this.canvas]{@link CircleProgress#canvas}. 168 * @protected 169 * @type {CanvasRenderingContext2D} 170 */ 171 ctx: null, 172 173 /** 174 * Radius of the outer circle. Automatically calculated as `[this.size]{@link CircleProgress#size} / 2`. 175 * @protected 176 * @type {number} 177 */ 178 radius: 0.0, 179 180 /** 181 * Fill of the main arc. Automatically calculated, depending on [this.fill]{@link CircleProgress#fill} option. 182 * @protected 183 * @type {string|CanvasGradient|CanvasPattern} 184 */ 185 arcFill: null, 186 187 /** 188 * Last rendered frame value. 189 * @protected 190 * @type {number} 191 */ 192 lastFrameValue: 0.0, 193 194 /** 195 * Init/re-init the widget. 196 * 197 * Throws a jQuery event: 198 * 199 * - `circle-inited(jqEvent)` 200 * 201 * @param {object} config - You can customize any class member (property or method). 202 */ 203 init: function(config) { 204 $.extend(this, config); 205 this.radius = this.size / 2; 206 this.initWidget(); 207 this.initFill(); 208 this.draw(); 209 this.el.trigger('circle-inited'); 210 }, 211 212 /** 213 * Initialize `<canvas>`. 214 * @protected 215 */ 216 initWidget: function() { 217 if (!this.canvas) 218 this.canvas = $('<canvas>')[this.insertMode == 'prepend' ? 'prependTo' : 'appendTo'](this.el)[0]; 219 220 var canvas = this.canvas; 221 canvas.width = this.size; 222 canvas.height = this.size; 223 this.ctx = canvas.getContext('2d'); 224 225 if (window.devicePixelRatio > 1) { 226 var scaleBy = window.devicePixelRatio; 227 canvas.style.width = canvas.style.height = this.size + 'px'; 228 canvas.width = canvas.height = this.size * scaleBy; 229 this.ctx.scale(scaleBy, scaleBy); 230 } 231 }, 232 233 /** 234 * This method sets [this.arcFill]{@link CircleProgress#arcFill}. 235 * It could do this async (on image load). 236 * @protected 237 */ 238 initFill: function() { 239 var self = this, 240 fill = this.fill, 241 ctx = this.ctx, 242 size = this.size; 243 244 if (!fill) 245 throw Error("The fill is not specified!"); 246 247 if (typeof fill == 'string') 248 fill = {color: fill}; 249 250 if (fill.color) 251 this.arcFill = fill.color; 252 253 if (fill.gradient) { 254 var gr = fill.gradient; 255 256 if (gr.length == 1) { 257 this.arcFill = gr[0]; 258 } else if (gr.length > 1) { 259 var ga = fill.gradientAngle || 0, // gradient direction angle; 0 by default 260 gd = fill.gradientDirection || [ 261 size / 2 * (1 - Math.cos(ga)), // x0 262 size / 2 * (1 + Math.sin(ga)), // y0
263 size / 2 * (1 + Math.cos(ga)), // x1 264 size / 2 * (1 - Math.sin(ga)) // y1 265 ]; 266 267 var lg = ctx.createLinearGradient.apply(ctx, gd); 268 269 for (var i = 0; i < gr.length; i++) { 270 var color = gr[i], 271 pos = i / (gr.length - 1); 272 273 if ($.isArray(color)) { 274 pos = color[1]; 275 color = color[0]; 276 } 277 278 lg.addColorStop(pos, color); 279 } 280 281 this.arcFill = lg; 282 } 283 } 284 285 if (fill.image) { 286 var img; 287 288 if (fill.image instanceof Image) { 289 img = fill.image; 290 } else { 291 img = new Image(); 292 img.src = fill.image; 293 } 294 295 if (img.complete) 296 setImageFill(); 297 else 298 img.onload = setImageFill; 299 } 300 301 function setImageFill() { 302 var bg = $('<canvas>')[0]; 303 bg.width = self.size; 304 bg.height = self.size; 305 bg.getContext('2d').drawImage(img, 0, 0, size, size); 306 self.arcFill = self.ctx.createPattern(bg, 'no-repeat'); 307 self.drawFrame(self.lastFrameValue); 308 } 309 }, 310 311 /** 312 * Draw the circle. 313 * @protected 314 */ 315 draw: function() { 316 if (this.animation) 317 this.drawAnimated(this.value); 318 else 319 this.drawFrame(this.value); 320 }, 321 322 /** 323 * Draw a single animation frame. 324 * @protected 325 * @param {number} v - Frame value. 326 */ 327 drawFrame: function(v) { 328 this.lastFrameValue = v; 329 this.ctx.clearRect(0, 0, this.size, this.size); 330 this.drawEmptyArc(v); 331 this.drawArc(v); 332 }, 333 334 /** 335 * Draw the arc (part of the circle). 336 * @protected 337 * @param {number} v - Frame value. 338 */ 339 drawArc: function(v) { 340 if (v === 0) 341 return; 342 343 var ctx = this.ctx, 344 r = this.radius, 345 t = this.getThickness(), 346 a = this.startAngle; 347 348 ctx.save(); 349 ctx.beginPath(); 350 351 if (!this.reverse) { 352 ctx.arc(r, r, r - t / 2, a, a + Math.PI * 2 * v); 353 } else { 354 ctx.arc(r, r, r - t / 2, a - Math.PI * 2 * v, a); 355 } 356 357 ctx.lineWidth = t; 358 ctx.lineCap = this.lineCap; 359 ctx.strokeStyle = this.arcFill; 360 ctx.stroke(); 361 ctx.restore(); 362 }, 363 364 /** 365 * Draw the _empty (background)_ arc (part of the circle). 366 * @protected 367 * @param {number} v - Frame value. 368 */ 369 drawEmptyArc: function(v) { 370 var ctx = this.ctx, 371 r = this.radius, 372 t = this.getThickness(), 373 a = this.startAngle; 374 375 if (v < 1) { 376 ctx.save(); 377 ctx.beginPath(); 378 379 if (v <= 0) { 380 ctx.arc(r, r, r - t / 2, 0, Math.PI * 2); 381 } else { 382 if (!this.reverse) { 383 ctx.arc(r, r, r - t / 2, a + Math.PI * 2 * v, a); 384 } else { 385 ctx.arc(r, r, r - t / 2, a, a - Math.PI * 2 * v); 386 } 387 } 388 389 ctx.lineWidth = t; 390 ctx.strokeStyle = this.emptyFill; 391 ctx.stroke(); 392 ctx.restore(); 393 } 394 }, 395 396 /** 397 * Animate the progress bar. 398 * 399 * Throws 3 jQuery events: 400 * 401 * - `circle-animation-start(jqEvent)` 402 * - `circle-animation-progress(jqEvent, animationProgress, stepValue)` - multiple event 403 * animationProgress: from `0.0` to `1.0`; stepValue: from `0.0` to `value` 404 * - `circle-animation-end(jqEvent)` 405 * 406 * @protected 407 * @param {number} v - Final value. 408 */ 409 drawAnimated: function(v) { 410 var self = this, 411 el = this.el, 412 canvas = $(this.canvas); 413 414 // stop previous animation before new "start" event is triggered 415 canvas.stop(true, false); 416 el.trigger('circle-animation-start'); 417 418 canvas 419 .css({animationProgress: 0}) 420 .animate({animationProgress: 1}, $.extend({}, this.animation, { 421 step: function(animationProgress) { 422 var stepValue = self.animationStartValue * (1 - animationProgress) + v * animationProgress; 423 self.drawFrame(stepValue); 424 el.trigger('circle-animation-progress', [animationProgress, stepValue]); 425 } 426 })) 427 .promise() 428 .always(function() { 429 // trigger on both successful & failure animation end 430 el.trigger('circle-animation-end'); 431 }); 432 }, 433 434 /** 435 * Get the circle thickness. 436 * @see CircleProgress#thickness 437 * @protected 438 * @returns {number} 439 */ 440 getThickness: function() { 441 return $.isNumeric(this.thickness) ? this.thickness : this.size / 14; 442 }, 443 444 /** 445 * Get current value. 446 * @protected 447 * @return {number} 448 */ 449 getValue: function() { 450 return this.value; 451 }, 452 453 /**
454 * Set current value (with smooth animation transition). 455 * @protected 456 * @param {number} newValue 457 */ 458 setValue: function(newValue) { 459 if (this.animation) 460 this.animationStartValue = this.lastFrameValue; 461 this.value = newValue; 462 this.draw(); 463 } 464 }; 465 466 //----------------------------------- Initiating jQuery plugin ----------------------------------- 467 $.circleProgress = { 468 // Default options (you may override them) 469 defaults: CircleProgress.prototype 470 }; 471 472 // ease-in-out-cubic 473 $.easing.circleProgressEasing = function(x) { 474 if (x < 0.5) { 475 x = 2 * x; 476 return 0.5 * x * x * x; 477 } else { 478 x = 2 - 2 * x; 479 return 1 - 0.5 * x * x * x; 480 } 481 }; 482 483 /** 484 * Creates an instance of {@link CircleProgress}. 485 * Produces [init event]{@link CircleProgress#init} and [animation events]{@link CircleProgress#drawAnimated}. 486 * 487 * @param {object} [configOrCommand] - Config object or command name. 488 * 489 * Config example (you can specify any {@link CircleProgress} property): 490 * 491 * ```js 492 * { value: 0.75, size: 50, animation: false } 493 * ``` 494 * 495 * Commands: 496 * 497 * ```js 498 * el.circleProgress('widget'); // get the <canvas> 499 * el.circleProgress('value'); // get the value 500 * el.circleProgress('value', newValue); // update the value 501 * el.circleProgress('redraw'); // redraw the circle 502 * el.circleProgress(); // the same as 'redraw' 503 * ``` 504 * 505 * @param {string} [commandArgument] - Some commands (like `'value'`) may require an argument. 506 * @see CircleProgress 507 * @alias "$(...).circleProgress" 508 */ 509 $.fn.circleProgress = function(configOrCommand, commandArgument) { 510 var dataName = 'circle-progress', 511 firstInstance = this.data(dataName); 512 513 if (configOrCommand == 'widget') { 514 if (!firstInstance) 515 throw Error('Calling "widget" method on not initialized instance is forbidden'); 516 return firstInstance.canvas; 517 } 518 519 if (configOrCommand == 'value') { 520 if (!firstInstance) 521 throw Error('Calling "value" method on not initialized instance is forbidden'); 522 if (typeof commandArgument == 'undefined') { 523 return firstInstance.getValue(); 524 } else { 525 var newValue = arguments[1]; 526 return this.each(function() { 527 $(this).data(dataName).setValue(newValue); 528 }); 529 } 530 } 531 532 return this.each(function() { 533 var el = $(this), 534 instance = el.data(dataName), 535 config = $.isPlainObject(configOrCommand) ? configOrCommand : {}; 536 537 if (instance) { 538 instance.init(config); 539 } else { 540 var initialConfig = $.extend({}, el.data()); 541 if (typeof initialConfig.fill == 'string') 542 initialConfig.fill = JSON.parse(initialConfig.fill); 543 if (typeof initialConfig.animation == 'string') 544 initialConfig.animation = JSON.parse(initialConfig.animation); 545 config = $.extend(initialConfig, config); 546 config.el = el; 547 instance = new CircleProgress(config); 548 el.data(dataName, instance); 549 } 550 }); 551 }; 552});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.