Apply noise on circle according to mouseX and mouseY - javascript

I have created a blob with small points. I want my blob to show noise on its surface according to mouseX and mouseY. I want it to show high noise in the quadrant in which the mouse lies. I want it to be wavy. Below is my code.
var ctx = document.querySelector("canvas").getContext("2d");
var cx = 200;
var cy = 200;
var radius = 50;
var amp = 2;
var mouseX = 0;
var mouseY = 0;
document.querySelector("canvas").addEventListener("mousemove", function (e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
function drawTheBlob() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
ctx.beginPath();
ctx.strokeStyle = "#fff";
for (var a = 0; a < 360; a ++) {
var angle = a * Math.PI/180;
var x = cx + radius * Math.cos(angle) + Math.random() * amp;
var y = cy + radius * Math.sin(angle) + Math.random() * amp;
ctx.lineTo(x, y);
}
ctx.stroke();
ctx.closePath();
requestAnimationFrame(drawTheBlob);
}
drawTheBlob();
<canvas width="400" height="400"></canvas>

Adds a sin wave on the circle. Use ctx.arc to draw the flat part of the circle for speed as drawing many circles with lines will be slow. See code for comments on what is done.
var ctx = document.querySelector("canvas").getContext("2d");
ctx.lineWidth = 3;
ctx.lineJoin = "round";
var cx = 100;
var cy = 100;
var radius = 50;
var mouseX = 0;
var mouseY = 0;
const quadWidth = Math.PI / 2; // area of effect PI/2 is 90 degree
const steps = radius / quadWidth; // number steps around the circle matches 1 pixel per step,
const noiseAmpMax = 5; // in pixels
const noiseWaveMoveSpeed = 2; // speed of waves on circle in radians per second
const noiseWaveFreq = 16; // how many waves per 360 deg
document.querySelector("canvas").addEventListener("mousemove", function(e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
function drawTheBlob(time) { // time is passed from the requestAnimationFrame call
var amp = 0; // amplitude of noise
var wavePos = ((time / 1000) * Math.PI) * noiseWaveMoveSpeed;
var mouseDir = Math.atan2(mouseY - cy, mouseX - cx);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
ctx.beginPath();
ctx.strokeStyle = "#fff";
ctx.fillStyle = "red";
// draw arc for parts that have no noise as it is a log quicker
ctx.arc(cx, cy, radius, mouseDir + quadWidth / 2, mouseDir + Math.PI * 2 - quadWidth / 2);
for (var a = 0; a < 1; a += 1 / steps) {
var angle = (mouseDir - quadWidth / 2) + a * quadWidth;
var angDist = Math.abs(angle - mouseDir); // find angular distance from mouse
// as a positive value, it does not mater
// what the sign is
if (angDist < quadWidth / 2) { // is angle distance within the range of effect
// normalise the distance (make it 0 to 1)
amp = 1 - angDist / (quadWidth / 2);
} else {
amp = 0; // no noise
}
// amp will be zero if away from mouse direction and 0 to 1 the closer to
// mouse angle it gets.
// add a sin wave to the radius and scale it by amp
var dist = radius + Math.sin(wavePos + noiseWaveFreq * angle) * noiseAmpMax * amp;
var x = cx + dist * Math.cos(angle);
var y = cy + dist * Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.closePath(); // use close path to close the gap (only needed if you need to draw a line from the end to the start. It is not needed to match beginPath
ctx.fill();
ctx.stroke();
requestAnimationFrame(drawTheBlob);
}
requestAnimationFrame(drawTheBlob); // start this way so that you get the time argument
<canvas width="200" height="200"></canvas>
How it works.
Mouse direction
First we need the direction from the circle to the mouse. To do that we use the function Math.atan2 It takes the vector from the circle to the mouse and returns the direction in radians. The function is a little weird as it takes y first, then x.
var mouseDir = Math.atan2(mouseY - cy, mouseX - cx);
Draw arc to save CPU time
Now that we have the direction to the mouse we can draw the parts of the circle that has no noise using arc .
ctx.arc(cx, cy, radius, mouseDir + quadWidth / 2, mouseDir + Math.PI * 2 - quadWidth / 2);
The variable quadWidth is angular size of the noise bit so from the mouseDir we add half that angular width and draw the arc around to mouseDir plus 360deg take half the quadWidth.
Quick word on Radians
Almost all programming languages use radians to define angles, 360deg is equal to 2 * PI or 2 * 3.1415, which can be hard to get your head around, but there is good reason to use radians. For now just remember that a full circle in radians is 2 * Math.PI = 360deg, Math.PI = 180deg, Math.PI / 2 = 90deg, Math.PI / 4 = 45Deg and Math.PI / 180 = 1deg. You dont have to remember the digits just Math.PI is half a circle.
quadWidth from above is a constant defined as const quadWidth = Math.PI / 2; which is 90deg.
The for loop
The for loop only draws the (Math.PI / 2) 90deg section around the mouseDir, from 45 deg left to 45 right. or whatever you set quadWidth to.
for (var a = 0; a < 1; a += 1 / steps) {
I loop from 0 to 1 the number of steps that give a reasonably smooth curve. We can find what part of the noisy arc we are drawing by multiplying the value a *
quadWidth and adding that to the mouseDir - quadWidth / 2. This means that we start at mouseDir - 45deg and move clock wise to mouseDir + 45deg
var angle = (mouseDir - quadWidth / 2) + a * quadWidth;
Next i find how far that angle is from the mouseDir (could optimize it here a bit here but this way is a little more flexible, if you want to draw more noise on the other part of the arc)
var angDist = Math.abs(angle - mouseDir);
If that number is less than quadWidth / 2 convert the value to the range 0 to 1 where 0 is at the angle furthest from the mouse direction and 1 closest.
if (angDist < quadWidth / 2) {
amp = 1 - angDist / (quadWidth / 2);
} else {
amp = 0;
}
The sin wave
Now we calculate the radius of the circle at the current angle and add a sin wave to it. First the radius then the sin wave multiplied by the amp calculated in the last step. Where amp is zero none of the sin wave is added, where amp is 1 (in the direction of the mouse) the full sin wave is added.
var dist = radius + Math.sin(wavePos + noiseWaveFreq * angle) * noiseAmpMax * amp
The values wavePos, noiseWaveFreq, and noiseAmpMax control the animation of the sin wave. Play around with these values to get a feel of what they do, wavePos is calculated based on the time at the start of the function.
With dist we can calculate the x,y position for the next line of the circle
var x = cx + dist * Math.cos(angle);
var y = cy + dist * Math.sin(angle);
ctx.lineTo(x, y);
Experiment
I added some constants
const quadWidth = Math.PI / 2; // area of effect PI/2 is 90 degree
const steps = radius / quadWidth; // number steps around the circle matches 1 pixel per step,
const noiseAmpMax = 5; // in pixels
const noiseWaveMoveSpeed = 2; // speed of waves on circle in radians per second
const noiseWaveFreq = 16; // how many waves per 360 deg
To get a understanding what they do experiment and change the numbers to see what happens.

Related

Simplest way to make a spiral line go through an arbitrary point?

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
let last = 1
let start = 1
let i = 0
let origin = [250, 250]
for (let i2 = 0; i2 < 20; i2++) {
ctx.ellipse(...origin, start, start, Math.PI / 2 * i, 0, Math.PI / 2);
i++
i %= 4
if (i == 1) origin[1] -= last
else if (i == 2) origin[0] += last
else if (i == 3) origin[1] += last
else if (i == 0) origin[0] -= last;
[last, start] = [start, start + last]
}
ctx.stroke();
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 7
ctx.strokeStyle = "red";
ctx.lineTo(400, 400)
ctx.stroke()
<canvas width="500" height="500" style="border:1px solid #000000;"></canvas>
What is the simplest way to make the spiral line go through an arbitrary point in the canvas? For example 400x 400y. I think adjusting the initial start and last values based on some calculation could work. The only difference between the first code snippet and the second one is the initial last and start variables. Other solutions that rewrite the entire thing are welcome too.
const canvas = document.querySelector( 'canvas' );
const ctx = canvas.getContext( '2d' );
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
let last = 0.643
let start = 0.643
let i = 0
let origin = [250,250]
for (let i2=0; i2<20; i2++) {
ctx.ellipse(...origin, start, start, Math.PI/2 *i , 0, Math.PI /2);
i++
i%=4
if (i==1) origin[1] -= last
if (i==2) origin[0] += last
if (i==3) origin[1] += last
if (i==0) origin[0] -= last
;[last, start] = [start, start + last]
}
ctx.stroke();
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 7
ctx.strokeStyle = "red";
ctx.lineTo(400, 400)
ctx.stroke()
<canvas width="500" height="500" style="border:1px solid #000000;"></canvas>
I am not sure how you want the spiral to intercept the point. There are twp options.
Rotate the spiral to intercept point
Scale the spiral to intercept point
This answer solves using method 1. Method 2 has some problems as the number of turns can grow exponentially making the rendering very slow if we don't set limits to where the point of intercept can be.
Not a spiral
The code you provided does not draw a spiral in the mathematical definition but rather is just a set of connected ellipsoids.
This means that there is more than one function that defines a point on these connected curves. To solve for a point will require some complexity as each possible curve must be solved and the solution then vetted to locate the correct curve. On top of that ellipsoids I find to result in some very ugly math.
A spiral function
If we define the curve as just one function where the spiral radius is defined by the angle, it is then very easy to solve.
The function for the radius can be a simplified polynomial in the form Ax^P+C where x is the angle, P is the spiralness (for want of a better term), A is the scale (again for want of a better term) and C is the start angle
C is there if you want to make the step angle of the spiral be a set length eg 1 px would be angle += 1 / (Ax^P+C) If C is 0 then 1/0 would result in an infinite loop.
Drawing the spiral
As defined above there are many types of spirals that can be rendered so there should be one that is close to the spiral you have.
Any point on the spiral is found as follows
x = cos(angle) * f(angle) + origin.x
y = sin(angle) * f(angle) + origin.y
where f is the poly f(x) = Ax^P+C
The following function draws a basic linear spiral f(x) = 1*x^1+0.1
function drawSpiral(origin) {
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.beginPath();
let i = 0;
while (i < 5) {
const r = i + 0.1; // f(x) = 1*x^1+0.1
ctx.lineTo(
Math.cos(i) * r + origin.x,
Math.sin(i) * r + origin.y
);
i += 0.1
}
ctx.stroke();
}
Solve to pass though point
To solve for a point we convert the point to a polar coordinate relative to the origin. See functions pointDist , pointAngle. We then solve for Ax^P+C = dist in terms of x (the angle) and dist the distance from the origin. Then subtract the angle to the point to get the spirals orientation. (NOTE ^ means to power of, rest of answer uses JavaScripts **)
To solve an arbitrary polynomial can become rather complex that is why I used the simplified version.
The function A * x ** P + C = pointDist(point) needs to be rearranged in terms of pointDist(point).
This gives x = ((pointDist(point) - C) / A) ** (1 / P)
And then subtract the polar angle x = ((pointDist(point)- C) / A) ** (1 / P) - pointAngle(point) and we have the angle offset so that the spiral will intercept the point.
Example
A working example in case the above was TLDR or had too much math like jargon.
The example defines a spiral via the coefficients of the radius function A, C, and P.
There are 3 example spirals Black, Blue, and Green.
A spiral is drawn until its radius is greater than the diagonal distance to the canvas corner. The origin is the center of the canvas.
The point to intercept is set by the mouse position over the page.
The spirals are only rendered when the mouse position changes.
The solution for the simplified polynomial is shown in steps in the function startAngle.
While I wrote the code I seam to have lost the orientation and thus needed to add 180 deg to the start angle (Math.PI) or the point ends up midway between spiral arms.
const ctx = canvas.getContext("2d");
const mouse = {x : 0, y : 0}, mouseOld = {x : undefined, y : undefined};
document.addEventListener("mousemove", (e) => { mouse.x = e.pageX; mouse.y = e.pageY });
requestAnimationFrame(loop);
const TURNS = 4 * Math.PI * 2;
let origin = {x: canvas.width / 2, y: canvas.height / 2};
scrollTo(0, origin.y - innerHeight / 2);
const maxRadius = (origin.x ** 2 + origin.y ** 2) ** 0.5; // dist from origin to corner
const pointDist = (p1, p2) => Math.hypot(p1.x - p2.x, p1.y - p2.y);
const pointAngle = (p1, p2) => Math.atan2(p1.y - p2.y, p1.x - p2.x);
const radius = (x, spiral) => spiral.A * x ** spiral.P + spiral.C;
const startAngle = (origin, point, spiral) => {
const dist = pointDist(origin, point);
const ang = pointAngle(origin, point);
// Da math
// from radius function A * x ** P + C
// where x is ang
// A * x ** P + C = dist
// A * x ** P = dist - C
// x ** P = (dist - C) / A
// x = ((dist - C) / A) ** (1 / p)
return ((dist - spiral.C) / spiral.A) ** (1 / spiral.P) - ang;
}
// F for Fibonacci
const startAngleF = (origin, point, spiral) => {
const dist = pointDist(origin, point);
const ang = pointAngle(origin, point);
return (1 / spiral.P) * Math.log(dist / spiral.A) - ang;
}
const radiusF = (x, spiral) => spiral.A * Math.E ** (spiral.P * x);
const spiral = (P, A, C, rFc = radius, aFc = startAngle) => ({P, A, C, rFc, aFc});
const spirals = [
spiral(2, 1, 0.1),
spiral(3, 0.25, 0.1),
spiral(0.3063489,0.2972713047, null, radiusF, startAngleF),
spiral(0.8,4, null, radiusF, startAngleF),
];
function drawSpiral(origin, point, spiral, col) {
const start = spiral.aFc(origin, point, spiral);
ctx.strokeStyle = col;
ctx.beginPath();
let i = 0;
while (i < TURNS) {
const r = spiral.rFc(i, spiral);
const ang = i - start - Math.PI;
ctx.lineTo(
Math.cos(ang) * r + origin.x,
Math.sin(ang) * r + origin.y
);
if (r > maxRadius) { break }
i += 0.1
}
ctx.stroke();
}
loop()
function loop() {
if (mouse.x !== mouseOld.x || mouse.y !== mouseOld.y) {
ctx.clearRect(0, 0, 500, 500);
ctx.lineWidth = 1;
drawSpiral(origin, mouse, spirals[0], "#FFF");
drawSpiral(origin, mouse, spirals[1], "#0FF");
ctx.lineWidth = 4;
drawSpiral(origin, mouse, spirals[2], "#FF0");
drawSpiral(origin, mouse, spirals[3], "#AF0");
ctx.beginPath();
ctx.lineCap = "round";
ctx.lineWidth = 7;
ctx.strokeStyle = "red";
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
Object.assign(mouseOld, mouse);
}
requestAnimationFrame(loop);
}
canvas { position : absolute; top : 0px; left : 0px; background: black }
<canvas id="canvas" width = "500" height = "500"></canvas>
UPDATE
As requested in the comments
I have added the Fibonacci spiral to the example
The radius function is radiusF
The function to find the start angle to intercept a point is startAngleF
The two new Fibonacci spirals are colored limeGreen and Yellow
To use the Fibonacci spiral you must include the functions radiusF and startAngleF when defining the spiral eg spiral(1, 1, 0, radiusF, startAngleF)
Note the 3rd argument is not used and is zero in the eg above. as I don't think you will need it

how i get a coordinate of each rectangle?

i have tried this,
public drawNumbers(ctx, x1, y1, length, count) {
let angle = 0;
for (let i = 0; i <= count; i++ ) {
angle += 2 * Math.PI / (count );
const x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.fillRect(x2, y2, 10, 20);
ctx.stroke();
}
}
this.canvas.drawNumbers(ctx, this.midX, this.midY, 160, 60);
output:
expected result:
i want to calculate a four coordinate(rectangle) of rotated axis.
How do i detect click event on each rectangle?
Using setTransform
Salix alba answer is a solution though a few too many steps.
It can be done in a single transform using setTransform and applying the translate and rotations in one step. Also the second translation is where you draw the box relative to its origin. When using transforms always draw objects around the center of rotation.
ctx.strokeRect(-10,-10,20,20); // rotation is always around 0,0
const ctx = canvas.getContext("2d");
const centerX = 250;
const centerY = 250;
const radius = 200;
const boxWidth = 10;
const bobLength = 20;
// draw boxs around circle center at cx,cy and radius rad
// box width bw, and box height bh
// spacing optional is the distance between boxes
function drawCircleOfBoxes(cx,cy,rad,bw,bh,spacing = 5){
var steps = ((rad - bw /2) * Math.PI * 2) / (bw + spacing) | 0; // get number boxes that will fit circle
ctx.beginPath();
for(var i = 0; i < steps; i ++){
const ang = (i / steps) * Math.PI * 2;
var xAxisX = Math.cos(ang); // get the direction of he xAxis
var xAxisY = Math.sin(ang);
// set the transform to circle center x Axis out towards box
// y axis at 90 deg to x axis
ctx.setTransform(xAxisX, xAxisY, -xAxisY, xAxisX, cx, cy);
// draw box offset from the center so its center is distance radius
ctx.rect(rad - bh / 2, -bw / 2, bh, bw);
}
ctx.fill();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
ctx.fillStyle = "#FCD";
ctx.strokeStyle = "#000";
drawCircleOfBoxes(centerX, centerY, radius, boxWidth, bobLength);
<canvas id="canvas" width="500" height="500"></canvas>
Manually apply the transform to a point
If you wish to transform the box in code you can use the transform applied in the above and apply it directly to a set of points. You can not apply it to the ctx.rect function that needs the API transform.
To transform a point px,py you need the the direction of the rotated x axis
const xAx = Math.cos(dirOfXAxis);
const xAy = Math.sin(dirOfXAxis);
You can then move the point px distance along the xAxis and then turn 90 deg and move py distance along the y axis
var x = px * xAx; // move px dist along x axis
var y = px * xAy;
x += py * -xAy; // move px dist along y axis
y += py * xAx;
Then just add the translation
x += translateX;
y += translateY;
Or done in one go
var x = px * xAx - py * xAy + translateX; // move px dist along x axis
var y = px * xAy + py * xAx + translateY;
The snippet shows it in action
const ctx = canvas.getContext("2d");
const centerX = 250;
const centerY = 250;
const radius = 200;
const boxWidth = 10;
const boxLength = 20;
// draw boxs around circle center at cx,cy and radius rad
// box width bw, and box height bh
// spacing optional is the distance between boxes
function drawCircleOfBoxes(cx,cy,rad,bw,bh,spacing = 5){
var points = [ // setout points of box with coord (0,0) as center
{x : bh / 2, y : -bw / 2},
{x : bh / 2 + bh, y : -bw / 2},
{x : bh / 2 + bh, y : -bw / 2 + bw},
{x : bh / 2, y : -bw / 2 + bw},
];
var steps = (((rad - bw /2) * Math.PI * 2) / (bw + spacing) )+ 4| 0; // get number boxes that will fit circle
ctx.beginPath();
for(var i = 0; i < steps; i ++){
const ang = (i / steps) * Math.PI * 2;
const xAx = Math.cos(ang); // get the direction of he xAxis
const xAy = Math.sin(ang);
var first = true
for(const p of points){ // for each point
// Apply the transform to the point after moving it
// to the circle (the p.x + rad)
const x = (p.x + rad) * xAx - p.y * xAy + cx;
const y = (p.x + rad) * xAy + p.y * xAx + cy;
if(first){
ctx.moveTo(x,y);
first = false;
}else{
ctx.lineTo(x,y);
}
}
ctx.closePath();
}
ctx.fill();
ctx.stroke();
}
ctx.fillStyle = "#CFD";
ctx.strokeStyle = "#000";
for(var i = boxLength + 5; i < radius; i += boxLength + 5){
drawCircleOfBoxes(centerX, centerY, i , boxWidth, boxLength);
}
<canvas id="canvas" width="500" height="500"></canvas>
To get rotated rectangles you need to use the transform() method of the graphics context.
Imagine a set of axis at the top left of the drawing area. Any drawing will be done relative to these axis which we can move with transform.
To translate by xshift, yshift
ctx.transform(1,0,0,1, xshift, yshift);
ctx.fillRect(0,0,100,100);
To rotate by angle ang in radians
ctx.transform(Math.cos(ang),Math.sin(ang),
-Math.sin(ang),Math.cos(ang), 0,0);
We can combine things with three transformations. The first moves the origin to the center of the circle. Then rotate the axes about this point,
then shift the axes to where you want the shape to appear. Finally, draw the shape.
for(deg = 0; deg < 360; deg+=20) {
ctx.setTransform(1,0,0,1,0,0); // reset transformation
ang = deg * Math.PI/180;
ctx.transform(1,0,0,1,100,100); // shift origin
ctx.transform(Math.cos(ang),Math.sin(ang),
-Math.sin(ang),Math.cos(ang), 0,0);
ctx.transform(1,0,0,1,50,0);
ctx.fillRect(0,0,30,10);
}
You can achieve the same this using the translate and rotate
for(deg = 0; deg < 360; deg+=20) {
ctx.setTransform(1,0,0,1,0,0); // reset transformation
ang = deg * Math.PI/180;
ctx.translate(100,100); // shift origin
ctx.rotate(ang);
ctx.translate(50,0);
ctx.fillRect(0,0,30,10);
}

Creating dashes in a pattern of a circle using Javascript Math

I don't understand a lot about the code below and I would like someone to break it down piece by piece.
$(function() {
var centerX = 200,
centerY = 200,
radius = 100,
width = 15,
angles = []
function draw() {
var ctx = document.getElementById("canvas").getContext("2d");
var angle;
//why 180
for (var i = 0; i < 180; i += 10) {
//is the "angle" increasing on every iteration? can you demostrate this please
angle = 2.1 + (i * Math.PI / 90);
angles.push(angle)
ctx.beginPath();
ctx.moveTo(
centerX + Math.sin(angle) * radius,
centerY + Math.cos(angle) * radius
);
ctx.lineTo(
centerX + Math.sin(angle) * (radius + width),
centerY + Math.cos(angle) * (radius + width)
);
ctx.closePath();
ctx.stroke();
}
}
draw()
console.log(angles)
var str = "";
angles.forEach(function(e, i) {
str += " " + i + " : " + e + "|| Math.sin = " + Math.sin(e) + "|| Math.sin * radius = " + Math.sin(e) * radius
$(".display").html(str)
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>
<div class="display"></div>
like for this part angle = 2.1 + (i * Math.PI / 90); I see i is incrementing by 10 and the author is multiplying it by Math.PI / 90 which is equal to 0.0348888888888889. I know Math.PI is 180 degrees, but were not doing 180/90. We're increasing the number 2.1 by small amount. I can't put all the pieces together.
and in for(var i = 0; i < 180; i += 10){ why did the author choose 180. I know 180 degrees is a half a circle is that why they chose it?
And I always see in other code people use cos for the x coord and sin for the y coord. It doesn't look like the author uses it like the way i just described. could you elaborate.
Any help would be appreciated!
EDIT: I'm also wondering when we use for(var i = 0; i < 180; i += 10){ we get the dashes in a full circle and when i do i < 90 we get a half a circle but the half circle is not straight like against an x or y axis its on an angle. why is it not on the axis? why is it on an angle?
Let's start with SOH CAH TOA (link).
Given Right angled triangle (one side is 90 deg)..
Triangle has an angle.
Triangle has a side that's Opposite (O) to the angle.
Triangle has side that touches both, the angle and the right angle, called (A) Adjacent Side.
Triangle has a side that is called Hypotenuse (H). This is the side that also touches the Angle but doesn't make a right angle with Opposite side.
Now to find any side you need to know at minimum the angle and 1 another side.
Ex: You know angle, Q, is 40deg and Hypotenuse is 10. So what is Adjacent;
Looking at SOH CAH TOA. I See that I know H, and need to know A. CAH has both. So I choose CAH.
Cos Q = Adj/Hyp
Now if you put this Triangle inside circle. Then Hyp will become the radius, like so:
Cos Q = Adj/radius
Now to draw line going outward from a circle i need to know a starting point and the ending point of a line AND i need to make those points align with circle angles.
To get starting point i can just use circle's boundary.
So to get x,y for a point on circle boundary i solve this equation further..
Cos Q * radius = Adj
Cos Q * radius = x //adj is x
& for y...
SOH
Sin Q = Opp/Hyp
Sin Q = Opp/radius
Sin Q * radius = Opp
Sin Q * radius = y
So
x = Cos Q * radius
y = Sin Q * radius
or in js..
var x = Math.cos(angle) * radius;
var y = Math.sin(angle) * radius;
Now we have points that follow a circle's boundary. But for there to be line like we want, we need two points.
This code simply puts in a bigger radius, which gives bigger circle, which gives 2nd points what we needed.
ctx.lineTo(
centerX + Math.sin(angle) * (radius + width),
centerY + Math.cos(angle) * (radius + width));
Code Formatted to be clear:
var centerX = 200,
centerY = 200,
radius = 100,
width = 15,
angles = [],
ctx = document.getElementById("canvas").getContext("2d");
function draw() {
var angle;
for (var i = 0; i < 180; i += 10) {
angle = 2.1 + (i * Math.PI / 90);
ctx.beginPath();
ctx.moveTo(
centerX + Math.sin(angle) * radius,
centerY + Math.cos(angle) * radius);
ctx.lineTo(
centerX + Math.sin(angle) * (radius + width),
centerY + Math.cos(angle) * (radius + width));
ctx.closePath();
ctx.stroke();
}
}
draw();
The code you cite is a bit awkward.
To navigate around a circle, it would be more clear to increment i from 0 to 360 (as in 360 degrees).
for(var i=0; i<360; i++){
Instead, the coder increments from 0 to 180, but then they compensate for halving the 360 degrees by doubling the formula that converts degrees to radians.
// this is the usual conversion of degrees to radians
var radians = degrees * Math.PI / 180;
// This "compensating" conversion is used by the coder
// This doubling of the conversion compensates for halving "i" to 180
var radians = degrees * Math.PI / 90;
A clearer factoring of the iteration would look like this:
// the conversion factor to turn degrees into radians
var degreesToRadians = Math.PI / 180;
// navigate 360 degrees around a circle
for(var i=0; i<360; i++){
// use the standard degrees-to-radians conversion factor
var angle = i * degreesToRadians;
// This roughly rotates the angle so that it starts
// at the 12 o'clock position on the circle
// This is unnecessary if you're navigating completely
// around the circle anyway (then it doesn't matter where you start)
angle += 2.1;
....
}
And...
The code is intentionally drawing lines that radiate away from the circles circumference. The coder is not attempting to follow the circumference at all.

Raphael.js has weird behavior on drag

I have circle with a line from the center to the edge of the circle. The user can click on and drag the line and get the degrees from wherever they set the line. I'm using the code from the answer here and have adjusted the code accordingly.
Everything works fine, but the farther down the page the element affected by raphael is, the farther outside of the circle the mouse has to be in order to drag the line. jsfiddle
var canvas = Raphael('pivot', 0, 0, 320, 320);
var clock = canvas.circle(200, 150, 100).attr("stroke-width", 2);
canvas.circle(200, 150, 3).attr("fill", "#000");
var angleplus = 360,
rad = Math.PI / 180,
cx = 200,
cy = 150,
r = 90,
startangle = -90,
angle = 90,
x, y, endangle;
for (i = 1; i < 5; i++) {
endangle = startangle + angle;
x = cx + r * Math.sin(endangle * rad);
y = cy - r * Math.cos(endangle * rad);
canvas.text(x, y, endangle);
startangle = endangle;
}
var hand = canvas.path("M200 50L200 150").attr("stroke-width", 5);
hand.drag( move, start, end );
function move (dx,dy,x,y) {
var pt = this.node.ownerSVGElement.createSVGPoint();
pt.x = x;
pt.y = y;
var angle = ( 90 + Math.atan2(pt.y - clock.attr('cy') - 5, pt.x - clock.attr('cx') - 5 ) * 180 / Math.PI + 360) % 360;
this.rotate(angle, 200,150);
this.angle = angle;
}
function start() {
};
function end() {
alert(parseInt(this.angle));
}
I'm wondering why this happens and if it's even fixable?
Here is an updated original one, a bit added and another bit of redundancy removed, and I've added an extra fiddle to the original answer to reflect it.
The key bits I've added are...
var cpt = clock.node.ownerSVGElement.createSVGPoint();
cpt.x = clock.attr('cx');
cpt.y = clock.attr('cy');
cpt = cpt.matrixTransform(clock.node.getScreenCTM());
The centre of the clock has now moved, so using cx of the circle alone, doesn't really make sense relative to the mouse event. We need to take into account any offset and transforms on the screen to the clock.
We can get this with getScreenCTM (get the matrix from element to the screen), and we can use that matrix with the original cx, cy to figure out where it is on the screen.
Then later, instead of cx, cy, we use the new adjusted coordinates cpt.x/y
var angle = ( 90 + Math.atan2(y - cpt.y - 5, x - cpt.x - 5 ) * 180 / Math.PI + 360)
jsfiddle - drag any hand

Drawing a spiral on an HTML canvas using JavaScript

I have searched and haven't found anything really on how to draw spirals in canvas using JavaScript.
I thought it might be possible to do it with the bezier curve and if that didn't work use lineTo(), but that seemed a lot harder.
Also, to do that I'm guessing I would have to use trigonometry and graphing with polar coordinates and its been a while since I did that. If that is the case could you point me in the right direction on the math.
The Archimedean spiral is expressed as r=a+b(angle). Convert that into x, y coordinate, it will be expressed as x=(a+b*angle)*cos(angle), y=(a+b*angle)*sin(angle). Then you can put angle in a for loop and do something like this:
for (i=0; i< 720; i++) {
angle = 0.1 * i;
x=(1+angle)*Math.cos(angle);
y=(1+angle)*Math.sin(angle);
context.lineTo(x, y);
}
Note the above assumes a = 1 and b = 1.
Here is a jsfiddle link: http://jsfiddle.net/jingshaochen/xJc7M/
This is a slightly-changed, javascript-ified version of a Java spiral I once borrowed from here
It uses lineTo() and its not all that hard.
<!DOCTYPE HTML>
<html><body>
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #c3c3c3;"></canvas>
<script type="text/javascript">
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var centerX = 150;
var centerY = 150;
cxt.moveTo(centerX, centerY);
var STEPS_PER_ROTATION = 60;
var increment = 2*Math.PI/STEPS_PER_ROTATION;
var theta = increment;
while( theta < 40*Math.PI) {
var newX = centerX + theta * Math.cos(theta);
var newY = centerY + theta * Math.sin(theta);
cxt.lineTo(newX, newY);
theta = theta + increment;
}
cxt.stroke();
</script></body></html>
Here's a function I wrote for drawing Archimedean spirals:
CanvasRenderingContext2D.prototype.drawArchimedeanSpiral =
CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ||
function(centerX, centerY, stepCount, loopCount,
innerDistance, loopSpacing, rotation)
{
this.beginPath();
var stepSize = 2 * Math.PI / stepCount,
endAngle = 2 * Math.PI * loopCount,
finished = false;
for (var angle = 0; !finished; angle += stepSize) {
// Ensure that the spiral finishes at the correct place,
// avoiding any drift introduced by cumulative errors from
// repeatedly adding floating point numbers.
if (angle > endAngle) {
angle = endAngle;
finished = true;
}
var scalar = innerDistance + loopSpacing * angle,
rotatedAngle = angle + rotation,
x = centerX + scalar * Math.cos(rotatedAngle),
y = centerY + scalar * Math.sin(rotatedAngle);
this.lineTo(x, y);
}
this.stroke();
}
there is a fine free tool that will help if you have illustrator
ai2canvas
it will create all the curves to javascript in html canvas tag for you!
(if you are looking for archmedes spiral than you will first have to get it from coreldraw and copy that to illustrator, because the default spiral tool enlarges the angle with each point)
this is example of drawing spiral using function below:
spiral(ctx, {
start: {//starting point of spiral
x: 200,
y: 200
},
angle: 30 * (Math.PI / 180), //angle from starting point
direction: false,
radius: 100, //radius from starting point in direction of angle
number: 3 // number of circles
});
spiral drawing code:
spiral = function(ctx,obj) {
var center, eAngle, increment, newX, newY, progress, sAngle, tempTheta, theta;
sAngle = Math.PI + obj.angle;
eAngle = sAngle + Math.PI * 2 * obj.number;
center = {
x: obj.start.x + Math.cos(obj.angle) * obj.radius,
y: obj.start.y + Math.sin(obj.angle) * obj.radius
};
increment = 2 * Math.PI / 60/*steps per rotation*/;
theta = sAngle;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
while (theta <= eAngle + increment) {
progress = (theta - sAngle) / (eAngle - sAngle);
tempTheta = obj.direction ? theta : -1 * (theta - 2 * obj.angle);
newX = obj.radius * Math.cos(tempTheta) * progress;
newY = obj.radius * Math.sin(tempTheta) * progress;
theta += increment;
ctx.lineTo(center.x + newX, center.y + newY);
}
ctx.stroke();
};
The following code approximates a spiral as a collection of quarters of a circle each with a slightly larger radius. It might look worse than an Archimedes spiral for small turning numbers but it should run faster.
function drawSpiral(ctx, centerx, centery, innerRadius, outerRadius, turns=2, startAngle=0){
ctx.save();
ctx.translate(centerx, centery);
ctx.rotate(startAngle);
let r = innerRadius;
let turns_ = Math.floor(turns*4)/4;
let dr = (outerRadius - innerRadius)/turns_/4;
let cx = 0, cy = 0;
let directionx = 0, directiony = -1;
ctx.beginPath();
let angle=0;
for(; angle < turns_*2*Math.PI; angle += Math.PI/2){
//draw a quarter arc around the center point (x, cy)
ctx.arc( cx, cy, r, angle, angle + Math.PI/2);
//move the center point and increase the radius so we can draw a bigger arc
cx += directionx*dr;
cy += directiony*dr;
r+= dr;
//rotate direction vector by 90 degrees
[directionx, directiony] = [ - directiony, directionx ];
}
//draw the remainder of the last quarter turn
ctx.arc( cx, cy, r, angle, angle + 2*Math.PI*( turns - turns_ ))
ctx.stroke();
ctx.restore();
}
Result:

Categories

Resources