jquery-circle-progress
Advanced tools
Comparing version 1.1.4 to 1.2.0
{ | ||
"name": "jquery-circle-progress", | ||
"version": "1.1.4", | ||
"authors": [ | ||
"Rostyslav Bryzgunov <kottenator@gmail.com>" | ||
], | ||
"description": "Plugin to draw animated circular progress bars", | ||
"license": "MIT", | ||
"main": "dist/circle-progress.js", | ||
"keywords": [ | ||
"jquery", | ||
"canvas", | ||
"progress-bar" | ||
], | ||
"homepage": "https://kottenator.github.io/jquery-circle-progress/", | ||
"ignore": [ | ||
"**/.*", | ||
"node_modules", | ||
"bower_components", | ||
"tests" | ||
], | ||
"dependencies": { | ||
"jquery": "*" | ||
}, | ||
"devDependencies": { | ||
"qunit": "1.15.0", | ||
"modernizr": "*" | ||
} | ||
"name": "jquery-circle-progress", | ||
"version": "1.2.0", | ||
"authors": [ | ||
"Rostyslav Bryzgunov <kottenator@gmail.com>" | ||
], | ||
"description": "Plugin to draw animated circular progress bars", | ||
"license": "MIT", | ||
"main": "dist/circle-progress.js", | ||
"keywords": [ | ||
"jquery", | ||
"canvas", | ||
"progress-bar" | ||
], | ||
"homepage": "https://kottenator.github.io/jquery-circle-progress/", | ||
"ignore": [ | ||
"**/.*", | ||
"node_modules", | ||
"bower_components", | ||
"tests" | ||
], | ||
"dependencies": { | ||
"jquery": "*" | ||
} | ||
} |
@@ -1,439 +0,560 @@ | ||
/* | ||
jquery-circle-progress - jQuery Plugin to draw animated circular progress bars | ||
/** | ||
* 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.0 | ||
* @licence MIT | ||
* @preserve | ||
*/ | ||
// UMD factory - https://github.com/umdjs/umd/blob/d31bb6ee7098715e019f52bdfe27b3e4bfd2b97e/templates/jqueryPlugin.js | ||
// Uses CommonJS, AMD 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 | ||
module.exports = function(root, jQuery) { | ||
if (jQuery === undefined) { | ||
// require('jQuery') returns a factory that requires window to | ||
// build a jQuery instance, we normalize how we use modules | ||
// that require this pattern but the window provided is a noop | ||
// if it's defined (how jquery works) | ||
if (typeof window !== 'undefined') { | ||
jQuery = require('jquery'); | ||
} else { | ||
jQuery = require('jquery')(root); | ||
} | ||
} | ||
factory(jQuery); | ||
return jQuery; | ||
}; | ||
} 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); | ||
} | ||
URL: http://kottenator.github.io/jquery-circle-progress/ | ||
Author: Rostyslav Bryzgunov <kottenator@gmail.com> | ||
Version: 1.1.4 | ||
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} | ||
* @default 0.0 | ||
*/ | ||
value: 0.0, | ||
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 canvas in pixels. | ||
* It's a square so we need only one dimension. | ||
* @type {number} | ||
* @default 100.0 | ||
*/ | ||
size: 100.0, | ||
/** | ||
* Size of the circle / canvas in pixels | ||
* @type {number} | ||
*/ | ||
size: 100.0, | ||
/** | ||
* Initial angle for `0.0` value in radians. | ||
* @type {number} | ||
* @default -Math.PI | ||
*/ | ||
startAngle: -Math.PI, | ||
/** | ||
* Initial angle for 0.0 value in radians | ||
* @type {number} | ||
*/ | ||
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', | ||
/** | ||
* 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: | ||
* - `'#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'] | ||
}, | ||
/** | ||
* 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} | ||
* @default 'rgba(0, 0, 0, .1)' | ||
*/ | ||
emptyFill: 'rgba(0, 0, 0, .1)', | ||
/** | ||
* Color of the "empty" arc. Only a color fill supported by now | ||
* @type {string} | ||
*/ | ||
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' | ||
}, | ||
/** | ||
* 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_ - 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, | ||
/** | ||
* 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. | ||
* 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, | ||
/** | ||
* Reverse animation and arc draw | ||
* @type {boolean} | ||
*/ | ||
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', | ||
/** | ||
* Arc line cap ('butt', 'round' or 'square') | ||
* Read more: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap | ||
* @type {string} | ||
*/ | ||
lineCap: 'butt', | ||
/** | ||
* Canvas insertion mode: append or prepend it into the parent element? | ||
* @type {string} | ||
* @default 'prepend' | ||
*/ | ||
insertMode: 'prepend', | ||
//-------------------------------------- protected properties and methods -------------------------------------- | ||
/** | ||
* @protected | ||
*/ | ||
constructor: CircleProgress, | ||
//------------------------------ 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, | ||
/** | ||
* 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, | ||
/** | ||
* Canvas element. Automatically generated and prepended to [this.el]{@link CircleProgress#el}. | ||
* @protected | ||
* @type {HTMLCanvasElement} | ||
*/ | ||
canvas: null, | ||
/** | ||
* 2D-context of the {@link CircleProgress.canvas canvas} | ||
* @protected | ||
* @type {CanvasRenderingContext2D} | ||
*/ | ||
ctx: null, | ||
/** | ||
* 2D-context of [this.canvas]{@link CircleProgress#canvas}. | ||
* @protected | ||
* @type {CanvasRenderingContext2D} | ||
*/ | ||
ctx: null, | ||
/** | ||
* Radius of the outer circle. Automatically calculated as {@link CircleProgress.size} / 2 | ||
* @protected | ||
* @type {number} | ||
*/ | ||
radius: 0.0, | ||
/** | ||
* 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 {@link CircleProgress.fill} option | ||
* @protected | ||
* @type {string|CanvasGradient|CanvasPattern} | ||
*/ | ||
arcFill: null, | ||
/** | ||
* 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, | ||
/** | ||
* 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(); | ||
}, | ||
/** | ||
* 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'); | ||
}, | ||
/** | ||
* @protected | ||
*/ | ||
initWidget: function() { | ||
var canvas = this.canvas = this.canvas || $('<canvas>').prependTo(this.el)[0]; | ||
canvas.width = this.size; | ||
canvas.height = this.size; | ||
this.ctx = canvas.getContext('2d'); | ||
}, | ||
/** | ||
* Initialize `<canvas>`. | ||
* @protected | ||
*/ | ||
initWidget: function() { | ||
if (!this.canvas) | ||
this.canvas = $('<canvas>')[this.insertMode == 'prepend' ? 'prependTo' : 'appendTo'](this.el)[0]; | ||
/** | ||
* 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; | ||
var canvas = this.canvas; | ||
canvas.width = this.size; | ||
canvas.height = this.size; | ||
this.ctx = canvas.getContext('2d'); | ||
if (!fill) | ||
throw Error("The fill is not specified!"); | ||
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); | ||
} | ||
}, | ||
if (fill.color) | ||
this.arcFill = fill.color; | ||
/** | ||
* 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.gradient) { | ||
var gr = fill.gradient; | ||
if (!fill) | ||
throw Error("The fill is not specified!"); | ||
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 | ||
]; | ||
if (typeof fill == 'string') | ||
fill = {color: fill}; | ||
var lg = ctx.createLinearGradient.apply(ctx, gd); | ||
if (fill.color) | ||
this.arcFill = fill.color; | ||
for (var i = 0; i < gr.length; i++) { | ||
var color = gr[i], | ||
pos = i / (gr.length - 1); | ||
if (fill.gradient) { | ||
var gr = fill.gradient; | ||
if ($.isArray(color)) { | ||
pos = color[1]; | ||
color = color[0]; | ||
} | ||
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 | ||
]; | ||
lg.addColorStop(pos, color); | ||
} | ||
var lg = ctx.createLinearGradient.apply(ctx, gd); | ||
this.arcFill = lg; | ||
} | ||
} | ||
for (var i = 0; i < gr.length; i++) { | ||
var color = gr[i], | ||
pos = i / (gr.length - 1); | ||
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; | ||
if ($.isArray(color)) { | ||
pos = color[1]; | ||
color = color[0]; | ||
} | ||
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); | ||
} | ||
}, | ||
lg.addColorStop(pos, color); | ||
} | ||
draw: function() { | ||
if (this.animation) | ||
this.drawAnimated(this.value); | ||
else | ||
this.drawFrame(this.value); | ||
}, | ||
this.arcFill = lg; | ||
} | ||
} | ||
/** | ||
* @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); | ||
}, | ||
if (fill.image) { | ||
var img; | ||
/** | ||
* @protected | ||
* @param {number} v - Frame value | ||
*/ | ||
drawArc: function(v) { | ||
var ctx = this.ctx, | ||
r = this.radius, | ||
t = this.getThickness(), | ||
a = this.startAngle; | ||
if (fill.image instanceof Image) { | ||
img = fill.image; | ||
} else { | ||
img = new Image(); | ||
img.src = fill.image; | ||
} | ||
ctx.save(); | ||
ctx.beginPath(); | ||
if (img.complete) | ||
setImageFill(); | ||
else | ||
img.onload = setImageFill; | ||
} | ||
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); | ||
} | ||
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); | ||
} | ||
}, | ||
ctx.lineWidth = t; | ||
ctx.lineCap = this.lineCap; | ||
ctx.strokeStyle = this.arcFill; | ||
ctx.stroke(); | ||
ctx.restore(); | ||
}, | ||
/** | ||
* Draw the circle. | ||
* @protected | ||
*/ | ||
draw: function() { | ||
if (this.animation) | ||
this.drawAnimated(this.value); | ||
else | ||
this.drawFrame(this.value); | ||
}, | ||
/** | ||
* @protected | ||
* @param {number} v - Frame value | ||
*/ | ||
drawEmptyArc: function(v) { | ||
var ctx = this.ctx, | ||
r = this.radius, | ||
t = this.getThickness(), | ||
a = this.startAngle; | ||
/** | ||
* 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); | ||
}, | ||
if (v < 1) { | ||
ctx.save(); | ||
ctx.beginPath(); | ||
/** | ||
* Draw the arc (part of the circle). | ||
* @protected | ||
* @param {number} v - Frame value. | ||
*/ | ||
drawArc: function(v) { | ||
if (v === 0) | ||
return; | ||
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); | ||
} | ||
} | ||
var ctx = this.ctx, | ||
r = this.radius, | ||
t = this.getThickness(), | ||
a = this.startAngle; | ||
ctx.lineWidth = t; | ||
ctx.strokeStyle = this.emptyFill; | ||
ctx.stroke(); | ||
ctx.restore(); | ||
} | ||
}, | ||
ctx.save(); | ||
ctx.beginPath(); | ||
/** | ||
* @protected | ||
* @param {number} v - Value | ||
*/ | ||
drawAnimated: function(v) { | ||
var self = this, | ||
el = this.el, | ||
canvas = $(this.canvas); | ||
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); | ||
} | ||
// stop previous animation before new "start" event is triggered | ||
canvas.stop(true, false); | ||
el.trigger('circle-animation-start'); | ||
ctx.lineWidth = t; | ||
ctx.lineCap = this.lineCap; | ||
ctx.strokeStyle = this.arcFill; | ||
ctx.stroke(); | ||
ctx.restore(); | ||
}, | ||
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'); | ||
}); | ||
}, | ||
/** | ||
* 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; | ||
/** | ||
* @protected | ||
* @returns {number} | ||
*/ | ||
getThickness: function() { | ||
return $.isNumeric(this.thickness) ? this.thickness : this.size / 14; | ||
}, | ||
if (v < 1) { | ||
ctx.save(); | ||
ctx.beginPath(); | ||
getValue: function() { | ||
return this.value; | ||
}, | ||
setValue: function(newValue) { | ||
if (this.animation) | ||
this.animationStartValue = this.lastFrameValue; | ||
this.value = newValue; | ||
this.draw(); | ||
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); | ||
} | ||
} | ||
}; | ||
//-------------------------------------------- Initiating jQuery plugin -------------------------------------------- | ||
$.circleProgress = { | ||
// Default options (you may override them) | ||
defaults: CircleProgress.prototype | ||
}; | ||
ctx.lineWidth = t; | ||
ctx.strokeStyle = this.emptyFill; | ||
ctx.stroke(); | ||
ctx.restore(); | ||
} | ||
}, | ||
// 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. | ||
* Animate the progress bar. | ||
* | ||
* Appends <canvas> to the element or updates already appended one. | ||
* Throws 3 jQuery events: | ||
* | ||
* 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)` | ||
* | ||
* - 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 configOrCommand - Config object or command name | ||
* Example: { value: 0.75, size: 50, animation: false }; | ||
* you may set any public property (see above); | ||
* `animation` may be set to false; | ||
* you may use .circleProgress('widget') to get the canvas | ||
* you may use .circleProgress('value', newValue) to dynamically update the value | ||
* | ||
* @param commandArgument - Some commands (like 'value') may require an argument | ||
* @protected | ||
* @param {number} v - Final value. | ||
*/ | ||
$.fn.circleProgress = function(configOrCommand, commandArgument) { | ||
var dataName = 'circle-progress', | ||
firstInstance = this.data(dataName); | ||
drawAnimated: function(v) { | ||
var self = this, | ||
el = this.el, | ||
canvas = $(this.canvas); | ||
if (configOrCommand == 'widget') { | ||
if (!firstInstance) | ||
throw Error('Calling "widget" method on not initialized instance is forbidden'); | ||
return firstInstance.canvas; | ||
} | ||
// stop previous animation before new "start" event is triggered | ||
canvas.stop(true, false); | ||
el.trigger('circle-animation-start'); | ||
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); | ||
}); | ||
} | ||
} | ||
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, 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; | ||
}; | ||
/** | ||
* 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() { | ||
var el = $(this), | ||
instance = el.data(dataName), | ||
config = $.isPlainObject(configOrCommand) ? configOrCommand : {}; | ||
$(this).data(dataName).setValue(newValue); | ||
}); | ||
} | ||
} | ||
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); | ||
} | ||
}); | ||
}; | ||
})(jQuery); | ||
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); | ||
} | ||
}); | ||
}; | ||
}); |
// Karma configuration | ||
module.exports = function(config) { | ||
var customLaunchers = { | ||
'latest-chrome': { | ||
base: 'SauceLabs', | ||
browserName: 'chrome' | ||
}, | ||
'latest-firefox': { | ||
base: 'SauceLabs', | ||
browserName: 'firefox' | ||
}, | ||
'internet-explorer-9': { | ||
base: 'SauceLabs', | ||
browserName: 'internet explorer', | ||
version: '9' | ||
}, | ||
'safari-5': { | ||
base: 'SauceLabs', | ||
platform: "OS X 10.6", | ||
browserName: 'safari', | ||
version: '5' | ||
}, | ||
'ios-6': { | ||
base: 'SauceLabs', | ||
platform: "OS X 10.8", | ||
browserName: 'iphone', | ||
version: "6.0" | ||
}, | ||
'android-4': { | ||
base: 'SauceLabs', | ||
platform: "Linux", | ||
device: 'Motorola Droid Razr Emulator', | ||
browserName: 'android', | ||
version: "4.0" | ||
} | ||
}; | ||
var customLaunchers = { | ||
'Latest Chrome on Windows 10': { | ||
base: 'SauceLabs', | ||
platform: 'Windows 10', | ||
browserName: 'chrome', | ||
version: 'latest' | ||
}, | ||
'Latest Firefox on Windows 10': { | ||
base: 'SauceLabs', | ||
platform: 'Windows 10', | ||
browserName: 'firefox', | ||
version: 'latest' | ||
}, | ||
'IE9 on Windows 7': { | ||
base: 'SauceLabs', | ||
platform: 'Windows 7', | ||
browserName: 'internet explorer', | ||
version: '9' | ||
}, | ||
'Latest Safari on OS X 10.11': { | ||
base: 'SauceLabs', | ||
platform: 'OS X 10.11', | ||
browserName: 'safari', | ||
version: 'latest' | ||
}, | ||
'iPhone emulator': { | ||
base: 'SauceLabs', | ||
platform: "OS X 10.11", | ||
browserName: 'iphone', | ||
version: "8.1" | ||
}, | ||
'Android emulator': { | ||
base: 'SauceLabs', | ||
platform: 'Linux', | ||
browserName: 'android', | ||
version: '5.0' | ||
} | ||
}; | ||
config.set({ | ||
frameworks: ['qunit'], | ||
files: [ | ||
{ pattern: 'tests/images/circle.png', served: true, watched: false, included: false }, | ||
'bower_components/jquery/dist/jquery.min.js', | ||
'bower_components/modernizr/modernizr.js', | ||
'dist/circle-progress.js', | ||
'tests/test_utils.js', | ||
'tests/tests.js' | ||
], | ||
sauceLabs: { | ||
testName: 'Unit Tests for jquery-circle-progress' | ||
}, | ||
captureTimeout: 120000, | ||
customLaunchers: customLaunchers, | ||
browsers: Object.keys(customLaunchers), | ||
reporters: ['dots', 'saucelabs'], | ||
singleRun: true | ||
}); | ||
config.set({ | ||
frameworks: ['qunit'], | ||
files: [ | ||
{pattern: 'tests/images/circle.png', served: true, watched: false, included: false}, | ||
'node_modules/jquery/dist/jquery.min.js', | ||
'dist/circle-progress.js', | ||
'tests/test_utils.js', | ||
'tests/tests.js' | ||
], | ||
sauceLabs: { | ||
testName: 'Unit tests for jquery-circle-progress' | ||
}, | ||
captureTimeout: 120000, | ||
customLaunchers: customLaunchers, | ||
browsers: Object.keys(customLaunchers), | ||
reporters: ['dots', 'saucelabs'], | ||
singleRun: true | ||
}); | ||
}; |
// Karma configuration | ||
module.exports = function(config) { | ||
config.set({ | ||
frameworks: ['qunit'], | ||
files: [ | ||
{ pattern: 'tests/images/circle.png', served: true, watched: false, included: false }, | ||
'bower_components/jquery/dist/jquery.min.js', | ||
'bower_components/modernizr/modernizr.js', | ||
'dist/circle-progress.js', | ||
'tests/test_utils.js', | ||
'tests/tests.js' | ||
], | ||
browsers: ['Firefox', 'PhantomJS'], | ||
singleRun: true | ||
}); | ||
config.set({ | ||
frameworks: ['qunit'], | ||
files: [ | ||
{pattern: 'tests/images/circle.png', served: true, watched: false, included: false}, | ||
'node_modules/jquery/dist/jquery.min.js', | ||
'dist/circle-progress.js', | ||
'tests/test_utils.js', | ||
'tests/tests.js' | ||
], | ||
browsers: ['Firefox', 'PhantomJS'], | ||
singleRun: true | ||
}); | ||
}; |
{ | ||
"name": "jquery-circle-progress", | ||
"version": "1.1.4", | ||
"author": "Rostyslav Bryzgunov <kottenator@gmail.com>", | ||
"description": "Plugin to draw animated circular progress bars", | ||
"license": "MIT", | ||
"keywords": [ | ||
"jquery", | ||
"canvas", | ||
"progress-bar" | ||
], | ||
"main": "dist/circle-progress.js", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"karma": "~0.12.24", | ||
"qunitjs": "~1.15.0", | ||
"karma-qunit": "~0.1.4", | ||
"karma-firefox-launcher": "~0.1.3", | ||
"karma-phantomjs-launcher": "~0.1.4", | ||
"karma-sauce-launcher": "~0.2.10" | ||
}, | ||
"scripts": { | ||
"test": "karma start" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/kottenator/jquery-circle-progress.git" | ||
} | ||
"name": "jquery-circle-progress", | ||
"version": "1.2.0", | ||
"author": "Rostyslav Bryzgunov <kottenator@gmail.com>", | ||
"description": "Plugin to draw animated circular progress bars", | ||
"license": "MIT", | ||
"main": "dist/circle-progress.js", | ||
"keywords": [ | ||
"jquery", | ||
"canvas", | ||
"progress-bar" | ||
], | ||
"dependencies": { | ||
"jquery": "*" | ||
}, | ||
"devDependencies": { | ||
"karma": "~1.2", | ||
"qunitjs": "~2.0", | ||
"karma-qunit": "~1.2", | ||
"karma-firefox-launcher": "~1.0", | ||
"karma-phantomjs-launcher": "~1.0", | ||
"karma-sauce-launcher": "~1.0", | ||
"uglify-js": "~2.7", | ||
"jsdoc": "~3.4" | ||
}, | ||
"scripts": { | ||
"test": "karma start", | ||
"build-min": "uglifyjs dist/circle-progress.js -cmo dist/circle-progress.min.js --comments", | ||
"build-docs": "jsdoc dist/circle-progress.js -c jsdoc.conf" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/kottenator/jquery-circle-progress.git" | ||
} | ||
} |
114
README.md
jquery-circle-progress | ||
====================== | ||
[![Build Status](https://travis-ci.org/kottenator/jquery-circle-progress.svg?branch=master)](https://travis-ci.org/kottenator/jquery-circle-progress) | ||
[![Bower version](https://badge.fury.io/bo/jquery-circle-progress.svg)](https://badge.fury.io/bo/jquery-circle-progress) | ||
@@ -13,2 +15,3 @@ jQuery Plugin to draw animated circular progress bars like this: | ||
------- | ||
Download [latest GitHub release](https://github.com/kottenator/jquery-circle-progress/releases) | ||
@@ -19,4 +22,5 @@ or `bower install jquery-circle-progress` | ||
----- | ||
```html | ||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> | ||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> | ||
<script src="jquery-circle-progress/dist/circle-progress.js"></script> | ||
@@ -39,2 +43,3 @@ | ||
------- | ||
You should specify options like in usage example above. | ||
@@ -47,13 +52,14 @@ | ||
| startAngle | Initial angle (for `0` value) <br> Default: `-Math.PI` | | ||
| reverse | Reverse animation and arc draw<br> Default: `false` | | ||
| reverse | Reverse animation and arc draw <br> Default: `false` | | ||
| thickness | Width of the arc. By default it's automatically calculated as 1/14 of `size` but you may set your own number <br> Default: `"auto"` | | ||
| lineCap | Arc line cap: `"butt"`, `"round"` or `"square"` - [read more](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.lineCap) <br> Default: `"butt"` | ||
| fill | The arc fill config. You may specify next: <br>- `{ color: "#ff1e41" }` <br>- `{ color: 'rgba(255, 255, 255, .3)' }` <br>- `{ gradient: ["red", "green", "blue"] }` <br>- `{ gradient: [["red", .2], ["green", .3], ["blue", .8]] }` <br>- `{ gradient: [ ... ], gradientAngle: Math.PI / 4 }` <br>- `{ gradient: [ ... ], gradientDirection: [x0, y0, x1, y1] }` <br>- `{ image: "http://i.imgur.com/pT0i89v.png" }`<br>- `{ image: imageInstance }`<br>- `{ color: "lime", image: "http://i.imgur.com/pT0i89v.png" }` <br> Default: `{ gradient: ["#3aeabb", "#fdd250"] }` | | ||
| fill | The arc fill config. You may specify next: <br>- `"#ff1e41"` <br>- `{ color: "#ff1e41" }` <br>- `{ color: 'rgba(255, 255, 255, .3)' }` <br>- `{ gradient: ["red", "green", "blue"] }` <br>- `{ gradient: [["red", .2], ["green", .3], ["blue", .8]] }` <br>- `{ gradient: [ ... ], gradientAngle: Math.PI / 4 }` <br>- `{ gradient: [ ... ], gradientDirection: [x0, y0, x1, y1] }` <br>- `{ image: "http://i.imgur.com/pT0i89v.png" }`<br>- `{ image: imageInstance }`<br>- `{ color: "lime", image: "http://i.imgur.com/pT0i89v.png" }` <br> Default: `{ gradient: ["#3aeabb", "#fdd250"] }` | | ||
| emptyFill | Color of the "empty" arc. Only a color fill supported by now <br> Default: `"rgba(0, 0, 0, .1)"` | | ||
| animation | Animation config. See [jQuery animations](http://api.jquery.com/animate/). <br> You may also set it to `false` <br> Default: `{ duration: 1200, easing: "circleProgressEase" }` <br> `"circleProgressEase"` *is just a ease-in-out-cubic easing* | | ||
| animationStartValue | 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` <br> Default: `0.0` | ||
| insertMode | Canvas insertion mode: append or prepend it into the parent element? <br> Default: `"prepend"` | | ||
From version `1.1.3` you can specify any config option as HTML `data-` attribute. | ||
From version `1.1.3` you can specify any config option as HTML `data-` attribute. | ||
It will work *only on init*, i.e. after the widget is inited you may update its properties only via `.circleProgress({/*...*/})` method. `data-` attributes will be ignored. | ||
It will work *only on init*, i.e. after the widget is inited you may update its properties only via `.circleProgress({/*...*/})` method. `data-` attributes will be ignored. | ||
@@ -79,2 +85,3 @@ Also, object options like `"fill"` or `"animation"` should be valid JSON (and don't forget about HTML-escaping): | ||
------ | ||
When animation is enabled, there are 3 events available: | ||
@@ -88,4 +95,11 @@ | ||
When the circular progress bar is inited or re-inited, there is the following event: | ||
| Event | Handler | | ||
| ---- | ---- | | ||
| `circle-inited` | `function(event)`: <br>- `event` - jQuery event | | ||
Browsers support | ||
---------------- | ||
It uses `<canvas>` which is supported by all modern browsers *(including mobile browsers)* | ||
@@ -100,5 +114,6 @@ and Internet Explorer 9+ ([Can I Use](http://caniuse.com/#search=canvas)). | ||
#### Get/set value | ||
### Get/set value | ||
Get it: | ||
```js | ||
@@ -113,11 +128,12 @@ $('.circle').circleProgress({ value: 0.5 }); | ||
Set it: | ||
```js | ||
$('.circle').circleProgress('value', 0.75); // set value to 0.75 & animate the change | ||
$('.circle').circleProgress('value', 0.75); // set value to 0.75 & animate the change | ||
``` | ||
It will update *all* selected items value and animate the change. | ||
It will update *all* selected items value and animate the change. | ||
It doesn't *redraw* the widget - it updates the value & animates the changes. | ||
For example, it may be an AJAX loading indicator, which shows the loading progress. | ||
#### Get `<canvas>` | ||
### Get `<canvas>` | ||
@@ -132,3 +148,4 @@ ```js | ||
#### Get `CircleProgress` instance | ||
### Get `CircleProgress` instance | ||
```js | ||
@@ -138,3 +155,4 @@ var instance = $('#circle').data('circle-progress'); | ||
#### Redraw existing circle | ||
### Redraw existing circle | ||
```js | ||
@@ -149,3 +167,4 @@ $('#circle').circleProgress({ value: 0.5, fill: { color: 'orange' }}); | ||
#### Change default options | ||
### Change default options | ||
```js | ||
@@ -158,24 +177,71 @@ $.circleProgress.defaults.size = 50; | ||
#### How to start the animation only when the circle appears in browser's view (on scrolling)? | ||
<dl> | ||
<dt>How to start the animation only when the circle appears in browser's view (on scrolling)? | ||
<dd>Here is <a href="https://github.com/kottenator/jquery-circle-progress/issues/8">my proposed solution</a>. | ||
<dt>How to make the size flexible? | ||
<dd>E.g. for responsive design, you can do it <a href="https://github.com/kottenator/jquery-circle-progress/issues/17">in the following way</a>. | ||
<dt>What if I need it to run in IE8? | ||
<dd>There is no full-feature support for IE8 (actually, I didn't imlpement IE8 support at all). But you may follow <a href="https://github.com/kottenator/jquery-circle-progress/issues/35">my recommendations</a>. | ||
<dt>How to stop the animation? | ||
<dd>Here is <a href="https://github.com/kottenator/jquery-circle-progress/issues/37">what you can do</a>. | ||
<dt>Can I handle "click" event? | ||
<dd>It's not in the "core" but you can use <a href="http://output.jsbin.com/fetequ/5">my example of mouse/touch events handling</a>. | ||
<dt>May I customize the shape somehow? | ||
<dd>It's a bit "tricky" but possible. Here is <a href="https://github.com/kottenator/jquery-circle-progress/wiki/Custom-layouts">my little collection</a>. | ||
</dl> | ||
Here is [my proposed solution](https://github.com/kottenator/jquery-circle-progress/issues/8). | ||
Development | ||
----------- | ||
#### How to make the size flexible? | ||
### Install | ||
E.g. for Retina support or for responsive design, you can do it [in the following way](https://github.com/kottenator/jquery-circle-progress/issues/17). | ||
```sh | ||
git clone git@github.com:kottenator/jquery-circle-progress.git | ||
npm install | ||
``` | ||
#### What if I need it to run in IE8? | ||
### Update minified version | ||
There is no full-feature support for IE8 (actually, I didn't imlpement IE8 support at all). But you may follow [my recommendations](https://github.com/kottenator/jquery-circle-progress/issues/35). | ||
You need to update `dist/circle-progress.min.js` after any change to `dist/circle-progress.js`: | ||
```sh | ||
npm run build-min | ||
``` | ||
#### How to stop the animation? | ||
If you're using one of JetBrains IDEs - you can configure a File Watcher. | ||
It's also possible to use some CLI tool like [Watchman](https://facebook.github.io/watchman/). | ||
Here is [what you can do](https://github.com/kottenator/jquery-circle-progress/issues/37). | ||
### Test | ||
#### Can I handle "click" event? | ||
```sh | ||
npm test | ||
``` | ||
It's not in the "core" but you can use [my example of mouse/touch events handling](http://output.jsbin.com/fetequ/5). | ||
SauceLabs: | ||
#### May I customize the shape somehow? | ||
```sh | ||
export SAUCE_USERNAME=... | ||
export SAUCE_ACCESS_KEY=... | ||
export BUILD_NUMBER=... | ||
npm test -- karma-saucelabs.conf.js | ||
``` | ||
It's a bit "tricky" but possible. Here is [my little collection](https://github.com/kottenator/jquery-circle-progress/wiki/Custom-layouts). | ||
### Build docs | ||
The API docs are not complete yet but you can build them: | ||
```sh | ||
npm run build-docs | ||
``` | ||
They will be generated in `docs/api/`. | ||
### Release new version | ||
You need to: | ||
* finalize the code | ||
* update min dist: `npm run build-min` | ||
* push into `master` | ||
* create new version tag (e.g.): `git tag v1.2.3 && git push --tags` | ||
* update the version in `package.json`, `bower.json` and `dist/circle-progress.js` docstring |
(function() { | ||
QUnit.extend(QUnit.assert, { | ||
pixelRGBA: function(canvas, x, y, expectedRGBA, message) { | ||
return _pixelColor(canvas, x, y, expectedRGBA, null, message, _parseRGBA, _dumpRGBA); | ||
}, | ||
QUnit.extend(QUnit.assert, { | ||
pixelRGBA: function(canvas, x, y, expectedRGBA, message) { | ||
return _pixelColor.call(this, canvas, x, y, expectedRGBA, 0, message, _parseRGBA, _dumpRGBA); | ||
}, | ||
pixelHex: function(canvas, x, y, expectedHex, message) { | ||
return _pixelColor(canvas, x, y, expectedHex, null, message, _parseHex, _dumpHex, true); | ||
}, | ||
pixelHex: function(canvas, x, y, expectedHex, message) { | ||
return _pixelColor.call(this, canvas, x, y, expectedHex, 0, message, _parseHex, _dumpHex, true); | ||
}, | ||
pixelCloseRGBA: function(canvas, x, y, expectedRGBA, maxDiff, message) { | ||
return _pixelColor(canvas, x, y, expectedRGBA, maxDiff, message, _parseRGBA, _dumpRGBA); | ||
}, | ||
pixelCloseRGBA: function(canvas, x, y, expectedRGBA, maxDiff, message) { | ||
if (typeof maxDiff == 'undefined' || maxDiff === null) | ||
maxDiff = 0.01; | ||
return _pixelColor.call(this, canvas, x, y, expectedRGBA, maxDiff, message, _parseRGBA, _dumpRGBA); | ||
}, | ||
pixelCloseHex: function(canvas, x, y, expectedHex, maxDiff, message) { | ||
return _pixelColor(canvas, x, y, expectedHex, maxDiff, message, _parseHex, _dumpHex, true); | ||
} | ||
}); | ||
pixelCloseHex: function(canvas, x, y, expectedHex, maxDiff, message) { | ||
if (typeof maxDiff == 'undefined' || maxDiff === null) | ||
maxDiff = 0.015; | ||
return _pixelColor.call(this, canvas, x, y, expectedHex, maxDiff, message, _parseHex, _dumpHex, true); | ||
} | ||
}); | ||
function _pixelColor(canvas, x, y, expectedColor, maxDiff, message, parseColorFn, dumpColorFn, ignoreAlpha) { | ||
var data = canvas.getContext('2d').getImageData(x, y, 1, 1).data, | ||
expectedData = parseColorFn(expectedColor), | ||
actualColor = dumpColorFn(data); | ||
function _pixelColor(canvas, x, y, expectedColor, maxDiff, message, parseColorFn, dumpColorFn, ignoreAlpha) { | ||
var scaleBy = window.devicePixelRatio || 1, | ||
data = canvas.getContext('2d').getImageData(Math.round(x * scaleBy), Math.round(y * scaleBy), 1, 1).data, | ||
expectedData = parseColorFn(expectedColor), | ||
actualColor = dumpColorFn(data); | ||
maxDiff = maxDiff || 0; | ||
maxDiff = maxDiff || 0; | ||
var actualDiff = Math.max( | ||
Math.abs(data[0] - expectedData[0]) / 255, | ||
Math.abs(data[1] - expectedData[1]) / 255, | ||
Math.abs(data[2] - expectedData[2]) / 255, | ||
ignoreAlpha ? 0 : Math.abs(data[3] - expectedData[3]) / 255 | ||
); | ||
var actualDiff = Math.max( | ||
Math.abs(data[0] - expectedData[0]) / 255, | ||
Math.abs(data[1] - expectedData[1]) / 255, | ||
Math.abs(data[2] - expectedData[2]) / 255, | ||
ignoreAlpha ? 0 : Math.abs(data[3] - expectedData[3]) / 255 | ||
); | ||
var result = actualDiff <= maxDiff; | ||
var result = actualDiff <= maxDiff; | ||
if (!message) { | ||
message = "Pixel color at " + x + "×" + y + " should be "; | ||
message += maxDiff ? "close to " + expectedColor : "equal to " + expectedColor; | ||
if (!result) { | ||
message += ". Actual color: " + actualColor; | ||
message += maxDiff ? ". Actual diff: " + actualDiff.toFixed(6) + ". Expected diff: " + maxDiff : ""; | ||
} | ||
} | ||
QUnit.push(result, actualColor, expectedColor, message); | ||
if (!message) { | ||
message = "Pixel color at " + x + "×" + y + " should be "; | ||
message += maxDiff ? "close to " + expectedColor : "equal to " + expectedColor; | ||
if (!result) { | ||
message += ". Actual color: " + actualColor; | ||
message += maxDiff ? ". Actual diff: " + actualDiff.toFixed(6) + ". Expected diff: " + maxDiff : ""; | ||
} | ||
} | ||
function _parseRGBA(s) { | ||
s = s.replace(/^rgba\(|\s+|\)$/gi, '').split(','); | ||
return [ | ||
parseInt(s[0], 10), | ||
parseInt(s[1], 10), | ||
parseInt(s[2], 10), | ||
s[3] * 255 | ||
]; | ||
} | ||
this.push(result, actualColor, expectedColor, message); | ||
} | ||
function _dumpRGBA(data) { | ||
return 'rgba(' + data[0] + ', ' + data[1] + ', ' + data[2] + ', ' + data[3] / 255 + ')'; | ||
} | ||
function _parseRGBA(s) { | ||
s = s.replace(/^rgba\(|\s+|\)$/gi, '').split(','); | ||
return [ | ||
parseInt(s[0], 10), | ||
parseInt(s[1], 10), | ||
parseInt(s[2], 10), | ||
s[3] * 255 | ||
]; | ||
} | ||
function _parseHex(s) { | ||
s = s.replace(/[#\s]/gi, ''); | ||
return [ | ||
parseInt(s.substr(0, 2), 16), | ||
parseInt(s.substr(2, 2), 16), | ||
parseInt(s.substr(4, 2), 16), | ||
255 | ||
]; | ||
} | ||
function _dumpRGBA(data) { | ||
return 'rgba(' + data[0] + ', ' + data[1] + ', ' + data[2] + ', ' + data[3] / 255 + ')'; | ||
} | ||
function _dumpHex(data) { | ||
var r = '0' + data[0].toString(16), | ||
g = '0' + data[1].toString(16), | ||
b = '0' + data[2].toString(16); | ||
r = r.substr(r.length - 2); | ||
g = g.substr(g.length - 2); | ||
b = b.substr(b.length - 2); | ||
return '#' + r + g + b; | ||
} | ||
function _parseHex(s) { | ||
s = s.replace(/[#\s]/gi, ''); | ||
return [ | ||
parseInt(s.substr(0, 2), 16), | ||
parseInt(s.substr(2, 2), 16), | ||
parseInt(s.substr(4, 2), 16), | ||
255 | ||
]; | ||
} | ||
function _dumpHex(data) { | ||
var r = '0' + data[0].toString(16), | ||
g = '0' + data[1].toString(16), | ||
b = '0' + data[2].toString(16); | ||
r = r.substr(r.length - 2); | ||
g = g.substr(g.length - 2); | ||
b = b.substr(b.length - 2); | ||
return '#' + r + g + b; | ||
} | ||
})(); |
(function() { | ||
if (Modernizr.canvas) { | ||
QUnit.module("Layout tests, no animation"); | ||
// Utilities | ||
function createCircle(cfg) { | ||
var output = $('#qunit-fixture'); | ||
if (!output[0]) | ||
output = $('body'); | ||
var el = $('<span>').appendTo(output).circleProgress(cfg); | ||
return el.circleProgress('widget'); | ||
} | ||
QUnit.test("Test circle with value = 0 (without any options)", function(assert) { | ||
var canvas = createCircle({ value: 0 }), | ||
$canvas = $(canvas), | ||
defaultSize = 100, | ||
defaultThickness = parseInt(defaultSize / 14); // 7 | ||
QUnit.module("Layout tests, no animation"); | ||
assert.equal($.circleProgress.defaults.size, defaultSize, "Default circle size: 100 pixels"); | ||
assert.equal($.circleProgress.defaults.thickness, 'auto', "Default circle thickness: 'auto' (i.e. 1/14 of size)"); | ||
assert.equal(canvas.tagName.toLowerCase(), 'canvas', "Method .circleProgress('widget') returns HTMLCanvasElement"); | ||
assert.equal($canvas.width(), defaultSize, "Default width: 100 pixels"); | ||
assert.equal($canvas.height(), defaultSize, "Default height: 100 pixels"); | ||
assert.pixelCloseRGBA(canvas, 0, defaultSize / 2, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelCloseRGBA(canvas, defaultThickness - 1, defaultSize / 2, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelRGBA(canvas, defaultThickness + 1, defaultSize / 2, 'rgba(0, 0, 0, 0)'); | ||
}); | ||
QUnit.test("Test circle with value = 0 (without any options)", function(assert) { | ||
var canvas = createCircle({value: 0}), | ||
$canvas = $(canvas), | ||
defaultSize = 100, | ||
defaultThickness = parseInt(defaultSize / 14); // 7 | ||
QUnit.test("Test circle with value = 0.5 and default fill", function(assert) { | ||
var canvas = createCircle({ | ||
value: 0.5, | ||
animation: false | ||
}), | ||
size = $.circleProgress.defaults.size; | ||
assert.equal($.circleProgress.defaults.size, defaultSize, "Default circle size: 100 pixels"); | ||
assert.equal($.circleProgress.defaults.thickness, 'auto', "Default circle thickness: 'auto' (i.e. 1/14 of size)"); | ||
assert.equal(canvas.tagName.toLowerCase(), 'canvas', "Method .circleProgress('widget') returns HTMLCanvasElement"); | ||
assert.equal($canvas.width(), defaultSize, "Default width: 100 pixels"); | ||
assert.equal($canvas.height(), defaultSize, "Default height: 100 pixels"); | ||
assert.pixelCloseRGBA(canvas, 0, defaultSize / 2, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelCloseRGBA(canvas, defaultThickness - 1, defaultSize / 2, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelRGBA(canvas, defaultThickness + 1, defaultSize / 2, 'rgba(0, 0, 0, 0)'); | ||
}); | ||
assert.pixelCloseHex(canvas, 1, size / 2 - 1, '#3aeabb', 0.015); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelCloseHex(canvas, size - 1, size / 2 - 1, '#fdd250', 0.015); | ||
assert.pixelCloseRGBA(canvas, size - 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelCloseHex(canvas, size / 2, 1, '#9ade85', 0.015); | ||
assert.pixelRGBA(canvas, size / 2, 8, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelCloseRGBA(canvas, size / 2, size - 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelRGBA(canvas, size / 2, size - 9, 'rgba(0, 0, 0, 0)'); | ||
}); | ||
QUnit.test("Test circle with value = 0.5 and default fill", function(assert) { | ||
var canvas = createCircle({ | ||
value: 0.5, | ||
animation: false | ||
}), | ||
size = $.circleProgress.defaults.size; | ||
QUnit.test("Test circle with value = 0.5 and solid fill", function(assert) { | ||
var color = '#ff0000', | ||
canvas = createCircle({ | ||
value: 0.5, | ||
fill: { color: color }, | ||
animation: false | ||
}), | ||
defaultSize = $.circleProgress.defaults.size; | ||
assert.pixelCloseHex(canvas, 1, size / 2 - 1, '#3aeabb'); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelCloseHex(canvas, size - 1, size / 2 - 1, '#fdd250'); | ||
assert.pixelCloseRGBA(canvas, size - 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelCloseHex(canvas, size / 2, 1, '#9ade85'); | ||
assert.pixelRGBA(canvas, size / 2, 8, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelCloseRGBA(canvas, size / 2, size - 1, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelRGBA(canvas, size / 2, size - 9, 'rgba(0, 0, 0, 0)'); | ||
}); | ||
assert.pixelHex(canvas, 1, defaultSize / 2 - 1, color); | ||
assert.pixelHex(canvas, defaultSize - 1, defaultSize / 2 - 1, color); | ||
}); | ||
QUnit.test("Test circle with value = 0.5 and solid fill", function(assert) { | ||
var color = '#ff0000', | ||
canvas = createCircle({ | ||
value: 0.5, | ||
fill: {color: color}, | ||
animation: false | ||
}), | ||
defaultSize = $.circleProgress.defaults.size; | ||
QUnit.module("Layout tests with animation"); | ||
assert.pixelHex(canvas, 1, defaultSize / 2 - 1, color); | ||
assert.pixelHex(canvas, defaultSize - 1, defaultSize / 2 - 1, color); | ||
}); | ||
QUnit.test("Test circle with value = 0.5 and solid fill", function(assert) { | ||
var color = '#00aa55', | ||
canvas = createCircle({ | ||
value: 0.5, | ||
fill: { color: color } | ||
}), | ||
size = $.circleProgress.defaults.size; | ||
QUnit.module("Layout tests with animation"); | ||
assert.expect(8); | ||
QUnit.stop(); | ||
QUnit.test("Test circle with value = 0.5 and solid fill", function(assert) { | ||
var color = '#00aa55', | ||
canvas = createCircle({ | ||
value: 0.5, | ||
fill: {color: color} | ||
}), | ||
size = $.circleProgress.defaults.size; | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 - 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelHex(canvas, 1, size / 2 - 1, color); | ||
}, 200); | ||
assert.expect(8); | ||
var done = assert.async(); | ||
assert.pixelCloseRGBA(canvas, size / 2 + 1, 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelHex(canvas, size / 2 + 1, 1, color); | ||
}, 700); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 - 1, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelHex(canvas, 1, size / 2 - 1, color); | ||
}, 200); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 - 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelHex(canvas, size - 2, size / 2 - 1, color); | ||
QUnit.start(); | ||
}, 1300); | ||
assert.pixelCloseRGBA(canvas, size / 2 + 1, 1, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelHex(canvas, size / 2 + 1, 1, color); | ||
}, 700); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 + 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
}); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 - 1, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelHex(canvas, size - 2, size / 2 - 1, color); | ||
done(); | ||
}, 1300); | ||
QUnit.test("Test circle with value = 0.5, size = 80 and custom gradient", function(assert) { | ||
var canvas = createCircle({ | ||
value: 0.5, | ||
size: 80, | ||
fill: { | ||
gradient: ['#ff327a', '#fff430', '#ff8989'] | ||
} | ||
}), | ||
size = 80; | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 + 1, 'rgba(0, 0, 0, 0.1)'); | ||
}); | ||
assert.expect(8); | ||
QUnit.stop(); | ||
QUnit.test("Test circle with value = 0.5, size = 80 and custom gradient", function(assert) { | ||
var canvas = createCircle({ | ||
value: 0.5, | ||
size: 80, | ||
fill: { | ||
gradient: ['#ff327a', '#fff430', '#ff8989'] | ||
} | ||
}), | ||
size = 80; | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 - 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 1, size / 2 - 1, '#ff3777', 0.01); | ||
}, 200); | ||
assert.expect(8); | ||
var done = assert.async(); | ||
assert.pixelCloseRGBA(canvas, size / 2, 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, size / 2, 1, '#fff330', 0.01); | ||
}, 700); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 - 1, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 1, size / 2 - 1, '#ff3777'); | ||
}, 200); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 - 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, size - 2, size / 2 - 1, '#ff8c86', 0.01); | ||
QUnit.start(); | ||
}, 1300); | ||
assert.pixelCloseRGBA(canvas, size / 2, 1, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, size / 2, 1, '#fff330'); | ||
}, 700); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 + 1, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
}); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 - 1, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, size - 2, size / 2 - 1, '#ff8c86'); | ||
done(); | ||
}, 1300); | ||
QUnit.test("Test circle with value = 0.75, custom start angle and custom animation start value", function(assert) { | ||
var canvas = createCircle({ | ||
value: 0.75, | ||
startAngle: -Math.PI / 4, | ||
animationStartValue: 0.25 | ||
}); | ||
assert.pixelCloseRGBA(canvas, 1, size / 2 + 1, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelCloseRGBA(canvas, size - 2, size / 2 + 1, 'rgba(0, 0, 0, 0.1)'); | ||
}); | ||
assert.expect(8); | ||
QUnit.stop(); | ||
QUnit.test("Test circle with value = 0.75, custom start angle and custom animation start value", function(assert) { | ||
var canvas = createCircle({ | ||
value: 0.75, | ||
startAngle: -Math.PI / 4, | ||
animationStartValue: 0.25 | ||
}); | ||
assert.pixelCloseRGBA(canvas, 80, 15, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
assert.pixelCloseHex(canvas, 85, 20, '#e0d55f', 0.01); | ||
assert.pixelCloseHex(canvas, 85, 80, '#e0d55f', 0.01); | ||
assert.pixelCloseRGBA(canvas, 80, 85, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 80, 85, '#d6d664', 0.01); | ||
}, 400); | ||
assert.expect(8); | ||
var done = assert.async(); | ||
assert.pixelCloseRGBA(canvas, 15, 20, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 15, 20, '#57e6aa', 0.01); | ||
assert.pixelCloseRGBA(canvas, 20, 15, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
QUnit.start(); | ||
}, 1300); | ||
}); | ||
assert.pixelCloseRGBA(canvas, 80, 15, 'rgba(0, 0, 0, 0.1)'); | ||
assert.pixelCloseHex(canvas, 85, 20, '#e0d55f'); | ||
assert.pixelCloseHex(canvas, 85, 80, '#e0d55f'); | ||
assert.pixelCloseRGBA(canvas, 80, 85, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 80, 85, '#d6d664'); | ||
}, 400); | ||
QUnit.asyncTest("Test circle with value = 0.5, image background and reverse", function(assert) { | ||
var urlPrefix = $('script[src*="tests.js"]').attr('src').replace(/tests\.js.*$/, ''), | ||
imageUrl = urlPrefix + 'images/circle.png', | ||
image = new Image(); | ||
assert.pixelCloseRGBA(canvas, 15, 20, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 15, 20, '#57e6aa'); | ||
assert.pixelCloseRGBA(canvas, 20, 15, 'rgba(0, 0, 0, 0.1)'); | ||
done(); | ||
}, 1300); | ||
}); | ||
assert.expect(9); | ||
image.src = imageUrl; | ||
QUnit.test("Test circle with value = 0.5, image background and reverse", function(assert) { | ||
var urlPrefix = $('script[src*="tests.js"]').attr('src').replace(/tests\.js.*$/, ''), | ||
imageUrl = urlPrefix + 'images/circle.png', | ||
image = new Image(); | ||
$(image).load(function() { | ||
var canvas = createCircle({ | ||
value: 0.5, | ||
thickness: 20, | ||
fill: {image: image}, | ||
reverse: true | ||
}); | ||
assert.expect(9); | ||
var done = assert.async(); | ||
image.src = imageUrl; | ||
assert.pixelRGBA(canvas, 21, 49, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelRGBA(canvas, 78, 49, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelRGBA(canvas, 49, 77, 'rgba(0, 0, 0, 0)'); | ||
$(image).on('load', function() { | ||
var canvas = createCircle({ | ||
value: 0.5, | ||
thickness: 20, | ||
fill: {image: image}, | ||
reverse: true | ||
}); | ||
assert.pixelCloseRGBA(canvas, 17, 51, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 17, 51, '#00f7ff', 0.01); | ||
}, 400); | ||
assert.pixelRGBA(canvas, 21, 49, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelRGBA(canvas, 78, 49, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelRGBA(canvas, 49, 77, 'rgba(0, 0, 0, 0)'); | ||
assert.pixelCloseRGBA(canvas, 49, 81, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 49, 81, '#7700ff', 0.01); | ||
}, 700); | ||
assert.pixelCloseRGBA(canvas, 17, 51, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 17, 51, '#00f7ff'); | ||
}, 400); | ||
assert.pixelCloseRGBA(canvas, 81, 51, 'rgba(0, 0, 0, 0.1)', 0.01); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 81, 51, '#ff0008', 0.01); | ||
QUnit.start(); | ||
}, 1400); | ||
}); | ||
}); | ||
} else { | ||
QUnit.test("Your browser doesn't support Canvas", function(assert) { | ||
assert.ok(true, "That's fine"); | ||
}); | ||
} | ||
assert.pixelCloseRGBA(canvas, 49, 81, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 49, 81, '#7700ff'); | ||
}, 700); | ||
// Utilities | ||
function createCircle(cfg) { | ||
var output = $('#qunit-fixture'); | ||
if (!output[0]) | ||
output = $('body'); | ||
var el = $('<span>').appendTo(output).circleProgress(cfg); | ||
return el.circleProgress('widget'); | ||
} | ||
assert.pixelCloseRGBA(canvas, 81, 51, 'rgba(0, 0, 0, 0.1)'); | ||
setTimeout(function() { | ||
assert.pixelCloseHex(canvas, 81, 51, '#ff0008'); | ||
done(); | ||
}, 1400); | ||
}); | ||
}); | ||
QUnit.test("Test correct rendering on Retina displays", function(assert) { | ||
/** | ||
* Mock devicePixelRatio | ||
*/ | ||
window.devicePixelRatio = 2; | ||
var canvas = createCircle({ | ||
value: 0.75, | ||
size: 50 | ||
}); | ||
assert.equal(50, $(canvas).width()); | ||
assert.equal(100, canvas.width); | ||
}); | ||
QUnit.test("Test correct rendering on regular pixel density", function(assert) { | ||
/** | ||
* Mock devicePixelRatio | ||
*/ | ||
window.devicePixelRatio = 1; | ||
var canvas = createCircle({ | ||
value: 0.75, | ||
size: 50 | ||
}); | ||
assert.equal(50, $(canvas).height()); | ||
assert.equal(50, canvas.width); | ||
}); | ||
})(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
1037
236
0
56958
1
8
18
2
1
+ Addedjquery@*
+ Addedjquery@3.7.1(transitive)