Reputation: 41
I have a code to draw spiral with points
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 400;
var centerY = 400;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText("o", newX, newY);
count++;
if (count % 4 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;"></canvas>
Notice how many time i change the increment value, there're always a line that have more or less points than others
increment = 5/32;
Is this possible to draw a perfect spiral with all lines has the same lenght with each other?
Upvotes: 3
Views: 273
Reputation: 54026
The circumference of a circle is 2 * PI * radius
.
We can use this to determine what angle step to make to cover a distance on the circumference. Thus the angle that sweeps a given distance along the curve is distance / radius
(Note this is not the straight line distance, but the distance ALONG the CURVE)
Though you are creating a spiral and the distance for an angle step is a little longer (as the line moves outward) the approximation will suffice for the human eye.
So changing your code with the following steps
increment
distance
to radius
maxRadius = 350
to fit the canvaslineLength
to set the approx distance between each point in pixels.theta
to angle
(we are programmers not mathematicians)radiusMin
defines the min radius (starting radius)radiusScale
defines the rate per turn in pixel that the spiral moves out.count
as its no longer neededradiusScale / (Math.PI * 2)
so the radius is radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
angle += lineLength / radius;
which is derived from the circumference formula (and why we use radians for angles)cxt
to the more idiomatic ctx
moveTo
to move to the start of each circle.ctx.arc
to define the circlectx.stroke()
The code below. As I have only approximated your spiral you can play with the constants to make it fit your needs. Also Note that for the inner spiral, longer line distances will not work as well.
const ctx = myCanvas.getContext("2d");
const centerX = 400;
const centerY = 400;
const markRadius = 2; // radius of each circle mark in pixels
const maxRadius = 350; // 50 pixel boarder
const lineLength = 20; // distance between points in pixels
const radiusScale = 80; // how fast the spiral moves outward per turn
const radiusMin = 10; // start radius
var angle = 0, radius = 0;
ctx.lineWidth = 1;
ctx.strokeStye = "black";
ctx.beginPath();
while (radius < maxRadius) {
radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
angle += lineLength / radius;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
ctx.moveTo(x + markRadius, y);
ctx.arc(x, y, markRadius, 0, Math.PI * 2);
}
ctx.stroke();
<canvas id="myCanvas" width="800" height="800" style="border:1px solid #c3c3c3;"></canvas>
If you want separate spirals then it is a minor modification of the above
armCount
spiralRate
requestAnimationFrame(mainLoop);
const ctx = myCanvas.getContext("2d");
const centerX = 200;
const centerY = 200;
const markRadius = 2; // radius of each circle mark in pixels
const maxRadius = 190; // 50 pixel boarder
const armCount = 8; // Number of arms
const radiusScale = 8; // how fast the spiral moves outward per turn
const radiusMin = 10; // start radius
function drawSpiral(spiralRate) { // spiralRate in pixels per point per turn
var angle = 0, radius = radiusMin;
ctx.lineWidth = 1;
ctx.strokeStye = "black";
ctx.beginPath();
while (radius < maxRadius) {
angle += (Math.PI * 2) / armCount + (spiralRate/ radius);
radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
ctx.moveTo(x + markRadius, y);
ctx.arc(x, y, markRadius, 0, Math.PI * 2);
}
ctx.stroke();
}
function mainLoop(time) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawSpiral(Math.sin(time / 4000) * 2); // occilate spiral rate every ~ 24 seconds
requestAnimationFrame(mainLoop);
}
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
Upvotes: 1
Reputation: 1382
There are a quite a few issues here. Like @Anytech said, you need to first decide how many arms (strings of dots) you want. In your screenshot, it looks like you have 5 arms, but you probably got that by accident. I've replaced the "o" with the distance to help visualize the problem:
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 200;
var centerY = 200;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText(distance, newX, newY);
count++;
if (count % 4 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
As you can see, the first four dots are at distance 10, the 5th one is at distance 20, from there, you've already broken the rhythm.
Assuming you still want 5 arms, increase the distance every 5 dots by checking count % 5 === 0
, instead of count % 4 === 0
.
var c = document.getElementById("myCanvas");
var cxt = c.getContext("2d");
var centerX = 200;
var centerY = 200;
cxt.moveTo(centerX, centerY);
var count = 0;
var increment = 3/32;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 4 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 4 * Math.PI * increment );
cxt.fillText(distance, newX, newY);
count++;
if (count % 5 === 0) {
distance = distance + 10;
}
}
cxt.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
Also, a full circle is 2 * Math.PI
. If you can use Math.cos((theta) * 2 * Math.PI * increment)
, increment becomes the arc the dot will travel after each paint. If increment is 1/5
, you will get five straight lines. If increment is a little bit more than 1/5
, you will get the curve effect you want.
And one final detail, we usually call context ctx
, instead of cxt
. Combining all of the above, the output looks like this
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var centerX = 200;
var centerY = 200;
ctx.moveTo(centerX, centerY);
var count = 0;
var increment = 1.02/5;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 2 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 2 * Math.PI * increment );
ctx.fillText('o', newX, newY);
count++;
if (count % 5 === 0) {
distance = distance + 10;
}
}
ctx.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
EDIT
After having a chat with the question raiser, I realize the problem also has to to with fillText
using the bottom-left corner of the string as the starting point for the paint. To address the issue, we must plot actual circles, instead of letter 'o'.
Here is the final result (concentric circles added to show perfect symmetry)
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var centerX = 200;
var centerY = 200;
ctx.moveTo(centerX, centerY);
var count = 0;
var increment = 1.05/5;
var distance = 10;
for (theta = 0; theta < 50; theta++) {
var newX = centerX + distance * Math.cos((theta) * 2 * Math.PI * increment );
var newY = centerY + distance * Math.sin(((theta)) * 2 * Math.PI * increment );
ctx.textAlign = "center";
//ctx.fillText('o', newX, newY); <== this will be off-center
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.arc(newX, newY, 2, 0, Math.PI * 2, true)
ctx.stroke();
count++;
if (count % 5 === 0) {
ctx.strokeStyle = "#cccccc";
ctx.beginPath();
ctx.arc(200, 200, distance, 0, Math.PI * 2, true)
ctx.stroke();
distance = distance + 10;
}
}
ctx.stroke();
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;"></canvas>
Upvotes: 2
Reputation: 41
After changing the code to increment in number form you can see that the "spiral" is not generated in the expected line.
You will ned to work out how many spiral arms you want and calculate that with the stoplimit.
The increment value is also never used.
// var increment = 6/45;
var stoplimit = 51;
https://jsfiddle.net/Anytech/spzyufev/4/
Upvotes: 0