HTML5 canvas : counterclockwise arc with angle > 2 PI - javascript

I try to figure why angle > 2 PI does not give the same result when drawing an arc clockwise and counterclockwise.
Look at this code snippet, on the first line I draw "clockwise" 3 red arcs with a start angle of 0 and an end angle of PI, 2*PI and 3*PI.
Then I draw "counterclockwise" 3 blue arcs with same parameters.
The 3rd result bewilders me... Can anyone explain this to me ?
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// CLOCKWISE, angle = PI
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI, false);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
// CLOCKWISE, angle = 2 PI
ctx.beginPath();
ctx.arc(150, 50, 40, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
// CLOCKWISE, angle = 3 PI
ctx.beginPath();
ctx.arc(250, 50, 40, 0, 3 * Math.PI, false);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
// COUNTERCLOCKWISE, angle = PI
ctx.beginPath();
ctx.arc(50, 150, 40, 0, Math.PI, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
// COUNTERCLOCKWISE, angle = 2 PI
ctx.beginPath();
ctx.arc(150, 150, 40, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
// COUNTERCLOCKWISE, angle = 3 PI
ctx.beginPath();
ctx.arc(250, 150, 40, 0, 3 * Math.PI, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"/>

According to specs:
If anticlockwise is false and endAngle-startAngle is equal to or greater than 2π, or, if anticlockwise is true and startAngle-endAngle is equal to or greater than 2π, then the arc is the whole circumference of this ellipse, and the point at startAngle along this circle's circumference, measured in radians clockwise from the ellipse's semi-major axis, acts as both the start point and the end point.
Otherwise, the points at startAngle and endAngle along this circle's circumference, measured in radians clockwise from the ellipse's semi-major axis, are the start and end points respectively, and the arc is the path along the circumference of this ellipse from the start point to the end point, going anti-clockwise if anticlockwise is true, and clockwise otherwise. Since the points are on the ellipse, as opposed to being simply angles from zero, the arc can never cover an angle greater than 2π radians.
Put in maybe clearer pseudo-code:
if(
(anticlockwise === false && (endAngle - startAngle) >= 2π) ||
(anticlockwise === true && (startAngle - endAngle) >= 2π)
) {
arc_circumference = 2π;
}
else {
startAngle = startAngle % 2π;
endAngle = endAngle % 2π;
}
In your case, startAngle = 0, endAngle = 3π, anticlowkwise = true, if we run the above algorithm, we end up in the else case (0 - 3π < 2π) and endAngle is now (3π % 2π = 1π).
We could achieve the same output without the anticlockwise flag by swapping startAngle and endAngle:
var ctx = canvas.getContext("2d");
// COUNTERCLOCKWISE, angle = -3 PI (from OP)
ctx.beginPath();
ctx.arc(50, 150, 40, 0, 3 * Math.PI, true);
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
// CLOCKWISE, angle = -3 PI
ctx.beginPath();
ctx.arc(50, 50, 40, 3 * Math.PI, 0);
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
<canvas id="canvas"></canvas>

When you go COUNTERCLOCKWISE, after 0 comes 2PI. You should try this instead:
// COUNTERCLOCKWISE, angle = 3 PI
ctx.beginPath();
ctx.arc(250, 150, 40, 2 * Math.PI, 0, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
UPDATE:
After OP's comment I've added an animated demo:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
let delta = 0;
function Draw(){
requestAnimationFrame(Draw)
delta+= .01;
ctx.clearRect(0,0,c.width,c.height)
// CLOCKWISE: animating the end point
ctx.beginPath();
ctx.arc(50, 50, 40, 0, delta, false);
ctx.closePath();
ctx.stroke();
// CONTERCLOCKWISE, animating the start point
ctx.beginPath();
ctx.arc(150, 50, 40, 0,-delta, true);
ctx.closePath();
ctx.stroke();
}
Draw()
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"/>

Related

Unwanted half circle when trying to draw quarter circles

I'm trying to create the Fibonacci spiral with the canvas element in HTML5.
This is my drawing code, this function gets called for every number in the Fibonacci sequence. Side is the square's side length, or the current Fibonacci number. And start is the bottom right corner of each square.
function drawSpiral(side, start) {
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(start.x, start.y - side);
ctx.lineTo(start.x - side, start.y - side);
ctx.lineTo(start.x - side, start.y);
ctx.lineTo(start.x, start.y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
switch (direction) {
case 0:
ctx.arc(start.x, start.y - side, side, Math.PI, 1.5 * Math.PI, true);
break;
case 1:
ctx.arc(
start.x - side,
start.y - side,
side,
1.5 * Math.PI,
2 * Math.PI,
true
);
break;
case 2:
ctx.arc(start.x - side, start.y, side, 2 * Math.PI, Math.PI / 2, true);
break;
case 3:
ctx.arc(start.x, start.y, side, Math.PI / 2, Math.PI, true);
break;
}
ctx.stroke();
ctx.closePath();
}
Direction is a number, 0 to 4, that represents the direction of the next square, counterclockwise - 0 = right, 1 = up, ...
It draws the squares and arcs just fine, but it's also drawing half circles everywhere, and I can't figure out why.
Screenshot of the issue
The last param is "counterclockwise",
ctx.arc(x, y, radius, startAngle, endAngle, true);
will do the same as
ctx.arc(x, y, rad, endAngle, startAngle);
// note how start and end have been swapped
that is to trace an arc from endAngle to startAngle, so in your case, it will draw the 3/4 of a circle:
const ctx = document.querySelector("canvas").getContext("2d");
const startAngle = Math.PI; // 9 o'clock
const endAngle = Math.PI * 1.5; // 12 o'clock
ctx.arc(50, 50, 50, startAngle, endAngle, true);
ctx.strokeStyle = "green";
ctx.stroke();
ctx.beginPath();
ctx.arc(160, 50, 50, endAngle, startAngle);
ctx.strokeStyle = "blue";
ctx.stroke();
<canvas></canvas>
If you want to trace only the 1/4 of the circle, then don't set this param.

create dynamic line with circles in each end and parameters for width height and rotation for the line - javascript

i need help to create a function to create x quant of lines with circles in each end of the line using parameters to define the angle of rotation, width,height and color of the line and fill the space between the lines, the propours of this is making a kind of rotation max and min angle of the human arm and shoulder.
this is a image of ilustrative example what i need to do, the image of the person model is fine in png i need just to create dynamic lines.
this is the code i have so far:
function drawLine(deg,width,height,canvasId,color){
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
ctx.rotate(deg);
ctx.fillStyle = color;
ctx.fillRect(0, 0, width, height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
drawLine(0,200,3,'canvas','red')
drawLine(120,200,3,'canvas1','blue')
canvas{
position: absolute;
}
<canvas id="canvas"></canvas>
<canvas id="canvas1"></canvas>
thanks very much
Based on the picture I will assume 180deg is the reference point of 0. Anything out from there is where we start counting degrees. If that is the case you will want to run a function that calculates the angle between a solid line at 180deg and two other lines from that point.
In total you will need four points. You starting point for all references (pointB in this example), another point (pointD) will be used to set the angle reference to 0 degrees. We will measure the next two angles from this line.
PointA and PointC can be adjusted as needed and we then calculate the angle from pointD/pointB vector. We can use Math.atan2 to calculate the angles of BA and BC move away from BD.
let angle1 = Math.atan2(distBC_x * distBD_y - distBC_y * distBD_x, distBC_x * distBD_x + distBC_y * distBD_y);
In this snippet I changed the color of the lines to make it easier to see what is what. You also won't need to draw the pink line. This is static so you will need to create limits and a dynamic method to change the angles.
Change the x value of pointA and pointC to change the Min and Max. Keep in mind I have to restrictions set for you to be able to switch them.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
//change pointA and pointC x value.
//pointA sould be your Min
//PointC should be your Max
let pointA = {x: 200, y: 250};
let pointB = {x: 250, y: 100}; //common point
let pointC = {x: 120, y: 250};
//only used to set reference to 0 at 180 degrees
let pointD = {x: pointB.x, y: pointB.y + 150};
//creating our vectors length
let distBA_x = pointB.x - pointA.x;
let distBA_y = pointB.y - pointA.y;
let distBC_x = pointB.x - pointC.x;
let distBC_y = pointB.y - pointC.y;
let distBD_x = pointB.x - pointD.x;
let distBD_y = pointB.y - pointD.y;
//calculate angle between pink and purple
let angle1 = Math.atan2(distBC_x * distBD_y - distBC_y * distBD_x, distBC_x * distBD_x + distBC_y * distBD_y);
if(angle1 < 0) {angle1 = angle1 * -1;}
let degree_angle1 = angle1 * (180 / Math.PI);
//calculate angle between purple and red
let angle2 = Math.atan2(distBA_x * distBD_y - distBA_y * distBD_x, distBA_x * distBD_x + distBA_y * distBD_y);
if(angle2 < 0) {angle2 = angle2 * -1;}
let degree_angle2 = angle2 * (180 / Math.PI);
function draw() {
ctx.textStyle = 'black';
ctx.font = '20px Arial';
ctx.fillText('Max = '+ degree_angle1, 100, 20);
ctx.fillText('Min = '+ degree_angle2, 100, 50);
//Lines
ctx.strokeStyle = 'purple';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.stroke();
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.stroke();
ctx.strokeStyle = 'pink';
ctx.beginPath();
ctx.moveTo(pointB.x, pointB.y);
ctx.lineTo(pointD.x, pointD.y);
ctx.stroke();
//Points
ctx.fillStyle = 'purple';
ctx.beginPath();
ctx.arc(pointB.x, pointB.y, 5, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(pointA.x, pointA.y, 5, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(pointC.x, pointC.y, 5, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
//Fill
ctx.fillStyle = 'rgba(113, 0, 158, 0.3)'
ctx.beginPath();
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.fill();
ctx.closePath();
}
draw()
<canvas id='canvas'></canvas>

Clipping lines within ellipse on javascript canvas [duplicate]

This question already has an answer here:
All html canvas shapes are given the color of the last object added
(1 answer)
Closed 2 years ago.
I am drawing a filled ellipse (using arc()) on the javascript canvas:
ctx.arc(centerX, centerY, dotDiameter / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = color1;
ctx.fill();
I then want to draw a line, that is clipped to this ellipse, in a different color.
ctx.clip(); // Clip to the ellipse
ctx.strokeStyle = color2;
ctx.moveTo(centerX - dotRadius, centerY);
ctx.lineTo(centerX + dotRadius, centerY);
ctx.stroke();
However, the ellipse is also stroked with color2.
How can I clip lines to my ellipse, but not have the ellipse stroked? Is there a way to remove the ellipse (aka arc) from the canvas after I call clip()?
Thanks!
(FYI, this is an oversimplification of my code. The lines that stroked are more complicated than a single horizontal line and the lines do need to be clipped.)
We can calculate a point on the perimeter of an ellipsis with some basic trigonometry...
ctx.moveTo(x, y);
x += radiusX * Math.cos(lineangle)
y += radiusY * Math.sin(lineangle)
ctx.lineTo(x, y);
See code snippet below, I'm drawing an ellipse and a line from center to edge.
ctx = document.getElementById('c').getContext('2d');
ctx.lineWidth = 2;
function draw(x, y, radiusX, radiusY, lineangle, color1, color2) {
ctx.beginPath()
ctx.ellipse(x, y, radiusX, radiusY, 0, 0, 2 * Math.PI, false);
ctx.fillStyle = color1;
ctx.fill();
ctx.beginPath()
ctx.strokeStyle = color2;
ctx.moveTo(x, y);
x += radiusX * Math.cos(lineangle)
y += radiusY * Math.sin(lineangle)
ctx.lineTo(x, y);
ctx.stroke();
}
angle = 0
function loop() {
ctx.clearRect(0,0, 500, 500);
draw(80, 80, 70, 50, angle, "red", "lime")
draw(240, 60, 80, 30, angle*0.7, "black", "cyan")
draw(360, 80, 30, 70, angle*2, "white", "black")
angle += 0.05
}
setInterval(loop, 100)
<canvas height="160" width="500" id="c">

How to convert an Arc into a Line with HTML5 Canvas?

I would like to animate a circle into a line with the radius equaling the width, and I am wondering how I can do this with an Arc? Or perhaps there is a better way?
From
To
Here's my arc:
function drawStar(x,y,size,scale,opacity,ctx){
ctx.save();
ctx.beginPath();
ctx.arc(x,y,size+scale,0,size+scale * Math.PI,false);
ctx.globalAlpha = opacity
ctx.closePath();
setFillStyle('rgb(255,237,219)',ctx);
ctx.fill()
ctx.restore();
}
I tried using ctx.scale(n,1), however it does not keep the same radius(width) and it scales the collection of arcs as a whole (zoom in effect).
Use instead a wide line-width value with "round" lineCap and stroke():
var ctx = c.getContext("2d");
ctx.lineWidth = 50;
ctx.lineCap = "round";
ctx.moveTo(45 , 25);
ctx.lineTo(45.5, 25); // in IE11 this must be slightly offset
ctx.moveTo( 45, 100);
ctx.lineTo(150, 100);
ctx.stroke();
<canvas id=c></canvas>
Remember beginPath() for animation.
You can use Bezier Curves to 'transform' your arc.
There's some math involved in calculating the perfect ends of your stretched circle but I guessed and tweaked my numbers.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(40, 20);
ctx.lineTo(100, 20);
ctx.bezierCurveTo(130, 20, 130, 60, 100, 60);
ctx.lineTo(40, 60);
ctx.bezierCurveTo(10, 60, 10, 20, 40, 20);
ctx.stroke();
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(40, 80);
ctx.bezierCurveTo(68, 80, 68, 120, 40, 120);
ctx.bezierCurveTo(12, 120, 12, 80, 40, 80);
ctx.fill();
ctx.stroke();
<canvas id="myCanvas"></canvas>
You could draw the left and right halves of a circle using arc, then do a fillRect in between to connect them.
Edit: To elaborate on what I said earlier:
function init() {
let canvas = document.getElementById('myCanvas');
canvas.width = 400;
canvas.height = 400;
canvas.style.width = "400px";
canvas.style.height = "400px";
let ctx = canvas.getContext("2d");
function fillArc(ctx, cx, cy, r, startDeg, endDeg) {
ctx.beginPath();
ctx.arc(cx, cy, r, startDeg * Math.PI / 180, endDeg * Math.PI / 180);
ctx.fill();
}
function fillOval(ctx, cx, cy, r, sideLength, skipFirstArc) {
if (!skipFirstArc) {
fillArc(ctx, cx, cy, r, 90, 270);
}
ctx.fillRect(cx, cy - r, sideLength, r * 2);
fillArc(ctx, cx + sideLength, cy, r, 270, 90);
}
let sideLength = 0;
ctx.fillStyle = 'red';
function animateOval() {
if (sideLength === 100) {
ctx.clearRect(0, 0, 400, 400);
}
else {
fillOval(ctx, 30, 30, 25, sideLength, sideLength > 0);
}
++sideLength;
if (sideLength > 100) {
sideLength = 0;
}
}
setInterval(animateOval, 16);
}
Here's a Plunker with the above code running: http://plnkr.co/edit/vNqoUjPKg2lqC7JtYuEb?p=preview

How can I move just one of many shapes inside canvas by 50 pixels;

I have a project to do and I cannot do it until I understand how moving object inside canvas work.
I need to move one of the objects below by 50pixels to right.
Anyone willing to help me is greatly appreciated.
Meanwhile thank you very much in advance for your help or suggestions.
function canvasOneShape() {
//refers to the html canvasone id
var canvas = document.getElementById("canvasOne");
this.canvasOne.width = 945;
this.canvasOne.height = 650;
// draws the canvas in 2d
var ctx = canvas.getContext("2d");
// Set the fill colour to blue.
ctx.fillStyle = "blue"; //used like this instead of rgb due personal preference:)
// Create a filled rectangle at co-ordinates (10,10)
// with height and width set to 100.
ctx.fillRect(10, 10, 250, 330); //
// Here I draw the square
// Set the canvas up for drawing in 2D.
// Set the fill colour to blue.
ctx.fillStyle = "rgba(244, 244, 189,.5)";
ctx.fillRect(10, 50, 330, 250);
//draw my first circle
var midXone = canvas.width / 2; //x location
var midXtwo = canvas.height / 2; //y location
var radius = 60; //circle radius
ctx.beginPath();
ctx.arc(midXone, midXtwo, radius, 0, 2 * Math.PI, false);
ctx.fillStyle ="rgba(89, 192, 227,.4)";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = '#003300';
ctx.stroke();
//draw the second circle
var midX = canvas.width / 2.35; //x location
var midY = canvas.height / 2.35; //y location
var radius = 50; //circle radius
ctx.beginPath();
ctx.arc(midX, midY, radius, 0, 2 * Math.PI, false);
ctx.fillStyle ="rgba(66, 244, 89,.4)";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle ="rgba(255, 244, 9,.4)";
ctx.stroke();
//draw Square with circle inside
//square
ctx.fillStyle = "rgb(222, 33, 51)";
ctx.fillRect(550, 20, 300, 300);
//circle
ctx.beginPath();
ctx.arc(700, 170, 150, 0, 2 * Math.PI, false);
ctx.fillStyle ="rgba(66, 244, 89,.4)";
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = '#f44242';
ctx.stroke();
//The Pacman object
var radius = 100; //circle radius
var x = 100;
var y = 500;
ctx.beginPath();
ctx.arc(120, 500, radius, 1.85 * Math.PI, .15 * Math.PI, true);
//Draw mouth
ctx.lineTo(120, 500);
ctx.closePath();
ctx.fillStyle = "rgb(255, 255, 0)";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgb(0,0,0)';
ctx.stroke();
//draw eye
ctx.beginPath();
ctx.arc(x + 40, y - 40, 10, 0 * Math.PI, 2 * Math.PI, true);
ctx.fillStyle = "rgb(0,0,0)";
ctx.fill();
}
there are no layers or objects on a canvas(it's just a single layer of pixels), so, if you draw something, you overwrite what's underneath it. to move something to the right, you will need to have a function to draw everything behind it, and then variables to store the location (x and y) of the objects you want to move.
then, to move it, you clear the canvas with
ctx.clearRect(0, 0,width of canvas, height height of canvas);,
call the background function to draw everything behind it again, and the redraw your object in a different location.

Categories

Resources