I'm building a dynamic radar chart, I got the code reviewed and followed the recommendation from fellow SO member.
This is how far I've come, but seem to have hit a roadblock:
var canv = document.getElementById('canvas');
var canv1 = document.getElementById('canvas1');
var point_xy = document.getElementById('point_xy');
var tipCanvas = document.getElementById("tip");
var tipCtx = tipCanvas.getContext("2d");
var point_xy_cords = [
[]
];
var pentagon_one = 24;
var pentagon_two = 18;
var pentagon_three = 12;
var pentagon_four = 6;
var pentagon_five = 0;
var circles = [];
var contx = canv.getContext('2d');
var contx1 = canv1.getContext('2d');
var offsetX = canv1.offsetLeft;
var offsetY = canv1.offsetTop;
contx.clearRect(0, 0, canv.width, canv.height);
function drawShape(ctx, x, y, points, radius1, radius2, alpha0) {
//points: number of points (or number of sides for polygons)
//radius1: "outer" radius of the star
//radius2: "inner" radius of the star (if equal to radius1, a polygon is drawn)
//angle0: initial angle (clockwise), by default, stars and polygons are 'pointing' up
var radius_size = radius1;
var i, angle, radius;
if (radius2 !== radius1) {
points = 2 * points;
}
for (var i = 0; i <= 5; i++) {
var temp = [];
contx1.beginPath();
for (var j = 0; j <= 4; j++) {
angle = j * 2 * Math.PI / points - Math.PI / 2 + alpha0;
radius = j % 2 === 0 ? radius_size : radius_size;
temp[j] = [(x + radius_size * Math.cos(angle)), (y + radius_size * Math.sin(angle))];
ctx.lineTo(temp[j][0], temp[j][1]);
}
ctx.closePath();
style(ctx);
radius_size = radius_size - 20;
point_xy_cords.push(temp);
}
point_xy.textContent = "[1] = " + point_xy_cords[1] + " y = " + point_xy_cords[1][1];
}
function style(ctx, fill) {
ctx.strokeStyle = "rgba(0, 109, 0, 1)";
ctx.lineWidth = 2;
if (fill) {
ctx.fillStyle = "rgba(74, 157, 33, 0.6)";
ctx.fill();
} else {
ctx.stroke()
}
//contx.fill();
}
var radius = 2;
var Circle = function(x, y, radius) {
this.left = x - radius;
this.top = y - radius;
this.right = x + radius;
this.bottom = y + radius;
this.point_clicked = [];
this.clicked = function(){
points[1][0] = x; //hardcoded part
points[1][1] = y; //hardcoded part
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
this.draw = function(ctx) {
//Draw all points
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI, false);
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(74, 157, 33, 1)";
ctx.fill();
ctx.stroke();
}
this.containsPoint = function(x,y){
return (x < this.right && x > this.left && y > this.top && y < this.bottom);
}
};
//Draw background
function drawBackgroundPentagons(ctx) {
drawShape(ctx, 120, 120, 5, 100, 100, 0);
}
drawBackgroundPentagons(contx1);
//Draw all the points
function drawPoints(){
for (var x = 1; x <= 5; x++){
for (var y = 0; y <= 4; y++){
var circle = new Circle(point_xy_cords[x][y][0], point_xy_cords[x][y][1], 8);
circle.draw(contx1);
circles.push(circle);
}
}
}
drawPoints();
function drawMainPentagon(ctx, points) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var x = 1; x <= 4; x++) {
ctx.lineTo(points[x][0], points[x][1]);
}
style(ctx, "fill");
ctx.closePath();
}
points = point_xy_cords[1];
drawMainPentagon(contx1, points);
function handleMouseDown(e, message) {
point_xy.textContent = (message);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canv1.onmousedown = function(e) {
var pos = getMousePos(canv1, e);
var clickedX = pos.x;
var clickedY = pos.y;
var tooltipText = "nothing";
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
if (circle.containsPoint(clickedX, clickedY)) {
circle.clicked();
return;
}
}
tooltip("points[0]", clickedX, clickedY);
};
function tooltip(text, clickedX, clickedY) {
tipCtx.fillStyle = "black";
tipCtx.fillRect(0, 0, canvas.width, canvas.height);
tipCtx.fillStyle = "white";
tipCtx.fillText(text, 5, 10);
tipCanvas.style.left = (clickedX + 15) + "px";
tipCanvas.style.top = (clickedY - 26) + "px";
}
canv1.onmouseover = function(e) {
return null;
}
canv1.onmouseout = function(e) {
return null;
}
canv1.onmousemove = function(e) {
return null;
}
#tip {
left: -200px;
top: 100px;
position: absolute;
float: left;
maxWidth: 200px;
backgroundColor: rgba(0, 0, 0, 0.8);
border: rgba(45, 65, 45, 1);
borderRadius: 5px;
color: #f9f9f9;
fontSize: 14px;
padding: 5px;
textAlign: left;
}
<div id="canvasesdiv" style="position:relative; width:400px; height:300px">
<canvas id="tip" width=100 height=100 style="z-index: 3;"></canvas>
<canvas id="canvas" style="z-index: 1;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<canvas id="canvas1" style="z-index: 2;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<div id='point_xy'></div>
If you click a point, it is suppose to move the point of the highlighted pentagon to the clicked point. It works, except I can't figure out what conditions to add in order to move the correct corner of the highlighted pentagon. In the above code I have hardcoded it, so that no matter which point you click, it will move point at index 0.
Any direction would be appreciated.
So what you want to do is let each circle know what spoke or radii it belongs to. Something like this:
var Circle = function(x, y, radius, spoke, value) {
this.x = x;
this.y = y;
this.radius = radius;
this.spoke = spoke;
this.value = value;
Now create them something like:
function drawPoints() {
for (var value = 1; value <= 5; value++){
for (var spoke = 0; spoke <= 4; spoke++){
var circle = new Circle(point_xy_cords[value][spoke][0], point_xy_cords[value][spoke][1], 8, spoke, value);
circle.draw(contx1);
circles.push(circle);
}
}
}
I changed the variable names to something meaningful. One note here is that you mix code to create the circles and code to draw them. You don't want to do this. Create them once on initialization and redraw them as changes are made (clicking). You don't want to re-create the circles every time you redraw.
Lastly change this:
// Circle
this.clicked = function(){
points[this.spoke][0] = this.x;
points[this.spoke][1] = this.y;
updateCanvas();
}
And elsewhere:
function updateCanvas() {
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
If I can make a suggestion, start with the simplest code you can. Start just by displaying the circles and pentagons, get that working cleanly and build onto it. Try and keep logic separate in your code. There are several places where you create objects and initialize arrays (like coords) while you are drawing which is both unnecssary but also means that you do it over and over instead of just once. There is a also lot of code here that is unnecessary.
Related
I'm trying to make a simple interactive game where there are circles of different colours moving in the canvas and when the user clicks on the blue circles, it logs the number of clicks on the screen. When clicking circles with any other colour, the animation stops.
I'm very new to javascript but this is what I have for now. I've made a function with random coloured circles and a function with blue circles moving but I'm totally stuck on how to stop the animation when clicking on the function with a random coloured circles and logging the amount of clicks on the blue circles. If someone could help me move forward with it in any way (doesn't have to be the full thing), that would be awesome, thanks.
JS
var canvas;
var ctx;
var w = 1000;
var h = 600;
var colours = ["red", "blue"];
var allCircles = [];
for(var i=0; i<1; i++){
setTimeout(function(){console.log(i)},1000);
}
document.querySelector("#myCanvas").onclick = click;
createData(2);
createDataTwo(20);
setUpCanvas();
animationLoop();
function animationLoop(){
clear();
for(var i = 0; i<allCircles.length; i++){
circle(allCircles[i]);
forward(allCircles[i], 5)
turn(allCircles[i], randn(30));
collisionTestArray(allCircles[i],allCircles)
bounce(allCircles[i]);
}
requestAnimationFrame(animationLoop);
}
function collisionTestArray(o, a){
for(var i=0; i<a.length; i++){
if(o !=a[i]){
collision(o,a[i]);
}
}
}
function collision(o1,o2){
if(o1 && o2){
var differencex = Math.abs(o1.x-o2.x);
var differencey = Math.abs(o1.y-o2.y);
var hdif = Math.sqrt(differencex*differencex+differencey*differencey);
if(hdif<o1.r+o2.r){
if(differencex < differencey){
turn(o1, 180-2*o1.angle);
turn(o2, 180-2*o2.angle);
}else{
turn(o1, 360-2*o1.angle);
turn(o2, 360-2*o2.angle);
}
turn(o1, 180);
turn(o2, 180);
console.log("collision");
};
}
}
function click(event){
clear()
}
function bounce (o){
if(o.x > w || o.x < 0){
turn(o, 180-2*o.angle);
};
if(o.y > h || o.y < 0){
turn(o, 360-2*o.angle);
}
}
function clear(){
ctx.clearRect(0,0,w,h);
}
function stop (){
o1.changex = 0;
o1.changey = 0;
o2.changex = 0;
o2.changey = 0;
}
function circle (o){
var x = o.x;
var y = o.y;
var a = o.angle;
var d = o.d;
ctx.beginPath();
ctx.arc(o.x,o.y,o.r,0,2*Math.PI);
ctx.fillStyle = "hsla("+o.c+",100%,50%, "+o.a+")";
ctx.fill();
o.x = x;
o.y = y;
o.angle = a;
o.d = d;
}
function createData(num){
for(var i=0; i<num; i++){
allCircles.push({
"x": rand(w),
"changex": rand(10),
"y":rand(h),
"changex": rand(10),
"w": randn(w),
"h": randn(h),
"d": 1,
"a": 1,
"angle": 0,
"changle":15,
"c":216,
"r": 50
}
)
}
}
function createDataTwo(num){
for(var i=0; i<num; i++){
allCircles.push({
"x": rand(w),
"changex": rand(10),
"y":rand(h),
"changex": rand(10),
"w": randn(w),
"h": randn(h),
"d": 1,
"a": 1,
"angle": 0,
"changle":15,
"c":rand(90),
"r": 50
}
)
}
}
function turn(o,angle){
if(angle != undefined){
o.changle=angle;
};
o.angle+=o.changle;
}
function forward(o,d){
var changeX;
var changeY;
var oneDegree = Math.PI/180;
if(d != undefined){
o.d = d;
};
changeX= o.d*Math.cos(o.angle*oneDegree);
changeY = o.d*Math.sin(o.angle*oneDegree);
o.x+=changeX;
o.y+=changeY;
}
function randn(r){
var result = Math.random()*r - r/2
return result
}
function randi(r) {
var result = Math.floor(Math.random()*r);
return result
}
function rand(r){
return Math.random()*r
}
function setUpCanvas(){
canvas = document.querySelector("#myCanvas");
ctx = canvas.getContext("2d");
canvas.width = w;
canvas.height = h;
canvas.style.border = "5px solid orange"
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, w, h);
}
console.log("assi4")
HTML
<html>
<head>
<link rel="stylesheet" type="text/css" href="../modules.css">
</head>
<body>
<div id="container">
<h1>Click the Blue Circles Only</h1>
<canvas id = "myCanvas"></canvas>
</div>
<script src="assi5.js"></script>
</body>
</html>
CSS
#container {
margin: auto;
width: 75%;
text-align: center;
}
You can use cancelAnimationFrame to stop the animation when a non-blue circle is clicked
You need to pass it a reference to the frame ID returned from requestAnimationFrame for it to work.
In order to tell if a circle was clicked, you need to check the coordinates of each circle against the coordinates of the click.
I have an example below if you had your blue circles in an array "blue", and other circles in array "other", the ID returned by requestAnimationFrame as "frame".
The check function returns the number of blue circles hit (the points scored) and if any other circles were hit, it stops the animation.
getCoords returns the coordinates of the click on the canvas from the click event.
canvas.addEventListener('click', event=>{
points += check(getCoords(event), blue, other, frame);
document.getElementById('points').textContent = points;
})
function check({x, y}, blue, other, frame) {
other.filter(circle=>circle.isWithin(x, y))
.length && cancelAnimationFrame(frame); // This is where animation stops
return blue.filter(circle=>circle.isWithin(x, y)).length;
}
function getCoords(event) {
const canvas = event.target;
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
return { x, y };
}
I have an example that works below where I changed the circles to the result of a function rather than an inline object, and moved the functions you use on them into their own class. You don't have to do this, but I find it a lot easier to understand.
function main() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext("2d");
const clear = () => context.clearRect(0, 0, canvas.width, canvas.height);
const blue = new Array(2).fill().map(() => new Circle(context, 216));
const other = new Array(10).fill().map(() => new Circle(context));
let circles = [...blue, ...other];
let frame = 0;
let points = 0;
// Move the circle a bit and check if it needs to bounce
function update(circle) {
circle.forward(1)
circle.turn(30, true)
circle.collisionTestArray(circles)
circle.bounce();
}
// Main game loop, clear canvas, update circle positions, draw circles
function loop() {
clear();
circles.filter(circle => circle.free).forEach(update);
circles.forEach(circle => circle.draw());
frame = requestAnimationFrame(loop);
}
loop();
canvas.addEventListener('click', event => {
points += check(getCoords(event), blue, other, frame, circles);
document.getElementById('points').textContent = points;
})
}
function check({ x, y }, blue, other, frame) {
other.filter(circle => circle.isWithin(x, y))
// .map(circle=>circle.toggle())
.length && cancelAnimationFrame(frame); // This is where animation stops
return blue.filter(circle => circle.isWithin(x, y)).length;
}
function getCoords(event) {
const canvas = event.target;
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
return { x, y };
}
main();
function Circle(context, c) {
const randn = r => rand(r) - r / 2;
const randi = r => Math.floor(randi(r));
const rand = r => Math.random() * r;
// These are for easily stopping and starting a circle;
this.free = true;
this.stop = () => this.free = false;
this.release = () => this.free = true;
this.toggle = () => this.free = !this.free;
const {
width,
height
} = context.canvas;
// These are the same properties you were using in your code
this.x = rand(width);
this.changex = rand(10);
this.y = rand(height);
this.changey = rand(10);
this.w = randn(width);
this.h = randn(height);
this.d = 1;
this.a = 1;
this.angle = 0;
this.changle = 15;
this.c = c || rand(90); // This is the only difference between blue and other circles
this.r = 50;
// These next functions you had in your code, I just moved them into the circle definition
this.draw = () => {
const { x, y, r, c } = this;
context.beginPath();
context.arc(x, y, r, 0, 2 * Math.PI);
context.fillStyle = "hsla(" + c + ",100%,50%, 1)";
context.fill();
}
this.bounce = () => {
const { x, y, angle } = this;
if (x > width || x < 0) {
this.turn(180 - 2 * angle);
}
if (y > height || y < 0) {
this.turn(360 - 2 * angle);
}
}
this.turn = (angle, random = false) => {
this.changle = random ? randn(angle) : angle;
this.angle += this.changle;
}
this.forward = d => {
this.d = d;
this.x += this.d * Math.cos(this.angle * Math.PI / 180);
this.y += this.d * Math.sin(this.angle * Math.PI / 180);
}
this.collisionTestArray = a => a
.filter(circle => circle != this)
.forEach(circle => this.collision(circle));
this.collision = circle => {
var differencex = Math.abs(this.x - circle.x);
var differencey = Math.abs(this.y - circle.y);
var hdif = Math.sqrt(differencex ** 2 + differencey ** 2);
if (hdif < this.r + circle.r) {
if (differencex < differencey) {
this.turn(180 - 2 * this.angle);
circle.turn(180 - 2 * circle.angle);
} else {
this.turn(360 - 2 * this.angle);
circle.turn(360 - 2 * circle.angle);
}
this.turn(180);
circle.turn(180);
}
}
// These 2 functions I added to check if the circle was clicked
this.distanceFrom = (x, y) => Math.sqrt((this.x - x) ** 2 + (this.y - y) ** 2);
this.isWithin = (x, y) => this.r > this.distanceFrom(x, y);
}
#canvas {
border: 5px solid orange;
}
#container {
margin: auto;
width: 75%;
text-align: center;
}
<div id="container">
<h1>Click the Blue Circles Only</h1>
<canvas id="canvas" width="1000" height="600"></canvas>
<p>
Points: <span id="points">0</span>
</p>
</div>
using OOP is better in this situation and will save you a lot of time
I have written the OOP version of your game, I wrote it in harry so you may find some bugs but it is good as a starting point
const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")
let h = canvas.height = 600
let w = canvas.width = 800
const numberOfCircles = 20
let circles = []
// running gameover
let gameStatus = "running"
let score = 0
canvas.addEventListener("click", (e) => {
if(gameStatus === "gameOver") {
document.location.reload()
return;
}
const mouse = {x: e.offsetX, y: e.offsetY}
for(let circle of circles) {
if(distance(mouse, circle) <= circle.radius) {
if(circle.color == "blue") {
gameStatus = "running"
score += 1
} else {
gameStatus = "gameOver"
}
}
}
})
class Circle {
constructor(x, y, color, angle) {
this.x = x
this.y = y
this.color = color
this.radius = 15
this.angle = angle
this.speed = 3
}
draw(ctx) {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
}
move(circles) {
this.check(circles)
this.x += Math.cos(this.angle) * this.speed
this.y += Math.sin(this.angle) * this.speed
}
check(circles) {
if(this.x + this.radius > w || this.x - this.radius < 0) this.angle += Math.PI
if(this.y + this.radius > h || this.y - this.radius < 0) this.angle += Math.PI
for(let circle of circles) {
if(circle === this) continue
if(distance(this, circle) <= this.radius + circle.radius) {
// invert angles or any other effect
// there are much better soultion for resolving colliusions
circle.angle += Math.PI / 2
this.angle += Math.PI / 2
}
}
}
}
}
setUp()
gameLoop()
function gameLoop() {
ctx.clearRect(0,0,w,h)
if(gameStatus === "gameOver") {
ctx.font = "30px Comic"
ctx.fillText("Game Over", w/2 - 150, h/2 - 100)
ctx.fillText("you have scored : " + score, w/2 - 150, h/2)
return;
}
ctx.font = "30px Comic"
ctx.fillText("score : " + score, 20, 30)
for (let i = 0; i < circles.length; i++) {
const cirlce = circles[i]
cirlce.draw(ctx)
cirlce.move(circles)
}
requestAnimationFrame(gameLoop)
}
function random(to, from = 0) {
return Math.floor(Math.random() * (to - from) + from)
}
function setUp() {
gameStatus = "running"
score = 0
circles = []
for (var i = 0; i < numberOfCircles; i++) {
const randomAngle = random(360) * Math.PI / 180
circles.push(new Circle(random(w, 20), random(h, 20), randomColor(), randomAngle))
}
}
function randomColor() {
const factor = random(10)
if(factor < 3) return "blue"
return `rgb(${random(255)}, ${random(255)}, ${random(100)})`
}
function distance(obj1, obj2) {
const xDiff = obj1.x - obj2.x
const yDiff = obj1.y - obj2.y
return Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2))
}
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 can reshape polygon with mouse by using code in snippet. After drawing polygon, users can change shape by moving points. But I want to modify shape without changing line lengths. The points will change as possible but length of the lines will remain the same.
How can I do this?
var canvas, ctx;
var canvasIsMouseDown = false;
var radius = 6;
var pointIndex = -1;
var points = [
{ x: 10, y: 10 },
{ x: 100, y: 50 },
{ x: 150, y: 100 },
{ x: 60, y: 110 },
{ x: 30, y: 160 }
];
function start() {
canvas = document.getElementById("cnPolygon");
ctx = canvas.getContext("2d");
canvas.addEventListener("mousemove", canvasMouseMove);
canvas.addEventListener("mousedown", canvasMouseDown);
canvas.addEventListener("mouseup", canvasMouseUp);
draw();
}
function canvasMouseMove(ev) {
if (!canvasIsMouseDown || pointIndex === -1) return;
points[pointIndex].x = ev.pageX - this.offsetLeft;
points[pointIndex].y = ev.pageY - this.offsetTop;
draw();
}
function canvasMouseDown(ev) {
canvasIsMouseDown = true;
var x = ev.pageX - this.offsetLeft;
var y = ev.pageY - this.offsetTop;
pointIndex = -1;
var dist;
for (var i = 0; i < points.length; i++) {
dist = Math.sqrt(Math.pow((x - points[i].x), 2) + Math.pow((y - points[i].y), 2));
if (dist <= radius) {
pointIndex = i;
break;
}
}
}
function canvasMouseUp(ev) {
canvasIsMouseDown = false;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 0; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
for (var i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius, 0, Math.PI * 2);
ctx.stroke();
}
}
document.addEventListener("DOMContentLoaded", start);
<canvas id="cnPolygon" width="200" height="200" style="border:solid 1px silver"></canvas>
Take a look at this
var canvas, ctx;
var canvasIsMouseDown = false;
var radius = 3;
var pointIndex = -1;
var points = [
{ x: 10, y: 10 },
{ x: 100, y: 50 },
{ x: 150, y: 100 },
{ x: 60, y: 110 },
{ x: 30, y: 160 }
];
// PHYSICS START ----------------
var stiffness = 0.25 // defines how elastic the contrainst should be
var oscillations = 10 // defines how many iterations should be made, more iterations mean higher precision
function getAngle(x1,y1,x2,y2){
return Math.atan2(y2-y1,x2-x1) + Math.PI/2
}
function getConstraintPos(tx,ty,ox,oy,dist){
var rot = getAngle(tx,ty,ox,oy)
var x = tx+Math.sin(rot)*dist
var y = ty-Math.cos(rot)*dist
return [x,y]
}
function applyContraintForce(point,pos){
point.x += (pos[0] - point.x)*stiffness
point.y += (pos[1] - point.y)*stiffness
}
function defineDistances(){
for (var i = 0; i < points.length; i++) {
var next_point = points[(i+1)%points.length]
points[i].distance = Math.sqrt(Math.pow((next_point.x - points[i].x), 2) + Math.pow((next_point.y - points[i].y), 2))
}
}
function updateContraints(){
// forward pass
for (var i=0;i<points.length;i++)
{
if(i==pointIndex) continue
var j = (+i+1)%points.length
var pos = getConstraintPos(points[j].x,points[j].y,points[i].x,points[i].y,points[i].distance)
applyContraintForce(points[i],pos)
}
//backward pass
for (var i=points.length-1;i>=0;i--)
{
if(i==pointIndex) continue
var j = (i-1)
j = j<0 ? points.length+j : j
var pos = getConstraintPos(points[j].x,points[j].y,points[i].x,points[i].y,points[j].distance)
applyContraintForce(points[i],pos)
}
}
// PHYSICS END ----------------
function start() {
canvas = document.getElementById("cnPolygon");
ctx = canvas.getContext("2d");
canvas.addEventListener("mousemove", canvasMouseMove);
canvas.addEventListener("mousedown", canvasMouseDown);
canvas.addEventListener("mouseup", canvasMouseUp);
defineDistances()
draw();
}
function canvasMouseMove(ev) {
if (!canvasIsMouseDown || pointIndex === -1) return;
points[pointIndex].x = ev.pageX - this.offsetLeft;
points[pointIndex].y = ev.pageY - this.offsetTop;
for(var i=0;i<oscillations;i++){
updateContraints()
}
draw();
}
function canvasMouseDown(ev) {
canvasIsMouseDown = true;
var x = ev.pageX - this.offsetLeft;
var y = ev.pageY - this.offsetTop;
pointIndex = -1;
var dist;
for (var i = 0; i < points.length; i++) {
dist = Math.abs(Math.sqrt(Math.pow((x - points[i].x), 2) + Math.pow((y - points[i].y), 2)));
if (dist <= radius) {
pointIndex = i;
break;
}
}
}
function canvasMouseUp(ev) {
canvasIsMouseDown = false;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 0; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
for (var i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius * 2, 0, Math.PI * 2);
ctx.stroke();
}
}
document.addEventListener("DOMContentLoaded", start);
<canvas id="cnPolygon" width="500" height="300" style="border:solid 1px silver"></canvas>
What this does is calculate the angle between two given points and applies a force based on that angle and the distance delta. The force applied is multiplied by the stiffness.
This has to be done forwards (point A -> point B) and backwards (point A <- point B) in order to account for the differences between the last to first point in the chain.
NOTE this is not 100% accurate. The accuracy can be increased by the iterations count, but as #bhspencer already pointed out, there are cases where this is impossible, simply because of geometry.
I'm trying to find a way to put as much hexagons in a circle as possible. So far the best result I have obtained is by generating hexagons from the center outward in a circular shape.
But I think my calculation to get the maximum hexagon circles is wrong, especially the part where I use the Math.ceil() and Math.Floor functions to round down/up some values.
When using Math.ceil(), hexagons are sometimes overlapping the circle.
When using Math.floor() on the other hand , it sometimes leaves too much space between the last circle of hexagons and the circle's border.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var PI=Math.PI;
var PI2=PI*2;
var hexCircle = {
r: 110, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
};
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
},
space: 1
};
drawHexCircle( hexCircle, hexagon );
function drawHexCircle(hc, hex ) {
drawCircle(hc);
var hcr = Math.ceil( Math.sqrt(3) * (hc.r / 2) );
var hr = Math.ceil( ( Math.sqrt(3) * (hex.r / 2) ) ) + hexagon.space; // hexRadius
var circles = Math.ceil( ( hcr / hr ) / 2 );
drawHex( hc.pos.x , hc.pos.y, hex.r ); //center hex ///
for (var i = 1; i<=circles; i++) {
for (var j = 0; j<6; j++) {
var currentX = hc.pos.x+Math.cos(j*PI2/6)*hr*2*i;
var currentY = hc.pos.y+Math.sin(j*PI2/6)*hr*2*i;
drawHex( currentX,currentY, hex.r );
for (var k = 1; k<i; k++) {
var newX = currentX + Math.cos((j*PI2/6+PI2/3))*hr*2*k;
var newY = currentY + Math.sin((j*PI2/6+PI2/3))*hr*2*k;
drawHex( newX,newY, hex.r );
}
}
}
}
function drawHex(x, y, r){
ctx.beginPath();
ctx.moveTo(x,y-r);
for (var i = 0; i<=6; i++) {
ctx.lineTo(x+Math.cos((i*PI2/6-PI2/4))*r,y+Math.sin((i*PI2/6-PI2/4))*r);
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
}
<canvas id="myCanvas" width="350" height="350" style="border:1px solid #d3d3d3;">
If all the points on the hexagon are within the circle, the hexagon is within the circle. I don't think there's a simpler way than doing the distance calculation.
I'm not sure how to select the optimal fill point, (but here's a js snippet proving that the middle isn't always it). It's possible that when you say "hexagon circle" you mean hexagon made out of hexagons, in which case the snippet proves nothing :)
I made the hexagon sides 2/11ths the radius of the circle and spaced them by 5% the side length.
var hex = {x:0, y:0, r:10};
var circle = {x:100, y:100, r:100};
var spacing = 1.05;
var SQRT_3 = Math.sqrt(3);
var hexagon_offsets = [
{x: 1/2, y: -SQRT_3 / 2},
{x: 1, y: 0},
{x: 1/2, y: SQRT_3 / 2},
{x: -1/2, y: SQRT_3 / 2},
{x: -1, y: 0},
{x: -1/2, y: -SQRT_3 / 2}
];
var bs = document.body.style;
var ds = document.documentElement.style;
bs.height = bs.width = ds.height = ds.width = "100%";
bs.border = bs.margin = bs.padding = 0;
var c = document.createElement("canvas");
c.style.display = "block";
c.addEventListener("mousemove", follow, false);
document.body.appendChild(c);
var ctx = c.getContext("2d");
window.addEventListener("resize", redraw);
redraw();
function follow(e) {
hex.x = e.clientX;
hex.y = e.clientY;
redraw();
}
function drawCircle() {
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
}
function is_in_circle(p) {
return Math.pow(p.x - circle.x, 2) + Math.pow(p.y - circle.y, 2) < Math.pow(circle.r, 2);
}
function drawLine(a, b) {
var within = is_in_circle(a) && is_in_circle(b);
ctx.strokeStyle = within ? "green": "red";
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.stroke();
return within;
}
function drawShape(shape) {
var within = true;
for (var i = 0; i < shape.length; i++) {
within = drawLine(shape[i % shape.length], shape[(i + 1) % shape.length]) && within;
}
if (!within) return false;
ctx.fillStyle = "green";
ctx.beginPath();
ctx.moveTo(shape[0].x, shape[0].y);
for (var i = 1; i <= shape.length; i++) {
ctx.lineTo(shape[i % shape.length].x, shape[i % shape.length].y);
}
ctx.closePath();
ctx.fill();
return true;
}
function calculate_hexagon(x, y, r) {
return hexagon_offsets.map(function (offset) {
return {x: x + r * offset.x, y: y + r * offset.y};
})
}
function drawHexGrid() {
var hex_count = 0;
var grid_space = calculate_hexagon(0, 0, hex.r * spacing);
var y = hex.y;
var x = hex.x;
while (y > 0) {
y += grid_space[0].y * 3;
x += grid_space[0].x * 3;
}
while (y < c.height) {
x %= grid_space[1].x * 3;
while (x < c.width) {
var hexagon = calculate_hexagon(x, y, hex.r);
if (drawShape(hexagon)) hex_count++;
x += 3 * grid_space[1].x;
}
y += grid_space[3].y;
x += grid_space[3].x;
x += 2 * grid_space[1].x;
}
return hex_count;
}
function redraw() {
c.width = window.innerWidth;
c.height = window.innerHeight;
circle.x = c.width / 2;
circle.y = c.height / 2;
circle.r = Math.min(circle.x, circle.y) * 0.9;
hex.r = circle.r * (20 / 110);
ctx.clearRect(0, 0, c.width, c.height);
var hex_count = drawHexGrid();
drawCircle();
ctx.fillStyle = "rgb(0, 0, 50)";
ctx.font = "40px serif";
ctx.fillText(hex_count + " hexes within circle", 20, 40);
}
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>