Han Tran
Han Tran

Reputation: 41

How can i draw a perfect multiples lines spiral with loop & points?

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>

enter image description here enter image description here

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;

enter image description here

enter image description here

Is this possible to draw a perfect spiral with all lines has the same lenght with each other?

Upvotes: 3

Views: 273

Answers (3)

Blindman67
Blindman67

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

  • Remove increment
  • Change distance to radius
  • Rather than limit the number of turns, we will use the radius to limit the max radius of the spiral. Adding maxRadius = 350 to fit the canvas
  • Add lineLength to set the approx distance between each point in pixels.
  • Change the for loop to a while loop as we are going to step angle inside the loop.
  • Rename theta to angle (we are programmers not mathematicians)
  • Rather than use a character to draw each point we will create a path of arcs so that the positioning can be precise. That will add some 2D context setup code.
  • Rather than step out every 4 points (as that no longer will work) we will make the radius a function of angle. With that add some constants to control the radius function.
  • radiusMin defines the min radius (starting radius)
  • radiusScale defines the rate per turn in pixel that the spiral moves out.
  • Remove count as its no longer needed
  • Inside the loop calculate the radius. As we have difined the radius growth as a rate per turn we divide the radiusScale / (Math.PI * 2) so the radius is radius = angle * (radiusScale / (Math.PI * 2)) + radiusMin;
  • Inside the loop we step the angle to match the distance we want to move angle += lineLength / radius; which is derived from the circumference formula (and why we use radians for angles)
  • Change cxt to the more idiomatic ctx
  • Add moveTo to move to the start of each circle.
  • Add ctx.arc to define the circle
  • When all circles are defined, after the loop draw the path with ctx.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

  • Define the number of arms in the spiral armCount
  • Define the rate that the arms turn as the move out "spiralRate"
  • Just for fun animate the 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

Chuanqi Sun
Chuanqi Sun

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>

enter image description here

Upvotes: 2

Anytech
Anytech

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

Related Questions