JavaScript class for creating simple charts in Canvas. Uses requestAnimationFrame for smooth animations and includes options for variable speed and multiple linear gradient stops. Includes HiDPI resize.
A Pen by Justin Perry on CodePen.
JavaScript class for creating simple charts in Canvas. Uses requestAnimationFrame for smooth animations and includes options for variable speed and multiple linear gradient stops. Includes HiDPI resize.
A Pen by Justin Perry on CodePen.
JavaScript class for creating circles on Canvas. Uses requestAnimationFrame for smooth animations and includes options for variable speed and multiple linear gradient stops. Includes HiDPI resize.
A Pen by Justin Perry on CodePen.
<body> | |
<canvas id="canvas" width="200" height="200"></canvas> | |
<div class="js-controls"> | |
<label> | |
Value | |
<input type="number" min="0" max="360" name="" placeholder="Degrees" id="pie-value" value="80" /> | |
</label> | |
</div> | |
</body> |
(function(){ | |
'use strict'; | |
var canvas = document.getElementById('canvas'), | |
context = canvas.getContext('2d'), | |
canvasHeight = canvas.height, | |
canvasWidth = canvas.width, | |
canvasXCentre = canvasWidth / 2, | |
canvasYCentre = canvasHeight / 2, | |
inputPieValue = document.querySelector('#pie-value'), | |
updateHandler, | |
circle1, | |
circle2, | |
innerCircle1, | |
innerCircle2, | |
fps = 60, | |
timer, | |
draw, | |
resizeToRetina; | |
function Circle( options ){ | |
this.options = options; | |
this.context = options.context; | |
this.radius = options.radius; | |
this.direction = options.direction || 'clockwise'; | |
this.x = options.x; | |
this.y = options.y; | |
this.startAngle = options.startAngle; | |
this.endAngle = options.endAngle; | |
this.fillStyle = options.fillStyle; | |
this.lineWidth = options.lineWidth; | |
this.strokeStyle = options.strokeStyle; | |
this.animate = options.animate || false; | |
this.animate.counter = 0; | |
this.startAngle = this._degreesToRadians( this.startAngle ); | |
} | |
Circle.prototype._draw = function(){ | |
var endAngle; | |
if( this.animate ){ | |
if( ( this.animate.counter + 1 * this.animate.speed ) < this.endAngle ){ | |
this.animate.counter += 1 * this.animate.speed; | |
} | |
else if( this.endAngle < ( this.animate.counter - 1 * this.animate.speed ) ) { | |
this.animate.counter -= 1 * this.animate.speed; | |
} | |
else{ | |
this.animate.counter = this.endAngle; | |
} | |
endAngle = this.startAngle - this._degreesToRadians( this.animate.counter ); | |
} | |
else{ | |
endAngle = this._degreesToRadians( this.endAngle ); | |
} | |
if( typeof this.strokeStyle.gradient === 'object' ) { | |
if( typeof this.strokeStyle.gradient.cache !== 'object' ) { | |
this.strokeStyle.gradient.cache = this._createGradient( this.strokeStyle.gradient.x0, this.strokeStyle.gradient.y0, this.strokeStyle.gradient.x1, this.strokeStyle.gradient.y1); | |
} | |
this.context.strokeStyle = this.strokeStyle.gradient.cache; | |
} | |
else{ | |
this.context.strokeStyle = this.strokeStyle; | |
} | |
this.context.beginPath(); | |
this.context.arc( this.x, this.y, this.radius, this.startAngle, endAngle, (this.direction === 'clockwise' )); | |
this.context.lineWidth = this.lineWidth; | |
this.context.stroke(); | |
this.context.fillStyle = this.fillStyle; | |
this.context.fill(); | |
}; | |
Circle.prototype._createGradient = function( x0, y0, x1, y1 ){ | |
var gradient = this.context.createLinearGradient( x0, y0, x1, y1 ), | |
stops = this.strokeStyle.gradient.stops, | |
len = stops.length, | |
i; | |
for( i = 0; i < len; i++){ | |
gradient.addColorStop( stops[i].start, stops[i].color ); | |
} | |
return gradient; | |
}; | |
Circle.prototype._degreesToRadians = function( degrees ){ | |
return degrees * Math.PI / 180; | |
}; | |
Circle.prototype.changeDirection = function(){ | |
this.direction = ( this.direction === 'clockwise' )? 'anticlockwise' : 'clockwise'; | |
}; | |
Circle.prototype.getStartAngle = function(){ | |
return this.startAngle; | |
}; | |
Circle.prototype.getEndAngle = function(){ | |
return this.endAngle; | |
}; | |
Circle.prototype.updateStartAngle = function( angle ){ | |
this.startAngle = parseInt( angle || this.startAngle, 10 ); | |
}; | |
Circle.prototype.updateEndAngle = function( angle ){ | |
this.endAngle = parseInt( angle || this.endAngle, 10 ); | |
}; | |
// Gradient Circle | |
circle1 = new Circle({ | |
context: context, | |
radius: 75, | |
x: canvasXCentre, | |
y: canvasYCentre, | |
lineWidth: 15, | |
fillStyle: 'none', | |
strokeStyle: { | |
gradient: { | |
x0: canvasXCentre - 80, | |
y0: 0, | |
x1: 0, | |
y1: canvasXCentre + 80, | |
stops : [ | |
{ | |
color: '#ebd157', | |
start: 0.25 | |
}, | |
{ | |
color: '#eb7a32', | |
start: .5 | |
}, | |
{ | |
color: '#ff3131', | |
start: .75 | |
} | |
] | |
}, | |
}, | |
startAngle : 0, | |
endAngle : 360 | |
}); | |
// Outer grey circle | |
circle2 = new Circle({ | |
context: context, | |
direction: 'anticlockwise', | |
radius: 75, | |
x: canvasXCentre, | |
y: canvasYCentre, | |
lineWidth: 15.9, | |
fillStyle: 'none', | |
strokeStyle: '#5d6e7f', | |
clip : true, | |
startAngle : -90, | |
endAngle : 360 - 280, | |
animate: { | |
speed: 3 | |
} | |
}); | |
// White inner circle | |
innerCircle1 = new Circle({ | |
context: context, | |
radius: 63, | |
x: canvasXCentre, | |
y: canvasYCentre, | |
lineWidth : 10, | |
fillStyle: 'white', | |
strokeStyle : 'white', | |
startAngle : 0, | |
endAngle : 360 | |
}); | |
// Grey small inner circle | |
innerCircle2 = new Circle({ | |
context: context, | |
radius: 69, | |
x: canvasXCentre, | |
y: canvasYCentre, | |
lineWidth : 10, | |
fillStyle: 'none', | |
strokeStyle : '#e9ecf0', | |
startAngle : 0, | |
endAngle : 360 | |
}); | |
draw = function(){ | |
setTimeout(function(){ | |
requestAnimationFrame(draw); | |
context.clearRect( 0, 0, canvas.width, canvas.height); | |
circle1._draw(); | |
innerCircle2._draw(); | |
circle2._draw(); | |
innerCircle1._draw(); | |
}, 1000 / fps ); | |
} | |
inputPieValue.addEventListener('keyup', function(e){ | |
updateHandler.call(this); | |
}); | |
inputPieValue.addEventListener('change', function(e){ | |
updateHandler.call(this); | |
}); | |
updateHandler = function(){ | |
clearTimeout(timer); | |
timer = setTimeout(function(){ | |
circle2.updateEndAngle( this.value ); | |
}.bind(this), 250); | |
}; | |
// https://github.com/component/autoscale-canvas | |
resizeToRetina = function( canvas ){ | |
var context = canvas.getContext('2d'), | |
ratio = window.devicePixelRatio || 1; | |
if ( 1 !== ratio ) { | |
canvas.style.width = canvas.width + 'px'; | |
canvas.style.height = canvas.height + 'px'; | |
canvas.width *= ratio; | |
canvas.height *= ratio; | |
context.scale(ratio, ratio); | |
} | |
return canvas; | |
}; | |
resizeToRetina( canvas ); | |
draw(); | |
// Paul Irish polyfill | |
window.requestAnimFrame = (function(){ | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
function( callback ){ | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
})(); | |
})(); |
body{ | |
background-color: #272729; | |
color: #f7f7f7; | |
font-family: Helvetica, serif; | |
margin: 10px; | |
padding: 0; | |
} | |
#canvas{ | |
display: block; | |
left: 50%; | |
margin: -150px auto 0 -100px; | |
position: absolute; | |
top: 50%; | |
} | |
.js-controls{ | |
display: block; | |
position: fixed; | |
bottom: 30px; | |
text-align: center; | |
width: 100%; | |
} |