I have multiple shapes(rects,arcs,...) and I want all of them to be around one specific circle(on border of some invisible circle).
consider center of this invisible circle is at the center of screen
(canvas.width/2,canvas.height/2) and its radius is 200
but shapes are generated inside some rectangle and I don't want this.
let canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let particlesNum = 100;
let particles = [];
let ctx = canvas.getContext('2d');
function Particle(x,y,r){
this.x = x ;
this.y = y ;
this.r = r ;
}
Particle.prototype.draw = function() {
ctx.beginPath();
ctx.fillStyle = 'red' ;
ctx.arc(this.x,this.y,this.r,0,2*Math.PI,false);
ctx.fill();
}
function generateParticels(){
let x,y,r ;
for(let i=0 ; i<particlesNum ; i++){
x = canvas.width/2+Math.cos(Math.random()*(2*Math.PI))*200;
y = canvas.height/2+Math.sin(Math.random()*(2*Math.PI))*200;
r = 1 ; //radius of each circle
particles.push(new Particle(x,y,r));
}
particles.forEach(particle=>particle.draw());
}
generateParticels();
*{
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,body{height: 100%;}
<canvas></canvas>
The problem is that you generate different random angle for X and Y separately. Use the same Random angle for both:
let canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let particlesNum = 100;
let particles = [];
let ctx = canvas.getContext('2d');
function Particle(x,y,r){
this.x = x ;
this.y = y ;
this.r = r ;
}
Particle.prototype.draw = function() {
ctx.beginPath();
ctx.fillStyle = 'red' ;
ctx.arc(this.x,this.y,this.r,0,2*Math.PI,false);
ctx.fill();
}
function generateParticels(){
let x, y, r, randomAngle ;
for(let i=0 ; i<particlesNum ; i++){
randomAngle = Math.random() * 2 * Math.PI;
x = canvas.width / 2 + Math.cos(randomAngle) * 200;
y = canvas.height / 2 + Math.sin(randomAngle) * 200;
r = 1 ; //radius of each circle
particles.push(new Particle(x,y,r));
}
particles.forEach(particle=>particle.draw());
}
generateParticels();
*{
box-sizing: border-box;
padding: 0;
margin: 0;
}
html,body{height: 100%;}
<canvas></canvas>
Related
What I want to do
I want to keep the canvas size full to the window.
What the problem is
I set the canvas size as stated below.
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
It worked.
But when I resized the window, the canvas size didn't change but instead kept its initial size.
What should I do to keep the canvas size full to the window without changing the balls' size?
Here is my code
const canvas = document.querySelector('#sandBox');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
class Circle {
constructor(x, y, r, c) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
}
draw() {
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
const balls = [];
for (let i = 0; i < 20; i++) {
let r = Math.floor(Math.random() * 30) + 15;
let x = Math.random() * (canvas.width - r * 2) + r;
let y = Math.random() * (canvas.height - r * 2) + r;
let c = 'rgba(255,255,255,0.2)';
balls.push(new Circle(x, y, r, c));
}
balls.forEach(ball => ball.draw());
body {
position: relative;
}
canvas {
/* width: 100vw;
height: 100vh; */
background-color: #393939;
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
<canvas id="sandBox"></canvas>
You can have a callback onresize and use that to recreate the canvas:
Each time you resize the window you'll have to redraw the canvas, unless you save it somewhere.
function init() {
const canvas = document.querySelector("#sandBox");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
class Circle {
constructor(x, y, r, c) {
this.x = x;
this.y = y;
this.r = r;
this.c = c;
}
draw() {
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
const balls = [];
for (let i = 0; i < 20; i++) {
let r = Math.floor(Math.random() * 30) + 15;
let x = Math.random() * (canvas.width - r * 2) + r;
let y = Math.random() * (canvas.height - r * 2) + r;
let c = "rgba(255,255,255,0.2)";
balls.push(new Circle(x, y, r, c));
}
balls.forEach((ball) => ball.draw());
}
window.onload = function() {
init();
};
body {
position: relative;
}
canvas {
width: 100vw;
height: 100vh;
background-color: #393939;
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
<body onresize="init()">
<canvas id="sandBox"></canvas>
</body>
I'm making an AI navigation system based on polar coordinates. The purpose is to move an actor to a position, while also moving away from a possible obstacle on its path.
The code works perfectly most of the time but after testing, I discovered this: when the player, obstacle and actor are all perfectly aligned in either the X or Y direction or diagonally, the actor gets stuck in the obstacle. It's mostly noticeable when the player is "hugging" a wall because the actor's movement vector is clipped by the walls, making them aligned.
Click the buttons in the snippet to see what I'm on about.
Is there a way to prevent this?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let playerX = 100;
let playerY = 200;
let obstacleX = 200;
let obstacleY = 200;
let actorX = 300;
let actorY = 201;
function loop() {
// Wall clipping
if (actorX > 490) {
actorX = 490;
} else if (actorX < 10) {
actorX = 10;
}
if (actorY > 490) {
actorY = 490;
} else if (actorY < 10) {
actorY = 10;
}
// Vector between player and actor
let vectorPlayerX = playerX - actorX;
let vectorPlayerY = playerY - actorY;
// Vector between obstacle and actor
let vectorObstacleX = obstacleX - actorX;
let vectorObstacleY = obstacleY - actorY;
// Where to move, defaults to player's position
const anglePlayer = Math.atan2(vectorPlayerY, vectorPlayerX);
let moveAngle = anglePlayer;
// If near obstacle, adjust course and try to avoid it
if (Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY) < 50) {
const angleObstacle = Math.atan2(vectorObstacleY, vectorObstacleX);
moveAngle += anglePlayer - angleObstacle;
}
// Move the vector to desired location
actorX += Math.cos(moveAngle);
actorY += Math.sin(moveAngle);
//Drawing
ctx.clearRect(0, 0, 500, 500);
ctx.beginPath();
ctx.fillStyle = "gray";
ctx.arc(actorX, actorY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "orange";
ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(playerX, playerY, 10, 0, Math.PI * 2, true);
ctx.fill();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function nonAligned() {
playerX = 100;
playerY = 200;
obstacleX = 200;
obstacleY = 200;
actorX = 300;
actorY = 201;
}
function alignedY() {
playerX = 100;
playerY = 490;
obstacleX = 200;
obstacleY = 490;
actorX = 300;
actorY = 490;
}
function alignedBoth() {
playerX = 200;
playerY = 200;
obstacleX = 300;
obstacleY = 300;
actorX = 400;
actorY = 400;
}
#options {
position: fixed;
top: 5px;
left: 5px;
}
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<div id="options">
<button onclick="nonAligned()">Spawn non-aligned</button>
<button onclick="alignedY()">Spawn Y aligned</button>
<button onclick="alignedBoth()">Spawn diagonally aligned</button>
</div>
</body>
</html>
If the angle to the player and the obstacle are the same then we continue the course, as the variables cancel each other out.
moveAngle += anglePlayer - angleObstacle;
If anglePlayer is 117 and angleObstacle is 117 and your moveAngle is 117 you get
117 + 117 -117 = 117 ...
You might want something like this (pseudo code)
moveAngle += anglePlayer + random(90)-45;
Or if hitting an obstacle move left or right
moveAngle += anglePlayer - 90;
if (random(2)==1 moveAngle += 180
The issue is indeed that moveAngle is unchanged when it is pointed directly at the obstacle. A small modification checks whether the moveAngle is clockwise or counter-clockwise from the obstacle, and veers further away (note: my code breaks the wall hugging and behaves badly in the "aligned in Y" case for that reason, which is fixable but I don't care to):
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let playerX = 100;
let playerY = 200;
let obstacleX = 200;
let obstacleY = 200;
let actorX = 300;
let actorY = 201;
function loop() {
// Wall clipping
if (actorX > 490) {
actorX = 490;
} else if (actorX < 10) {
actorX = 10;
}
if (actorY > 490) {
actorY = 490;
} else if (actorY < 10) {
actorY = 10;
}
// Vector between player and actor
let vectorPlayerX = playerX - actorX;
let vectorPlayerY = playerY - actorY;
// Vector between obstacle and actor
let vectorObstacleX = obstacleX - actorX;
let vectorObstacleY = obstacleY - actorY;
// Where to move, defaults to player's position
const anglePlayer = Math.atan2(vectorPlayerY, vectorPlayerX);
let moveAngle = anglePlayer;
// If near obstacle, adjust course and try to avoid it
obs_distance = Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY);
if (obs_distance < 100) {
const angleObstacle = Math.atan2(vectorObstacleY, vectorObstacleX);
delta = Math.PI/2.0;
if (obs_distance > 100.0/32.0) { delta = (100.0/32.0)*Math.PI/obs_distance; }
cross = Math.sin(moveAngle-angleObstacle);
if (cross>0) { moveAngle += delta; }
if (cross<0) { moveAngle -= delta; }
if (cross==0) {
if (Math.random(2)==1) {
moveAngle += delta;
} else {
moveAngle -= delta;
}
}
}
// Move the vector to desired location
actorX += Math.cos(moveAngle);
actorY += Math.sin(moveAngle);
//Drawing
ctx.clearRect(0, 0, 500, 500);
ctx.beginPath();
ctx.fillStyle = "gray";
ctx.arc(actorX, actorY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "orange";
ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(playerX, playerY, 10, 0, Math.PI * 2, true);
ctx.fill();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function nonAligned() {
playerX = 100;
playerY = 200;
obstacleX = 200;
obstacleY = 200;
actorX = 300;
actorY = 201;
}
function alignedY() {
playerX = 100;
playerY = 490;
obstacleX = 200;
obstacleY = 490;
actorX = 300;
actorY = 490;
}
function alignedBoth() {
playerX = 200;
playerY = 200;
obstacleX = 300;
obstacleY = 300;
actorX = 400;
actorY = 400;
}
#options {
position: fixed;
top: 5px;
left: 5px;
}
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<div id="options">
<button onclick="nonAligned()">Spawn non-aligned</button>
<button onclick="alignedY()">Spawn Y aligned</button>
<button onclick="alignedBoth()">Spawn diagonally aligned</button>
</div>
</body>
</html>
what exactly I want to achieve is
to draw objects on canvas and
on mouseover display relevant data in tooltip.
here you can view the code.
var canvasBack;
var canvasLabel;
var canvasDraw;
var ctxBack;
var ctxLabel;
var ctxDraw;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
var canWidth;
var canHeight;
var scaleParameter;
var radius;
var xVertex;
var yVertex;
var hotspots = [];
// initialization on loading of canvas
$('canvas').ready(function() {
init();
});
// initialization function used for binding events, and inital logic implemented.
function init() {
scaleParameter = 1;
canvasBack = document.getElementById('backSpace');
canvasLabel = document.getElementById('layerCanvas');
canvasDraw = document.getElementById('drawSpace');
ctxBack = canvasBack.getContext('2d');
ctxLabel = canvasLabel.getContext('2d');
ctxDraw = canvasDraw.getContext('2d');
canWidth = parseInt($(canvasBack).attr('width'));
canHeight = parseInt($(canvasBack).attr('height'));
var canvasx = $(canvasBack).offset().left;
var canvasy = $(canvasBack).offset().top
var mousedown = false;
//Mousedown
$('canvas').on('mousedown', function(e) {
$('#drawSpace').css('display', 'block');
last_mousex = mousex = parseInt(e.clientX - canvasx);
last_mousey = mousey = parseInt(e.clientY - canvasy);
mousedown = true;
});
//Mouseup
$('canvas').on('mouseup', function(e) {
hotspots.push({
x: xVertex,
y: yVertex,
radius: radius,
tip: 'You are over ' + mousex + ',' + mousey
});
let cw = canvasBack.width;
let ch = canvasBack.height;
ctxBack.drawImage(canvasDraw, 0, 0, cw, ch);
$('#drawSpace').css('display', 'none');
mousedown = false;
});
//Mousemove
$('canvas').on('mousemove', function(e) {
mousex = parseInt(e.clientX - canvasx);
mousey = parseInt(e.clientY - canvasy);
if (mousedown) {
// draw(mousedown);
drawEllipse(last_mousex, last_mousey, mousex, mousey);
} else {
hoverTooltip();
}
});
}
function drawEllipse(x1, y1, x2, y2) {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
ctxDraw.clearRect(0, 0, cw, ch);
var radiusX = x2 - x1,
radiusY = y2 - y1,
centerX = x1 + radiusX,
centerY = y1 + radiusY,
step = 0.01,
a = step,
pi2 = Math.PI * 2 - step;
radius = Math.sqrt(radiusX * radiusX + radiusY * radiusY) / 2;
ctxDraw.beginPath();
ctxDraw.arc(centerX, centerY, radius, 0, 2 * Math.PI, true);
ctxDraw.closePath();
ctxDraw.fillStyle = 'green';
ctxDraw.fill();
ctxDraw.strokeStyle = '#000';
ctxDraw.stroke();
xVertex = centerX;
yVertex = centerY;
}
// tooltip show on hover over objects
function hoverTooltip() {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
for (var i = 0; i < hotspots.length; i++) {
var h = hotspots[i];
var dx = mousex - h.x;
var dy = mousey - h.y;
if (dx * dx + dy * dy < h.radius * h.radius) {
$('#console').text(h.tip);
ctxLabel.clearRect(0, 0, cw, ch);
ctxLabel.fillText(h.tip, mousex + leftScroll, mousey + topScroll);
} else {
ctxLabel.clearRect(0, 0, cw, ch);
}
}
}
#scrollParent {
width: 644px;
height: 364px;
overflow: auto;
position: relative;
}
#scrollParent>canvas {
position: absolute;
left: 0;
top: 0;
border: 1px solid #ababab;
}
#backSpace {
z-index: 0;
}
#drawSpace {
display: none;
z-index: 1;
}
#layerCanvas {
z-index: 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="scrollParent">
<!-- actual canvas that is visible -->
<canvas width="640" height="360" id="backSpace"></canvas>
<!-- canvas used for drawing new objects -->
<canvas width="640" height="360" id="drawSpace"></canvas>
<!-- canvas used to display tooltip -->
<canvas width="640" height="360" id="layerCanvas"></canvas>
</div>
<div id="console"></div>
</div>
actual problem is in the bellow image, tooltip worked fine when the 1st object was drawn, but once the second object was drawn tooltip worked only for the second one, not for previously drawn objects.
what is causing this issue, and how to fix it ?
Removing else will not remove the label when leaving the elipse.
You need to exit the loop once you found the correct elipse from the array using break.
function hoverTooltip() {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
for (var i = 0; i < hotspots.length; i++) {
var h = hotspots[i];
var dx = mousex - h.x;
var dy = mousey - h.y;
if (dx * dx + dy * dy < h.radius * h.radius) {
$('#console').text(h.tip);
ctxLabel.clearRect(0, 0, cw, ch);
ctxLabel.fillText(h.tip, mousex + leftScroll, mousey + topScroll);
break; // exit the loop
} else {
ctxLabel.clearRect(0, 0, cw, ch);
}
}
}
UPDATE
I figured that if you draw two objects over each other, it will behave poorly. Try this instead. It will display information of the latest drawn spot.
function hoverTooltip() {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
var spots = hotspots.filter((h) => {
var dx = mousex - h.x;
var dy = mousey - h.y;
return (dx * dx + dy * dy < h.radius * h.radius);
})
if (spots.length > 0) {
var h = spots[spots.length - 1]; // latest drawn spot
$('#console').text(h.tip);
ctxLabel.clearRect(0, 0, cw, ch);
ctxLabel.fillText(h.tip, mousex + leftScroll, mousey + topScroll);
}
else
{
ctxLabel.clearRect(0, 0, cw, ch);
}
}
I see there are already a few answers. This is mine:
In order to be able to show the label for every circle on hover you need to save all your circles in am array: the circles array. I'm using the ctx.isPointInPath() method to know if the mouse is over the circle, and if it is I paint the label.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 640;
let ch = canvas.height = 360;
let found = false;//is a circle found?
const cText = document.querySelector("#text");
const ctxText = cText.getContext("2d");
cText.width = 640;
cText.height = 360;
ctxText.font="1em Verdana";
let drawing = false;
let circles = []
class Circle{
constructor(x,y){
this.x = x;
this.y = y;
this.r = 0;
}
updateR(m) {
this.r = dist(this,m);
}
draw(){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,2*Math.PI);
}
paint(){
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
this.draw();
ctx.stroke();
ctx.fill();
}
label(m){
this.draw();
if (ctx.isPointInPath(m.x, m.y)) {
ctx.beginPath();
ctx.arc(this.x, this.y, 4, 0, 2 * Math.PI);
ctxText.fillStyle = "black";
ctxText.fillText(`you are over ${this.x},${this.y}`,m.x,m.y)
found = true;
}
}
}
let m = {}// mouse
cText.addEventListener("mousedown",(e)=>{
drawing = true;
m = oMousePos(canvas, e);
let circle = new Circle(m.x,m.y)
circles.push(circle);
})
cText.addEventListener("mouseup",(e)=>{
drawing = false;
})
cText.addEventListener("mousemove",(e)=>{
m = oMousePos(canvas, e);
found = false;
if(drawing){
let circle = circles[circles.length-1];//the last circle in the circles arrey
circle.updateR(m);
}
ctx.clearRect(0,0, cw,ch);
ctxText.clearRect(0,0,cw,ch)
circles.map((c) => {c.paint();});
for(let i = circles.length-1; i >=0 ; i--){
circles[i].label(m);
if(found){break;}
}
})
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
canvas{border:1px solid;position:absolute; top:0; left:0;}
#scrollParent{position:relative;}
<div id="scrollParent">
<!-- actual canvas that is visible -->
<canvas width="640" height="360"></canvas>
<canvas width="640" height="360" id="text"></canvas>
</div>
I've updated the code in base of the comment of #HelderSepu
A SECOND UPDATE in base of a second message from #HelderSepu. He wants to see "multiple message but avoid overlapping the messages"
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 640;
let ch = canvas.height = 360;
let text = "";
const cText = document.querySelector("#text");
const ctxText = cText.getContext("2d");
cText.width = 640;
cText.height = 360;
ctxText.font="1em Verdana";
let drawing = false;
let circles = []
class Circle{
constructor(x,y){
this.x = x;
this.y = y;
this.r = 0;
}
updateR(m) {
this.r = dist(this,m);
}
draw(){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,2*Math.PI);
}
paint(){
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
this.draw();
ctx.stroke();
ctx.fill();
}
label(m){
this.draw();
if (ctx.isPointInPath(m.x, m.y)) {
this.text = `[${this.x},${this.y}]`
}else{
this.text = "";
}
}
}
let m = {}// mouse
cText.addEventListener("mousedown",(e)=>{
drawing = true;
m = oMousePos(canvas, e);
let circle = new Circle(m.x,m.y)
circles.push(circle);
})
cText.addEventListener("mouseup",(e)=>{
drawing = false;
})
cText.addEventListener("mousemove",(e)=>{
m = oMousePos(canvas, e);
if(drawing){
let circle = circles[circles.length-1];//the last circle in the circles arrey
circle.updateR(m);
}
ctx.clearRect(0,0, cw,ch);
ctxText.clearRect(0,0,cw,ch);
text="";
circles.map((c) => {c.paint();c.label(m);});
circles.map((c) => {text += c.text;});
ctxText.fillStyle = "black";
ctxText.fillText(text,m.x,m.y)
})
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
canvas{border:1px solid;position:absolute; top:0; left:0;}
#scrollParent{position:relati
<div id="scrollParent">
<!-- actual canvas that is visible -->
<canvas width="640" height="360"></canvas>
<canvas width="640" height="360" id="text"></canvas>
<div id="console"></div>
</div>
I would like to remove the balls already generated in the canvas on the click and decrease the counter on the bottom, but my function does not work. Here is my code concerning the part of the ball removal.
Is it possible to use a div to get the same result and to facilitate the removal of the balls? thank you
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
below I enclose my complete code
// GLOBAL VARIBLES
var gravity = 4;
var forceFactor = 0.3; //0.3 0.5
var mouseDown = false;
var balls = []; //hold all the balls
var mousePos = []; //hold the positions of the mouse
var ctx = canvas.getContext('2d');
var heightBrw = canvas.height = window.innerHeight;
var widthBrw = canvas.width = window.innerWidth;
var bounciness = 1; //0.9
window.onload = function gameCore() {
function onMouseDown(event) {
mouseDown = true;
mousePos["downX"] = event.pageX;
mousePos["downY"] = event.pageY;
}
canvas.onclick = function onMouseUp(event) {
mouseDown = false;
balls.push(new ball(mousePos["downX"], mousePos["downY"], (event.pageX - mousePos["downX"]) * forceFactor,
(event.pageY - mousePos["downY"]) * forceFactor, 5 + (Math.random() * 10), bounciness, random_color()));
ball
}
function onMouseMove(event) {
mousePos['currentX'] = event.pageX;
mousePos['currentY'] = event.pageY;
}
function resizeWindow(event) {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function reduceBounciness(event) {
if (bounciness == 1) {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 0.9;
document.getElementById("btn-bounciness").value = "⤽ Bounciness";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].b = bounciness = 1;
document.getElementById("btn-bounciness").value = " ⤼ Bounciness";
}
}
return bounciness;
}
function reduceSpeed(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 20 + c;
balls[i].vy = velocityY = 20 + c;
}
}
function speedUp(event) {
for (var i = 0; i < balls.length; i++) {
balls[i].vx = velocityX = 120 + c;
balls[i].vy = velocityY = 120 + c;
}
}
function stopGravity(event) {
if (gravity == 4) {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 0;
balls[i].vx = velocityX = 0;
balls[i].vy = velocityY = 0;
document.getElementById("btn-gravity").value = "►";
}
} else {
for (var i = 0; i < balls.length; i++) {
balls[i].g = gravity = 4;
balls[i].vx = velocityX = 100;
balls[i].vy = velocityY = 100;
document.getElementById("btn-gravity").value = "◾";
}
}
}
ball.onclick = function removeBalls(event) {
var x = event.clientX;
var y = event.clientY;
ctx.clearRect(x, y, 100, 50);
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length - 1, 10, canvas.height - 10);
}
document.getElementById("btn-gravity").addEventListener("click", stopGravity);
document.getElementById("btn-speed-up").addEventListener("click", speedUp);
document.getElementById("btn-speed-down").addEventListener("click", reduceSpeed);
document.getElementById("btn-bounciness").addEventListener("click", reduceBounciness);
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
window.addEventListener('resize', resizeWindow);
}
// GRAPHICS CODE
function circle(x, y, r, c) { // x position, y position, r radius, c color
//draw a circle
ctx.beginPath(); //approfondire
ctx.arc(x, y, r, 0, Math.PI * 2, true);
ctx.closePath();
//fill
ctx.fillStyle = c;
ctx.fill();
//stroke
ctx.lineWidth = r * 0.1; //border of the ball radius * 0.1
ctx.strokeStyle = "#000000"; //color of the border
ctx.stroke();
}
function random_color() {
var letter = "0123456789ABCDEF".split(""); //exadecimal value for the colors
var color = "#"; //all the exadecimal colors starts with #
for (var i = 0; i < 6; i++) {
color = color + letter[Math.round(Math.random() * 15)];
}
return color;
}
function selectDirection(fromx, fromy, tox, toy) {
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.moveTo(tox, toy);
}
//per velocità invariata rimuovere bounciness
function draw_ball() {
this.vy = this.vy + gravity * 0.1; // v = a * t
this.x = this.x + this.vx * 0.1; // s = v * t
this.y = this.y + this.vy * 0.1;
if (this.x + this.r > canvas.width) {
this.x = canvas.width - this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.x - this.r < 0) {
this.x = this.r;
this.vx = this.vx * -1 * this.b;
}
if (this.y + this.r > canvas.height) {
this.y = canvas.height - this.r;
this.vy = this.vy * -1 * this.b;
}
if (this.y - this.r < 0) {
this.y = this.r;
this.vy = this.vy * 1 * this.b;
}
circle(this.x, this.y, this.r, this.c);
}
// OBJECTS
function ball(positionX, positionY, velosityX, velosityY, radius, bounciness, color, gravity) {
this.x = positionX;
this.y = positionY;
this.vx = velosityX;
this.vy = velosityY;
this.r = radius;
this.b = bounciness;
this.c = color;
this.g = gravity;
this.draw = draw_ball;
}
// GAME LOOP
function game_loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (mouseDown == true) {
selectDirection(mousePos['downX'], mousePos['downY'], mousePos['currentX'], mousePos['currentY']);
}
for (var i = 0; i < balls.length; i++) {
balls[i].draw();
}
ctx.fillStyle = "#000000";
ctx.font = "20px Arial";
ctx.fillText("Balls Counter: " + balls.length, 10, canvas.height - 10);
}
setInterval(game_loop, 10);
* {
margin: 0px; padding: 0px;
}
html, body {
width: 100%; height: 100%;
}
#canvas {
display: block;
height: 95%;
border: 2px solid black;
width: 98%;
margin-left: 1%;
}
#btn-speed-up, #btn-speed-down, #btn-bounciness, #btn-gravity{
padding: 0.4%;
background-color: beige;
text-align: center;
font-size: 20px;
font-weight: 700;
float: right;
margin-right: 1%;
margin-top:0.5%;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Power Balls</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<input type="button" value="⤼ Bounciness" id="btn-bounciness"></input>
<input type="button" onclick="c+=10" value="+ Speed" id="btn-speed-up"></input>
<input type="button" value="◾" id="btn-gravity"></input>
<input type="button" onclick="c+= -10" value=" - Speed" id="btn-speed-down"></input>
<script src="js/main.js"></script>
</body>
</html>
I see two problems:
ball.onclick will not get triggered, as ball it is not a DOM object. Instead, you need to work with canvas.onclick. Check whether the user clicked on a ball or on empty space to decide whether to delete or create a ball.
To delete the ball, ctx.clearRect is not sufficient. This will clear the ball from the currently drawn frame, but the object still remains in the array balls and will therefore be drawn again in the next frame via balls[i].draw();. Instead, you need to remove the clicked ball entirely from the array, e. g. by using splice.
Otherwise, nice demo! :)
This cannot be done because the canvas is an immediate mode API. All you can do is draw over the top but this is not reliable.
https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)
You will have to store each item in a separate data structure, then re-draw whenever a change occurs. In your case there is an array of balls, so you should remove the one that was clicked, before redrawing the entire array.
Each time you remove something, clear the canvas then re-draw each ball.
There are optimisations you can make if this is too slow.
I'm trying to plot a pie chart using HTML5 and Canvas.
Here below is my working example in jsfiddle.
http://jsfiddle.net/2mf8gt2c/
I need to show the values inside of the pie chart.
i.e
var myColor = ["Green","Red","Blue"];
var myData = [30,60,10];
The value should be displayed inside the pie chart. How can I achieve that?
The full code is available below.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>My Title</title>
</head>
<body>
<section>
<div>
<table width="80%" cellpadding=1 cellspacing=1 border=0>
<tr>
<td width=50%><canvas id="canvas" align="center" width="400" height="250"> This text is displayed if your browser does not support HTML5 Canvas. </canvas>
</td>
</tr>
</table>
</div>
<script type="text/javascript">
var myColor = ["Green","Red","Blue"];
var myData = [30,60,10];
function degreesToRadians(degrees) {
return (degrees * Math.PI)/180;
}
function sumTo(a, i) {
var sum = 0;
for (var j = 0; j < i; j++) {
sum += a[j];
}
return sum;
}
function getTotal(){
var myTotal = 0;
for (var j = 0; j < myData.length; j++) {
myTotal += (typeof myData[j] == 'number') ? myData[j] : 0;
}
return myTotal;
}
var drawSegmentLabel = function(canvas, context, i)
{
context.save();
var x = Math.floor(250 / 2);
var y = Math.floor(100 / 2);
var angle;
var angleD = sumTo(myData, i);
var flip = (angleD < 90 || angleD > 270) ? false : true;
context.translate(x, y);
if (flip) {
angleD = angleD-180;
context.textAlign = "left";
angle = degreesToRadians(angleD);
context.rotate(angle);
context.translate(-(x + (canvas.width * 0.5))+15, -(canvas.height * 0.05)-10);
}
else {
context.textAlign = "right";
angle = degreesToRadians(angleD);
context.rotate(angle);
}
var fontSize = Math.floor(canvas.height / 25);
context.font = fontSize + "pt Helvetica";
context.fillStyle = "black";
var dx = Math.floor(250 * 0.5) - 10;
var dy = Math.floor(100 * 0.05);
context.fillText(myData[i], dx, dy);
context.restore();
}
function plotData()
{
var canvas;
var ctx;
var lastend = 0;
var myTotal = getTotal();
var pRadius = 100;
var xPie=250;
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = true;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < myData.length; i++)
{
ctx.fillStyle = myColor[i];
ctx.beginPath();
ctx.moveTo(xPie,pRadius+10);
ctx.arc(xPie,pRadius+10,pRadius,lastend,lastend +
(Math.PI*2*(myData[i]/myTotal)),false);
ctx.lineTo(xPie,pRadius+10);
ctx.fill();
lastend += Math.PI*2*(myData[i]/myTotal);
ctx.lineWidth = 2;
ctx.strokeStyle = '#000000';
ctx.stroke();
}
}
plotData();
</script>
</section>
</body>
</html>
Can someone help me to get this done?
Thanks,
Kimz
Here's an alternate way to draw a wedge with a specified starting & ending angle:
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.arc(cx,cy,radius,startAngle,endAngle,false);
ctx.closePath();
ctx.fillStyle=fill;
ctx.strokeStyle='black';
ctx.fill();
ctx.stroke();
I suggest this alternate method because you can easily calculate the angle exactly between the starting & ending angle like this:
var midAngle=startAngle+(endAngle-startAngle)/2;
And given the midAngle, you can use some trigonometry to calculate where to draw your values inside the wedge:
// draw the value labels 75% of the way from centerpoint to
// the outside of the wedge
var labelRadius=radius*.75;
// calculate the x,y at midAngle
var x=cx+(labelRadius)*Math.cos(midAngle);
var y=cy+(labelRadius)*Math.sin(midAngle);
Here's example code and a Demo:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
ctx.lineWidth = 2;
ctx.font = '12px verdana';
var PI2 = Math.PI * 2;
var myColor = ["Green", "Red", "Blue"];
var myData = [30, 60, 10];
var cx = 150;
var cy = 150;
var radius = 100;
pieChart(myData, myColor);
function pieChart(data, colors) {
var total = 0;
for (var i = 0; i < data.length; i++) {
total += data[i];
}
var sweeps = []
for (var i = 0; i < data.length; i++) {
sweeps.push(data[i] / total * PI2);
}
var accumAngle = 0;
for (var i = 0; i < sweeps.length; i++) {
drawWedge(accumAngle, accumAngle + sweeps[i], colors[i], data[i]);
accumAngle += sweeps[i];
}
}
function drawWedge(startAngle, endAngle, fill, label) {
// draw the wedge
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle, false);
ctx.closePath();
ctx.fillStyle = fill;
ctx.strokeStyle = 'black';
ctx.fill();
ctx.stroke();
// draw the label
var midAngle = startAngle + (endAngle - startAngle) / 2;
var labelRadius = radius * .75;
var x = cx + (labelRadius) * Math.cos(midAngle);
var y = cy + (labelRadius) * Math.sin(midAngle);
ctx.fillStyle = 'white';
ctx.fillText(label, x, y);
}
body {
background-color: ivory;
padding: 10px;
}
#canvas {
border: 1px solid red;
}
<canvas id="canvas" width=400 height=300></canvas>