Related
I am trying to make a square that rotates in place, however my square is spiraling inward, and I have no idea why. Here is the code, if someone could please explain what is happening as to why it is not just spinning in place.
var angle = 2 * (Math.PI / 180);
var rotate = [
[Math.cos(angle),Math.sin(angle)],
[-Math.sin(angle),Math.cos(angle)]
];
var points = [[300,0],[0,300],[-300,0],[0,-300]];
init.ctx.translate(init.canvas.width/2,init.canvas.height/2);
function loop(){
draw();
}
setInterval(loop,10);
function draw(){
init.ctx.beginPath();
init.ctx.moveTo(points[0][0],points[0][1]);
init.ctx.lineTo(points[1][0],points[1][1]);
init.ctx.lineTo(points[2][0],points[2][1]);
init.ctx.lineTo(points[3][0],points[3][1]);
init.ctx.closePath();
init.ctx.stroke();
for(let i=0;i<points.length;i++){
init.ctx.beginPath();
init.ctx.fillStyle = "red";
init.ctx.fillRect(points[i][0],points[i][1],5,5);
points[i][0] = points[i][0]*rotate[0][0] + points[i][1]*rotate[0][1];
points[i][1] = points[i][0]*rotate[1][0] + points[i][1]*rotate[1][1];
}
}
So, you are applying a small rotation each time draw is called, specifically 1/180th of a full rotation. Problem is that you are relying on floating point math to give you exact values, and it's not because it doesn't. This is compounded by the points array being calculated by iterations. I suggest calculate the new points on each step through draw by applying the correct rotate matrix for your current angle to the starting points.
var angle = 0;
var startPoints = [[300,0],[0,300],[-300,0],[0,-300]];
var points = [[300,0],[0,300],[-300,0],[0,-300]];
init.ctx.translate(init.canvas.width/2,init.canvas.height/2);
function loop(){
draw();
}
setInterval(loop,10);
function draw(){
init.ctx.beginPath();
init.ctx.moveTo(points[0][0],points[0][1]);
init.ctx.lineTo(points[1][0],points[1][1]);
init.ctx.lineTo(points[2][0],points[2][1]);
init.ctx.lineTo(points[3][0],points[3][1]);
init.ctx.closePath();
init.ctx.stroke();
angle = angle + Math.PI / 90;
var rotate = [
[Math.cos(angle),Math.sin(angle)],
[-Math.sin(angle),Math.cos(angle)]
];
for(let i=0;i<points.length;i++){
init.ctx.beginPath();
init.ctx.fillStyle = "red";
init.ctx.fillRect(points[i][0],points[i][1],5,5);
points[i][0] = startPoints[i][0]*rotate[0][0] + startPoints[i][1]*rotate[0][1];
points[i][1] = startPoints[i][0]*rotate[1][0] + startPoints[i][1]*rotate[1][1];
}
}
Some tips to improve your code.
As a beginner I can see some bad habits creeping in and as there is already an answer I thought I would just give some tips to improve your code.
Don't use setInterval to create animations. requestAnimationFrame gives much better quality animations.
Arrays were created in high level languages to make life easier, not harder.
You have painfully typed out
init.ctx.beginPath();
init.ctx.moveTo(points[0][0],points[0][1]);
init.ctx.lineTo(points[1][0],points[1][1]);
init.ctx.lineTo(points[2][0],points[2][1]);
init.ctx.lineTo(points[3][0],points[3][1]);
init.ctx.closePath();
init.ctx.stroke();
That would be a nightmare if you had 100 points. Much better to create a generic function to do that for you.
function drawShape(ctx,shape){
ctx.beginPath();
for(var i = 0; i < shape.length; i++){
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.stroke();
}
Now you can render any shape on any canvas context with the same code.
drawShape(init.ctx,points); // how to draw your shape.
If you use a uniform scale then you can shorten the transform a little by reusing the x axis of the transformation
var rotate = [
[Math.cos(angle),Math.sin(angle)],
[-Math.sin(angle),Math.cos(angle)]
];
Note how the second two values are just the first two swapped with the new x negated. You can also include a scale in that and just hold the first two values.
var angle = ?
var scale = 1; // can be anything
// now you only need two values for the transform
var xAx = Math.cos(angle) * scale; // direction and size of x axis
var xAy = Math.sin(angle) * scale;
And you apply the transform to a point as follows
var px = ?; // point to transform
var py = ?;
var tx = px * xAx - py * xAy;
var ty = px * xAy + py * xAx;
And to add a origin
var tx = px * xAx - py * xAy + ox; // ox,oy is the origin
var ty = px * xAy + py * xAx + oy;
But is is much better to let the canvas 2D API do the transformation for you. The example below shows the various methods described above to render your box and animate the box.
Example using best practice.
const ctx = canvas.getContext("2d");
var w = canvas.width; // w,h these are set if canvas is resized
var h = canvas.height;
var cw = w / 2; // center width
var ch = h / 2; // center height
var globalScale = 1; // used to scale shape to fit the canvas
var globalTime;
var angle = Math.PI / 2;
var rotateRate = 90; // deg per second
var points = [
[300, 0],
[0, 300],
[-300, 0],
[0, -300]
];
var maxSize = Math.hypot(600, 600); // diagonal size used to caculate scale
// so that shape fits inside the canvas
// Add path to the current path
// shape contains path points
// x,y origin of shape
// scale is the scale of the shape
// angle is the amount of rotation in radians.
function createShape(shape, x, y, scale, angle) {
var i = 0;
ctx.setTransform(scale, 0, 0, scale, x, y); // set the scale and origin
ctx.rotate(angle); // set the rotation
ctx.moveTo(shape[i][0], shape[i++][1]);
while (i < shape.length) { // create a line to each point
ctx.lineTo(shape[i][0], shape[i++][1]);
}
}
// draws fixed scale axis aligned boxes at vertices.
// shape contains the vertices
// vertSize size of boxes drawn at verts
// x,y origin of shape
// scale is the scale of the shape
// angle is the amount of rotation in radians.
function drawVertices(shape, vertSize, x, y, scale, angle) {
ctx.setTransform(1, 0, 0, 1, x, y);
const xAx = Math.cos(angle) * scale; // direction and size of x axis
const xAy = Math.sin(angle) * scale;
var i = 0;
while (i < shape.length) {
const vx = shape[i][0]; // get vert coordinate
const vy = shape[i++][1]; // IMPORTANT DONT forget i++ in the while loop
ctx.fillRect(
vx * xAx - vy * xAy - vertSize / 2, // transform and offset by half box size
vx * xAy + vy * xAx - vertSize / 2,
vertSize, vertSize
);
}
}
// draws shape outline and vertices
function drawFullShape(shape, scale, angle, lineCol, vertCol, lineWidth, vertSize) {
// draw outline of shape
ctx.strokeStyle = lineCol;
ctx.lineWidth = lineWidth / scale; // to ensure that the line with is 1 pixel
// set the width to in inverse scale
ctx.beginPath();
// shape origin at cw,ch
createShape(shape, cw, ch, scale, angle);
ctx.closePath();
ctx.stroke();
// draw the vert boxes.
ctx.fillStyle = vertCol;
drawVertices(shape, vertSize, cw, ch, scale, angle);
}
function loop(timer) {
globalTime = timer;
if (w !== innerWidth || h !== innerHeight) { // check if canvas need resize
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
globalScale = Math.min(w / maxSize, h / maxSize);
}
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.clearRect(0, 0, w, h);
const innerAngle = globalTime * (rotateRate * (Math.PI / 180)) / 1000;
drawFullShape(points, globalScale, angle, "black", "red", 2, 6);
drawFullShape(points, globalScale * 0.5, innerAngle, "black", "red", 2, 6);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
I have two drawings: one arc and one circle and I want the circle to follow the end of the arc. Basically, i need to fetch the x&y of the endAngle of the arc so I would use it on the circle to follow it.
//Arc
context.beginPath();
var x2 = canvas.width / 2;
var y2 = canvas.height / 2;
var radius2 = 215;
var startAngle2 = 1.5 * Math.PI;
var endAngle2= 2.3 * Math.PI;
var counterClockwise2 = false;
context.arc(x2, y2, radius2, startAngle2, endAngle2, counterClockwise2);
context.lineWidth = 10;
context.strokeStyle = "blue";
context.stroke();
//Circle
context.beginPath();
var x3 = x2+ 130;
var y3 = y2 + 200;
var radius3 = 20;
var startAngle3 = 0 * Math.PI;
var endAngle3 = 2 * Math.PI;
var counterClockwise3 = false;
context.arc(x3, y3, radius3, startAngle3, endAngle3, counterClockwise3);
context.lineWidth = 5;
context.strokeStyle = "yellow";
context.stroke();
If you want to get the x,y coordinates of the end of the arc, you should work with cos for x and sin for y.
Get the end of your arc (x,y) like so:
var x3 = canvas.width / 2 + radius2*Math.cos(endAngle2);
var y3 = canvas.height / 2 + radius2*Math.sin(endAngle2);
It's easier to use ctx.rotate(angle);
The center of the rotation is the origin of the canvas identified by 0,0. In order to choose another one you need to use ctx.translate(x, y); where x,y are the coordinates of the center of the rotation hence
context.translate(x1,y1)
context.rotate(20*Math.PI/180)
Finally, you need to change your coordinates to refer to the new center
context.arc(x2, y2, radius2, startAngle2, endAngle2, counterClockwise2);
...
....
var x3 = x2 + 130;
var y3 = y2 + 200;
becomes
context.arc(0, 0, radius2, startAngle2, endAngle2, counterClockwise2);
....
...
var x3 = 130;
var y3 = 200;
Here's the full code:
var canvas=document.getElementById("clock");
var context = canvas.getContext('2d');
context.beginPath();
var x1 = canvas.width/2;
var y1 = canvas.height/2;
var radius1 = 200;
var startAngle1 = 0 * Math.PI;
var endAngle1 = 2 * Math.PI;
var counterClockwise1 = false;
context.arc(x1, y1, radius1, startAngle1, endAngle1, counterClockwise1);
context.lineWidth = 20;
context.strokeStyle = 'black';
context.stroke();
context.font = "bold 76px Arial";
context.textAlign="center";
context.textBaseline="middle";
context.fillText("25:00", canvas.width/2, canvas.height/2);
context.beginPath();
context.translate(x1,y1)
context.rotate(Math.PI)
var radius2 = 215;
var startAngle2 = 1.5 * Math.PI;
var endAngle2 = 2.3 * Math.PI;
var counterClockwise2 = false;
context.arc(0, 0, radius2, startAngle2, endAngle2, counterClockwise2);
context.lineWidth = 10;
context.strokeStyle = "blue";
context.stroke();
context.beginPath();
var x3 = 130;
var y3 = 200;
var radius3 = 20;
var startAngle3 = 0 * Math.PI;
var endAngle3 = 2 * Math.PI;
var counterClockwise3 = false;
context.arc(x3, y3, radius3, startAngle3, endAngle3, counterClockwise3);
context.lineWidth = 5;
context.strokeStyle = "yellow";
context.stroke();
body {
background-color: #50B3B9;
}
#clock {
background-color: red;
position:relative;
}
#time {
position:absolute;
top:0px;
}
----------
<div class="container">
<canvas id="clock" width="600" height="600">
<div id="time">25:00</div>
</canvas>
</div>
I have 2 canvasses that visualize values from 2 different labels.
I wrote 2 almost the same javascripts and when I run my application. It skips the first visualitation and only shows the second one. Where is my mistake?
Here is my html code:-
<div><canvas id="canvas" width="300" height="300"></canvas></div>
<asp:Label ID="LblGauge" runat="server"></asp:Label>
<div><canvas id="canvas2" width="300" height="300"></canvas></div>
<asp:Label ID="LblGauge1" runat="server"></asp:Label>
And here are my 2 javascripts. The only difference now is the canvas/canvas2 and the lblgauge and lblgauge1. Even if I change all the variables in the second script it will still only show the second visualition.
<script>
window.onload = function () {
//canvas initialization
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var degrees = document.getElementById("LblGauge").textContent;
var new_degrees = 0;
var difference = 0;
var color = "lightgreen";
var bgcolor = "#222";
var text;
var animation_loop, redraw_loop;
function init() {
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H);
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2, false);
ctx.stroke();
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 180;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//the arc will start from the topmost end
ctx.arc(W / 2, H / 2, 100, 0 - 90 * Math.PI / 180, radians - 90 * Math.PI / 180, false);
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees / 360 * 100) + "%";
text_width = ctx.measureText(text).width;
ctx.fillText(text, W / 2 - text_width / 2, H / 2 + 15);
}
function draw() {
//Cancel any movement animation if a new chart is requested
if (typeof animation_loop != undefined) clearInterval(animation_loop);
////time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000 / difference);
}
//function to make the chart move to new degrees
function animate_to() {
if (degrees == new_degrees)
if (degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
draw();
}
</script>
<script>
window.onload = function () {
//canvas initialization
var canvas = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
//dimensions
var W = canvas.width;
var H = canvas.height;
//Variables
var degrees = document.getElementById("LblGauge1").textContent;
var new_degrees = 0;
var difference = 0;
var color = "lightgreen";
var bgcolor = "#222";
var text;
var animation_loop, redraw_loop;
function init() {
//Clear the canvas everytime a chart is drawn
ctx.clearRect(0, 0, W, H);
//Background 360 degree arc
ctx.beginPath();
ctx.strokeStyle = bgcolor;
ctx.lineWidth = 30;
ctx.arc(W / 2, H / 2, 100, 0, Math.PI * 2, false);
ctx.stroke();
//Angle in radians = angle in degrees * PI / 180
var radians = degrees * Math.PI / 180;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 30;
//the arc will start from the topmost end
ctx.arc(W / 2, H / 2, 100, 0 - 90 * Math.PI / 180, radians - 90 * Math.PI / 180, false);
ctx.stroke();
//Lets add the text
ctx.fillStyle = color;
ctx.font = "50px bebas";
text = Math.floor(degrees / 360 * 100) + "%";
text_width = ctx.measureText(text).width;
ctx.fillText(text, W / 2 - text_width / 2, H / 2 + 15);
}
function draw() {
//Cancel any movement animation if a new chart is requested
if (typeof animation_loop != undefined) clearInterval(animation_loop);
////time for each frame is 1sec / difference in degrees
animation_loop = setInterval(animate_to, 1000 / difference);
}
//function to make the chart move to new degrees
function animate_to() {
if (degrees == new_degrees)
if (degrees < new_degrees)
degrees++;
else
degrees--;
init();
}
draw();
}
</script>
Can somebody tell me how to change my code.
This is what the javascript shows me when it works.
I'd like to move an image in circular direction.
I used setTimeout function... but it didn't work.
My code is:
x = image_size/2+radius*Math.cos(Math.PI*angle)-ball_image_size/2;
y = image_size/2-radius*Math.sin(Math.PI*angle)-ball_image_size/2;
//-----image main circle--------
base_image = new Image();
base_image.src = 'img/test.jpg';
base_image.onload = function()
{
ctx.drawImage(base_image,0,0,image_size,image_size);
};
//------------image ball---------
ball_image = new Image();
ball_image.src = 'img/ball.jpg';
ball_image.onload = function()
{
ctx.drawImage(ball_image, x, y, ball_image_size, ball_image_size);
}
clr = setTimeout('ball()',20);
}
//--------function of animation------------
function ball () {
ball_image.style.left = Math.cos(Math.PI*angle)*radius;
ball_image.style.top = Math.sin(Math.PI*angle)*radius;
angle = angle + .1;
//setTimeout(ball,20);
}
See this version of a rotating star (as requested now changed to an image, behold my mighty drawing skills!) on a canvas as reference. Note again that your update should use the draw call on the canvas, ctx.drawImage(ball_image, x, y, ball_image_size, ball_image_size); and not set a style on the image element, which the canvas doesn't care about. Optimally, you should load your images before starting anything.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let radius = canvas.width / 3;
let centerX = canvas.width / 2;
let centerY = canvas.height;
let x, y;
let angle = 0;
let star = new Image();
star.onload = draw;
//Imgur doesn't produce CORS issues luckily
star.src = "http://i.imgur.com/VIofbab.png";
function draw() {
//minus because it should start on the left
x = centerX - Math.cos(angle) * radius;
//minus because canvas y-coord system is from
//top to bottom
y = centerY - Math.sin(angle) * radius;
//Angle is in rad
angle += Math.PI / 180;
angle %= Math.PI;
//Draw a star (was circle before)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(star, x - star.width / 2, y - star.height / 2);
setTimeout(draw, 50);
};
<html>
<body>
<canvas id="canvas" width="320" height="180" style="border:1px solid #000000;">not supported</canvas>
</body>
</html>
I have a basic JSFiddle whereby I want to have random points plotted inside a circle.
But I do not know how to limit the points to be inside the circle.
This is what I currently have:
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = 150,
cy = 150,
radius = 148;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(canvas.width/2, canvas.height/2, radius, 0, 2*Math.PI);
ctx.closePath();
ctx.fillStyle = '#00000';
ctx.fill();
// create random points
ctx.fillStyle = '#ffffff';
while(count) {
// randomise x:y
ctx.fillRect(x + canvas.width/2, y + canvas.height/2, 2, 2);
count--;
}
How would i go about generating random (x,y) coordinates to plot random points inside the circle?
My current fiddle: http://jsfiddle.net/e8jqdxp3/
To plot points randomly in a circle, you can pick a random value from the radius squared, then square root it, pick a random angle, and convert the polar coordinate to rectangular. The square / square root step ensures that we get a uniform distribution (otherwise most points would be near the center of the circle).
So the formula to plot a random point in the circle is the following, where r' is a random value between 0 and r2, and θ is a random value between 0 and 2π:
Screenshot of result:
Live Demo:
var canvas = document.getElementById("thecanvas");
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = 150,
cy = 150,
radius = 148;
ctx.fillStyle = '#CCCCCC';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(canvas.width / 2, canvas.height / 2, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
// create random points
ctx.fillStyle = '#ffffff';
while (count) {
var pt_angle = Math.random() * 2 * Math.PI;
var pt_radius_sq = Math.random() * radius * radius;
var pt_x = Math.sqrt(pt_radius_sq) * Math.cos(pt_angle);
var pt_y = Math.sqrt(pt_radius_sq) * Math.sin(pt_angle);
ctx.fillRect(pt_x + canvas.width / 2, pt_y + canvas.width / 2, 2, 2);
count--;
}
<canvas id="thecanvas" width="400" height="400"></canvas>
JSFiddle Version: https://jsfiddle.net/qc735bqw/
Randomly pick dSquared (0..radius^2) and theta (0..2pi), then
x = sqrt(dSquared) cos(theta)
y = sqrt(dSquared) sin(theta)
JSFiddle
var ctx = canvas.getContext('2d'),
count = 1000, // number of random points
cx = canvas.width/2,
cy = canvas.height/2,
radius = 148;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(0+canvas.width/2, 0+canvas.height/2, radius, 0, 2*Math.PI);
ctx.closePath();
ctx.fillStyle = '#00000';
ctx.fill();
ctx.fillStyle = '#ffffff';
while(count) {
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
var xDiff = cx - x;
var yDiff = cy - y;
if(Math.sqrt(xDiff*xDiff+yDiff*yDiff)<radius)
{
ctx.fillRect(x, y, 2, 2);
count--;
}
}
This worked for me:
const getRandomCoordinateInCircle = radius => {
var angle = Math.random() * Math.PI * 2;
const x = Math.cos(angle) * radius * Math.random();
const y = Math.sin(angle) * radius * Math.random();
return { x, y };
};
console.log(getRandomCoordinateInCircle(1000);
// { x: 118.35662725763385, y: -52.60516556856313 }
Returns a random point with { x: 0, y: 0} as the centre of the circle.