cannot get collision detection on ball to floor array? - html5-canvas

I can't seem to get collision detection to work with the floor array? I seem to be having the issue in the ballMovement function.
The ball falls straight through the rectangles made by the array.
Floor Array (issue)
Collision Detection
<html lang=en>
<head>
<meta charset=utf-8>
<title>Javascript gravity</title>
<link rel="stylesheet" href="game.css">
</head>
<body onload="init()">
<script>
var canvas, ctx, container;
canvas = document.createElement( 'canvas' );
ctx = canvas.getContext("2d");
var ball;
var message = "Helloworld";
// Velocity x
var vx = 2.0;
// Velocity y - randomly set
var vy;
var gravity = 0.5;
var bounce = 0.7;
var xFriction = 0.1;
// Floor Array
var floor = new Array();
//Rectagles
Rectangle = function(x, y, w, h, color){
if (x == null || y == null || w == null || h == null){
alert("You must pass in all the veriables for a rectange: (x, y, width, height)");
var errorMsg = "The following are not provided:";
if (x == null)
errorMsg += " 'x' ";
if (y == null)
errorMsg += " 'y' ";
if (w == null)
errorMsg += " 'width' ";
if (h == null)
errorMsg += " 'height'";
alert(errorMsg);
throw new Error(errorMsg);
}
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.color = new Color();
this.Contains = function(x, y){
if (x >= this.x && x <= this.x + this.width &&
y >= this.y && y <= this.y + this.height)
return true;
else
return false;
};
this.Draw = function(ctx){
ctx.fillStyle = this.color.ToStandard();
ctx.fillRect(this.x, this.y, this.width, this.height);
}
};
//Rectangle Colors
Color = function(r, g, b, a){
this.r = 255;
this.g = 255;
this.b = 255;
this.a = 1;
if (r != null)
this.r = r;
if (g != null)
this.g = g;
if (b != null)
this.b = b;
if (a != null)
this.a = a;
this.ToStandard = function(noAlpha){
if (noAlpha == null || !noAlpha)
return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
else
return "rgb(" + this.r + "," + this.g + "," + this.b + ")";
};
};
function init(){
setupCanvas();
vy = (Math.random() * -5) + -5;
ball = {x:canvas.width / 2, y:100, radius:10, status: 0, color:"red"};
floor.push(new Rectangle(0, 480, 500, 20));
floor.push(new Rectangle(250, 350, 200, 20));
//floor.push(new Rectangle(150, 300, 20, 20));
//floor.push(new Rectangle(200, 250, 20, 20));
//floor.push(new Rectangle(250, 200, 20, 20));
//floor.push(new Rectangle(300, 150, 20, 20));
//floor.push(new Rectangle(350, 100, 20, 20));
//floor.push(new Rectangle(400, 50, 20, 20));
for (var i = 0; i < floor.length; i++)floor[i].color = new Color(0, 0, 0, 1);
}//end init method
function draw() {
ctx.clearRect(0,0,canvas.width, canvas.height);
for (var i = 0; i < floor.length; i++)
floor[i].Draw(ctx);
//display some text
ctx.fillStyle = "red";
ctx.font = "20px Arial";
ctx.fillText(message, 20,20);
//draw cirlce
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, false);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath();
ballMovement();
}
setInterval(draw, 1000/35);
function ballMovement(){
ball.x += vx;
ball.y += vy;
vy += gravity;
//If either wall is hit, change direction on x axis
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0){
vx *= -1;
}
// Ball hits the canvas floor
if (ball.y + ball.radius > canvas.height){// ||
// Re-positioning on the base
ball.y = canvas.height - ball.radius;
//bounce the ball
vy *= -bounce;
//do this otherwise, ball never stops bouncing
if(vy<0 && vy>-2.1)
vy=0;
//do this otherwise ball never stops on xaxis
if(Math.abs(vx)<1.1)
vx=0;
xF();
}
// Ball hits the rectangles
if (ball.y + ball.radius > floor.width){// ||
// Re-positioning on the base
ball.y = floor.height - ball.radius;
//bounce the ball
vy *= -bounce;
//do this otherwise, ball never stops bouncing
if(vy<0 && vy>-2.1)
vy=0;
//do this otherwise ball never stops on xaxis
if(Math.abs(vx)<1.1)
vx=0;
xF();
}
}
function xF(){
if(vx>0)
vx = vx - xFriction;
if(vx<0)
vx = vx + xFriction;
}
function setupCanvas() {//setup canvas
container = document.createElement( 'div' );
container.className = "container";
canvas.width = 500;
canvas.height = 500;
document.body.appendChild( container );
container.appendChild(canvas);
ctx.strokeStyle = "red";
ctx.lineWidth =1;
}
</script>
</body>
</html>

There are a couple of mistakes:
You compare the ball position on Y axis ball.y to the bar width floor.width, it may be a typing/editing mistake.
You should replace floor.width by floor.y to check whether the ball hits the bar when it falls.
But floor is an Array of Rectangle, it includes the bar and potential bricks to break, if I guess it right. So you need to loop through floor before checking, otherwise floor.height equals undefined.
for (let i = 0; i < floor.length; i += 1) {
const rect = floor[i];
// Ball hits the rectangle
if (ball.y + ball.radius > rect.y) {
// ...
}
}
Then, floor isn't an appropriate name for an Array which doesn't contain any 'floor'.
Finally, add a condition to handle the ball X position (collisions with the bar, the bricks, etc.).
Working example:
https://jsfiddle.net/nmerinian/3sokr512/22/
Have a good day

Related

HTML5 Canvas - Add border to transparent png with drop shadow

There are two issues I'm trying to hammer through still in this canvas script to get this image the way I want it to look. The green is just for demo. Eventually it will be white with a gray drop shadow.
1) I want to add a gray drop shadow to the image after it gets it's green border.
2) I want the corners of the green border to be square, not rounded.
var c = document.getElementById("canvas"),
ctx = c.getContext("2d"),
opaqueAlpha = 255,
img = new Image();
img.onload = function(){
ctx.shadowColor = '#0f0'; // green for demo purposes
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.drawImage(img, 30, 30);
img = ctx.getImageData(0, 0, ctx.canvas.width - 1, ctx.canvas.height - 1);
// turn all non-transparent pixels to full opacity
for (var i = img.data.length; i > 0; i -= 4) {
if (img.data[i+3] > 0) {
img.data[i+3] = opaqueAlpha;
}
}
// write transformed opaque pixels back to image
ctx.putImageData(img, 0, 0);
// trying to get the img again and then apply the gray drop shadow...not working
img = ctx.getImageData(0, 0, ctx.canvas.width - 1, ctx.canvas.height - 1);
// need to add a gray shadow to the now opaque border
ctx.shadowColor = '#aaa';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.putImageData(img, 0, 0);
};
img.setAttribute('crossOrigin', '');
img.src = "https://i.ezr.io/racks/bb0e6dd421df72541a79f271fb4f1a90.png?" + new Date().getTime();
<canvas id="canvas" width="800" height="800"></canvas>
This is how I would do it:
I know the width and the height of the image (img.width, img.height)
Also I know the offset: 30. You begin to draw the image at 30 units from the origin.
Since those distinctions are looking more or less the same I need to calculate 3 points to know how to draw the upper notches.
I hope this is what you need:
var c = document.getElementById("canvas"),
cw = (canvas.width = 750),
ch = (canvas.height = 310),
ctx = c.getContext("2d"),
opaqueAlpha = 255,
img = new Image();
img.onload = function() {
let offset = 30;
let whiteBorder = 15;
ctx.drawImage(img, offset, offset);
var imgData = ctx.getImageData(0, 0, cw, ch);
var pixels = imgData.data;
//get some points
let y = offset + 5;
let index = y * imgData.width * 4;
let x1;
for (let i = 0; i < imgData.width * 4; i += 4) {
if (pixels[index + i + 3] > 0) {
//console.log(i / 4);
x1 = i / 4;
break;
}
}
let x2;
for (let i = imgData.width * 4; i > 0; i -= 4) {
if (pixels[index + i] > 0) {
//console.log(i / 4);
x2 = i / 4;
break;
}
}
let x = (offset + 5) * 4;
let y1;
for (let i = 0; i < imgData.height; i++) {
if (pixels[i * imgData.width * 4 + x] > 0) {
//console.log(i);
y1 = i;
break;
}
}
// draw the border behind the image
ctx.globalCompositeOperation='destination-over';
ctx.beginPath();
ctx.moveTo(offset, img.height + offset);
ctx.lineTo(img.width + offset, img.height + offset);
ctx.lineTo(img.width + offset, y1);
ctx.lineTo(x2, y1);
ctx.lineTo(x2, offset);
ctx.lineTo(x1, offset);
ctx.lineTo(x1, y1);
ctx.lineTo(offset, y1);
ctx.closePath();
ctx.strokeStyle = "white";
ctx.lineWidth = 25;
ctx.stroke();
};
img.setAttribute("crossOrigin", "");
img.src =
"https://i.ezr.io/racks/bb0e6dd421df72541a79f271fb4f1a90.png?" +
new Date().getTime();
canvas{
filter:drop-shadow(0px 0px 5px #333);
}
<canvas id="canvas"></canvas>

Possibly miss using if else statements in p5.js animation

I'm trying to get a line to snake down a canvas. I'm using if else statements, currently the line travels left to right, then down then right to left. But here I get stuck, it should go down again and then left to right again. not sure if I should be using functions?
Here is the pen
function setup(){
createCanvas(600,800);
background(250,180,20)
}
function draw(){
var left = 600;
var right = 800;
var speed = 3
timeline.move()
}
var timeline = {
speed : 3,
x : 20,
y : 20,
move : function(){
fill(0);
ellipse(this.x,this.y,20,20)
if(this.x<width-20){
this.x = this.x + this.speed
}
else if(this.y > 0){
this.y = this.y + this.speed}
if(this.y > 100 || this.x < 20){
this.y = this.y - this.speed
this.speed= this.speed*-1
this.x = this.x + this.speed+1}
if(this.x < 20 && this.y > 200){
this.y = this.y + this.speed
}
}
}
Thanks in advance
I would try to use some kind of points or instructions instead of hardcoding the values. It would probably get much easier to build upon. You could also probably break down the movement part into more generic functions if you want.
Here is some modified code:
function setup(){
createCanvas(600,800);
background(250,180,20)
}
var points = [
{x: 500, y:20},
{x: 500, y:80},
{x: 20, y: 80},
{x: 500, y: 300}
]
var currentPointIndex =0;
function draw(){
var left = 600;
var right = 800;
var speed = 3
timeline.move()
}
var timeline = {
speed : 5,
x : 20,
y : 20,
move : function(){
fill(0);
ellipse(this.x,this.y,20,20)
var didMove = false;
var currentPoint = points[currentPointIndex];
if(this.x < currentPoint.x - this.speed){
this.x += this.speed;
didMove = true;
}
else if(this.x > currentPoint.x + this.speed){
this.x -= this.speed;
didMove = true;
}
if(this.y < currentPoint.y - this.speed){
this.y += this.speed;
didMove = true;
}
else if(this.y > currentPoint.y + this.speed){
this.y -= this.speed;
didMove = true;
}
if(!didMove && currentPointIndex < points.length){
currentPointIndex++;
}
}
}
It's quick and dirty, but shows the general idea. Hope it is helpful!

Setting background image in canvas animation

I need to set background image for this canvas animation without affecting the animation style.
This CodePen is shown below.
var c = document.getElementById('canv');
var $ = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var grav = 0.00095;
var s = [20, 15, 10, 5];
var gravX = w / 2;
var gravY = h / 2;
var nodes;
var num = 55;
var minDist = 155;
var spr = 0.0000009;
part();
run();
//random size function
function S() {
var curr = s.length;
var cur_ = Math.floor(Math.random() * curr);
return s[cur_];
}
function part() {
nodes = [];
for (var i = 0; i < num; i++) {
var node = {
hue: Math.random()*360,
rad: S(),
x: Math.random() * w,
y: Math.random() * h,
vx: Math.random() * 8 - 4,
vy: Math.random() * 8 - 4,
upd: function() {
this.x += this.vx;
this.y += this.vy;
if (this.x > w) this.x = 0;
else if (this.x < 0) this.x = w;
if (this.y > h) this.y = 0;
else if (this.y < 0) this.y = h;
},
draw: function() {
//outer ring
var g = $.createRadialGradient(this.x, this.y, this.rad * 2, this.x, this.y, this.rad);
g.addColorStop(0,'hsla(242, 55%, 15%,.7)');
g.addColorStop(.5, 'hsla(242, 50%, 10%,.5)');
g.addColorStop(1,'hsla(242, 30%, 5%,.5)');
$.fillStyle = g;
$.beginPath();
$.arc(this.x, this.y, this.rad * 2, 0, Math.PI * 2, true);
$.fill();
$.closePath();
//inner particle
var g2 = $.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.rad);
g2.addColorStop(0, 'hsla('+this.hue+', 85%, 40%, 1)');
g2.addColorStop(.5, 'hsla('+this.hue+',95%, 50%,1)');
g2.addColorStop(1,'hsla(0,0%,0%,0)');
$.fillStyle = g2;
$.beginPath();
$.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
$.fill();
$.closePath();
}
};
nodes.push(node);
}
}
function run() {
$.globalCompositeOperation = 'source-over';
$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.fillRect(0, 0, w, h);
$.globalCompositeOperation = 'lighter';
for (i = 0; i < num; i++) {
nodes[i].upd();
nodes[i].draw();
}
for (i = 0; i < num - 1; i++) {
var n1 = nodes[i];
for (var j = i + 1; j < num; j++) {
var n2 = nodes[j];
Spr(n1, n2);
}
Grav(n1);
}
window.requestAnimationFrame(run);
}
function Spr(na, nb) {
var dx = nb.x - na.x;
var dy = nb.y - na.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
$.lineWidth = 1;
$.beginPath();
$.strokeStyle = "hsla(217, 95%, 55%, .15)";
$.moveTo(na.x, na.y);
$.lineTo(nb.x, nb.y);
$.stroke();
$.closePath();
var ax = dx * spr;
var ay = dy * spr;
na.vx += ax;
na.vy += ay;
nb.vx -= ax;
nb.vy -= ay;
}
}
function Grav(n) {
n.vx += (gravX - n.x) * grav;
n.vy += (gravY - n.y) * grav;
};
window.addEventListener('resize', function() {
c.width = w = window.innerWidth;
c.height = h = window.innerHeight;
});
body{
width:100%;
margin:0;
overflow:hidden;
}
<canvas id='canv' ></canvas>
CSS
Just replace the beginning of the run() code to:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
...
Then move the color settings to CSS together with an image reference:
#canv {
background: hsla(242, 40%, 5%, .85) url(path/to/image.jpg);
}
Add background-size to the CSS rule if needed. Note that since you're using different blending modes such as lighter which depends on existing content, you may not get desired result as it will blend with an empty canvas and not a solid - the approach below should solve that in this case.
CodePen
JavaScript
As before, replace the first lines in run() but after you made sure the image you want to use has loaded, simply draw it in:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
If your image contains transparency you also need to clear the canvas first:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
CodePen

Changing shapes using addEventListener in HTML5

I'm trying to change the shape of the particles in this script so that every time you click, the shapes change to a random shape. The shapes I want to do are circles (which they already are), squares, triangles, pentagons, and a four-leaf clover shape without the stem. I want to use the addEventListener method, but i have no idea where to even start with that. Thanks in advance, and here's the code I have so far:
http://jsfiddle.net/eampkcrr/
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var xCirc;
var yCirc;
var rCirc;
var animate = true;
canvas.width = width;
canvas.height = height;
makeParticles();
makeShapes();
function makeParticles() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
particles = [];
for (var i = 0; i < 3000; i++){
particles.push(new Particle());
}
}
function makeShapes() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
shapes = [];
shapes.push(new Circle());
}
function Circle() {
var r1 = 150;
var r2 = 1000;
var gradient1 = context.createRadialGradient(width/2, height/2, r1, width/2, height/2, r2);
gradient1.addColorStop(0.2, "yellow");
gradient1.addColorStop(0.8, "purple");
context.fillStyle = gradient1;
context.fillRect(0, 0, canvas.width, canvas.height);
var gradient2 = context.createRadialGradient(width/2, height/2, 120, width/2, height/2, 150);
gradient2.addColorStop(0, "black");
gradient2.addColorStop(.75, "black");
gradient2.addColorStop(1, "orange");
context.beginPath();
context.arc(width/2, height/2, 150, 0, 2 * Math.PI, true);
context.fillStyle = gradient2;
context.fill();
}
function start() {
if(animate){
window.requestAnimationFrame(start);
}
draw();
moveParticles();
}
function Particle() {
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
}
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
var p = particles[i];
xP = (xCenter - p.x) * (canvas.width/p.z);
xP += xCenter;
yP = (yCenter - p.y) * (canvas.width/p.z);
yP += yCenter;
rP = (canvas.width/p.z);
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fillStyle = p.color;
context.fill();
xCirc -= p.x;
yCirc -= p.y;
}
}
function moveParticles() {
for (var j = 0; j < particles.length; j++){
var p = particles[j];
p.z -= 2;
if (p.z <= 0){
p.z = canvas.width;
}
}
}
start();
You can listen for click events on the canvas like this:
canvas.onclick=function(){ ... }
To change the particle shape, you can add a draw method to Particle that draws the appropriate shape based on a particle's this.particleType.
function Particle(particleType) {
this.particleType=particleType;
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
this.draw=function(){
// update position
var xP = (xCenter - this.x) * (canvas.width/this.z);
xP += xCenter;
var yP = (yCenter - this.y) * (canvas.width/this.z);
yP += yCenter;
var rP = (canvas.width/this.z);
// set fillStyle
context.fillStyle = this.color;
// draw on context based on the particle's current shape
switch (this.particleType){
case 'circle':
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fill();
break;
case 'square':
context.fillRect(xP-rP, yP-rP, rP*2, rP*2);
break;
}
// update
xCirc -= this.x;
yCirc -= this.y;
}
}
Then your external function draw simply requests each particle to draw itself:
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
// request each particle to draw itself
var p = particles[i].draw();
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var xCirc;
var yCirc;
var rCirc;
var xCenter,yCenter;
var animate = true;
var pTypes=['circle','square'];
var pTypeIndex=0;
canvas.width = width;
canvas.height = height;
makeParticles();
makeShapes();
canvas.onclick=function(){
pTypeIndex++;
if(pTypeIndex>pTypes.length-1){pTypeIndex=0;}
var pType=pTypes[pTypeIndex];
for(var i=0;i<particles.length;i++){
particles[i].particleType=pType;
}
};
start();
function makeParticles() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
particles = [];
for (var i = 0; i < 500; i++){
particles.push(new Particle(pTypes[pTypeIndex]));
}
}
function makeShapes() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
shapes = [];
shapes.push(new Circle());
}
function Circle() {
var r1 = 150;
var r2 = 1000;
var gradient1 = context.createRadialGradient(width/2, height/2, r1, width/2, height/2, r2);
gradient1.addColorStop(0.2, "yellow");
gradient1.addColorStop(0.8, "purple");
context.fillStyle = gradient1;
context.fillRect(0, 0, canvas.width, canvas.height);
var gradient2 = context.createRadialGradient(width/2, height/2, 120, width/2, height/2, 150);
gradient2.addColorStop(0, "black");
gradient2.addColorStop(.75, "black");
gradient2.addColorStop(1, "orange");
context.beginPath();
context.arc(width/2, height/2, 150, 0, 2 * Math.PI, true);
context.fillStyle = gradient2;
context.fill();
}
function start() {
if(animate){
window.requestAnimationFrame(start);
}
draw();
moveParticles();
}
function Particle(particleType) {
this.particleType=particleType;
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
this.draw=function(){
// update position
var xP = (xCenter - this.x) * (canvas.width/this.z);
xP += xCenter;
var yP = (yCenter - this.y) * (canvas.width/this.z);
yP += yCenter;
var rP = (canvas.width/this.z);
// set fillStyle
context.fillStyle = this.color;
// draw on context
switch (this.particleType){
case 'circle':
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fill();
break;
case 'square':
context.fillRect(xP-rP, yP-rP, rP*2, rP*2);
break;
}
// update
xCirc -= this.x;
yCirc -= this.y;
}
}
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
var p = particles[i].draw();
}
}
function moveParticles() {
for (var j = 0; j < particles.length; j++){
var p = particles[j];
p.z -= 2;
if (p.z <= 0){
p.z = canvas.width;
}
}
}
<h4>Click to change shapes.</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Fixed "yip" in circle <--> square conversion -- Thanks #Kaiido ]

How do i bind onclick event to piechart segment?

How do i bind onclick event to piechart segment?
https://github.com/sauminkirve/HTML5/blob/master/PieChart/piechart.html
A pie chart segment is really a wedge. You have several ways to hit-test a wedge.
One way is the math way:
Test if the mouse is within the radius of a circle created by the wedges.
If the radius test is true, then calculate the angle of the mouse versus the circle's centerpoint.
Compare that angle to each wedge. If the angle is between the starting and ending angle of a specific wedge's arc, then the mouse is inside that wedge.
Another way is to use canvas's built in path hit-testing method: isPointInPath
Redefine one wedge. There's no need to actually stroke or fill that wedge. Just do the commands from beginPath to closePath.
Use context.isPointInPath(mouseX,mouseY) to hit-test if the mouse is inside that wedge.
If isPointInPath returns true, you've discovered the wedge under the mouse. If not, then redefine & hit-test each of the other wedges.
Here's something I coded a while back that hit-tests the wedges of a pie chart when hovering and moves the wedge out of the pie when a wedge is clicked.
It uses the isPointInPath method to do the hit-testing:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.lineJoin = "round";
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
function Wedge(cx, cy, radius, startAngleDeg, endAngleDeg, fill, stroke, linewidth) {
this.cx = cx;
this.cy = cy;
this.radius = radius;
this.startAngle = startAngleDeg * Math.PI / 180;
this.endAngle = endAngleDeg * Math.PI / 180;
this.fill = fill;
this.stroke = stroke;
this.lineWidth = linewidth;
this.offsetX = 0;
this.offsetY = 0;
this.rr = radius * radius;
this.centerX = cx;
this.centerY = cy;
this.midAngle = this.startAngle + (this.endAngle - this.startAngle) / 2;
this.offsetDistance = 15;
this.explodeX = this.offsetDistance * Math.cos(this.midAngle);
this.explodeY = this.offsetDistance * Math.sin(this.midAngle);
this.isExploded = false;
};
Wedge.prototype.draw = function(fill, stroke) {
this.define();
this.fillStroke(fill, stroke);
ctx.beginPath();
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.lineWidth = 0.50;
ctx.stroke();
}
Wedge.prototype.fillStroke = function(fill, stroke) {
ctx.fillStyle = fill || this.fill;
ctx.fill();
ctx.strokeStyle = stroke, this.stroke;
ctx.lineWidth = this.lineWidth;
ctx.stroke();
}
Wedge.prototype.define = function() {
var x = this.cx + this.offsetX;
var y = this.cy + this.offsetY;
ctx.beginPath();
ctx.arc(x, y, this.radius, this.startAngle, this.endAngle);
ctx.lineTo(x, y);
ctx.closePath();
}
Wedge.prototype.ptAtAngle = function(radianAngle) {
var xx = (this.cx + this.offsetX) + this.radius * Math.cos(radianAngle);
var yy = (this.cy + this.offsetY) + this.radius * Math.sin(radianAngle);
return ({
x: x,
y: y
});
}
Wedge.prototype.explode = function(isExploded) {
this.isExploded = isExploded;
this.offsetX = isExploded ? this.explodeX : 0;
this.offsetY = isExploded ? this.explodeY : 0;
this.draw();
}
Wedge.prototype.isPointInside = function(x, y) {
var dx = x - (this.cx + this.offsetX);
var dy = y - (this.cy + this.offsetY);
if (dx * dx + dy * dy > this.rr) {
return (false);
}
var angle = (Math.atan2(dy, dx) + Math.PI * 2) % (Math.PI * 2);
return (angle >= this.startAngle && angle <= this.endAngle);
}
Wedge.prototype.marker = function(pos) {
ctx.beginPath();
ctx.arc(pos.x, pos.y, 3, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
}
function handleMouseDown(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
clear();
for (var i = 0; i < wedges.length; i++) {
var wedge = wedges[i].wedge;
if (wedge.isPointInside(mouseX, mouseY)) {
wedge.explode(!wedge.isExploded);
}
wedge.draw();
}
}
function handleMouseUp(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mouseup stuff here
isDown = false;
}
function handleMouseOut(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mouseOut stuff here
isDown = false;
}
function handleMouseMove(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
for (var i = 0; i < wedges.length; i++) {
var wedge = wedges[i].wedge;
if (wedge.isPointInside(mouseX, mouseY)) {
wedge.draw("black");
} else {
wedge.draw();
}
}
}
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function(e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function(e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function(e) {
handleMouseOut(e);
});
function clear() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
var PI2 = Math.PI * 2;
var cx = 150;
var cy = 150;
var r = 100;
var line = 2;
var stroke = "black";
var wedges = [];
wedges.push({
percent: 18,
fill: "red"
});
wedges.push({
percent: 30,
fill: "blue"
});
wedges.push({
percent: 25,
fill: "green"
});
wedges.push({
percent: 13,
fill: "purple"
});
wedges.push({
percent: 14,
fill: "gold"
});
var rAngle = 0;
for (var i = 0; i < wedges.length; i++) {
var wedge = wedges[i];
var angle = 360 * wedge.percent / 100;
wedge.wedge = new Wedge(cx, cy, r, rAngle, rAngle + angle, wedge.fill, "black", 1);
wedge.wedge.draw();
rAngle += angle;
}
window.onscroll = function(e) {
var BB = canvas.getBoundingClientRect();
offsetX = BB.left;
offsetY = BB.top;
}
body {
background-color: ivory;
}
#canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Hover wedge to highlight it<br>Click wedge to explode that wedge</h4>
<canvas id="canvas" width=300 height=300></canvas>

Resources