Blog  |   Puzzles  |   Books  |   About
 

Circles, Spirals and Sunflowers

An HTML5/canvas tutorial by Jim Bumgardner
In this tutorial, my aim is to show you some fun ways to draw circles, and spirals, and ultimately, how to draw the interesting pattern you see on sunflowers. All these techniques are described from a few basic facts about circles, which you probably learned (and forgot) a long time ago. Back in grade school, you may have learned these three equations:


D (diameter) = 2 R

C (circumference) = 2 π R (or π D)

A (area) = π R2

While these equations are useful, they aren't actually needed to draw a circle. We'll make use of them in a surprising way, below.

To draw a circle or anything, in Javascript, we need a graphics framework to work with. In this tutorial, I'm going to draw everything using the Canvas APIs which are part of HTML5. There is also a Processing.js version of this tutorial which you may prefer. Both versions use the HTML5 canvas - this means that if you are using an older web browser, like IE 8 or earlier, you're not going to see anything!

All the examples I'm using make use of the same basic skeleton code, which sets up a simple canvas and draws in it using its device context. You can download this skeleton, here, and use it for your own experiments.

To draw in the canvas, I will place the code in a routine called refresh, like so.

function refresh(dc, width, height)
{
}

The refresh routine accepts as parameters the device context (dc) of the canvas, the width of the canvas, and the height of the canvas. Click on my examples to see the contents of my refresh() function.

For the occasional animated example, I will use a slightly modified skeleton that also passes in a frame number, like so: (You'll find an additional piece of sample code for doing animations in the above archive).

function refresh(dc, width, height, frame_number)
{
}

Although I have supplied you with the skeleton code, you'll learn more if you attempt to build it from scratch yourself, using an HTML5 canvas reference.

Example 1 shows a basic black circle. The HTML5 code for this is perhaps needlessly complicated, but not too difficult to understand. I've made it longer than it needs to be, to make it readable. Here's what it looks like.
dc.fillStyle='#000';         // set a fill color
dc.beginPath();              // begin drawing a shape
var cx = width/2;            // compute center coordinates
var cy = height/2;
var rad = width*.45;         // work out the radius

dc.arc(cx, cy, rad, 
       0,2*Math.PI, false);  // create the shape path as a closed arc
dc.fill();                   // fill the shape
The most complicated piece of this is the arc function, which is used to draw circles, and circular curves. It's parameters are
  • cx, cy - the center coordinates of the circle you are drawing
  • rad - the radius of the circle
  • begin_angle, end_angle - the beginning and ending positions of the arc, expressed in radians. For a complete circle, use 0,2*Math.PI, which is the radians equivalent of 0 to 360 degrees.
  • counter_clockwise_flag - a flag which indicates whether to draw clockwise or counter-clockwise. For all of our examples, I'll pass false, meaning clockwise - this is more of an issue when you are drawing partial arcs.

Example 2 shows a more complicated taijitu figure drawn using multiple filled circles (and one half circle). This is a more advanced example and can be skipped for now. If you're curious about it, click on the link for example 2 and scroll to the bottom of the code to read more about it. Later, try reproducing it without looking at the code.


You may find it tempting to make a subroutine simply for making filled circles. It would look like this:

function filled_circle(cx, cy, rad)
{
  dc.beginPath();
  dc.arc(cx, cy, rad, 0,2*Math.PI, false);
  dc.fill();
}

Note that if you replace the fill function with the stroke function, you can get an outlined circle, which looks like example 3.

At this point, I would suggest, as a hands-on exercise, that you construct a sample web page that draws a circle using the HTML5 canvas. You can use my 'example 3' code as a starting point - click on one of the examples and do a "view source" to see what's going on. After that, take a break and come back when you're feeling fresh!

Feeling refreshed? Great. You may be curious how the points on these circles are plotted.

In example 4, I've drawn a large circle out of small circles, like a black pearl necklace. The are a number of ways to figure out how the points lie on a circle, but I usually use the sine and cosine functions to do it. I think of these functions as "circle drawing" functions. The basic equations are:

x = cx + cos(θ) R
y = cy + sin(θ) R

These are the classic equations for converting from polar coordinates (angle and distance from some center point) to cartesian coordinates (x and y). In these equations, cx and cy are the center point of the circle, R is the radius of the circle, and theta (θ) represents the angle going around the circle. In code, you usually supply the angle in radians, not degrees. The more familiar degrees go from 0 to 360. Radians go from 0 to 2π. To convert a number from degrees to radians, multiply it by a scaling constant (π / 180). Note that the angle value you pass to sin and cosine doesn't need to be restricted to 0 to 2π - you can keep going around the circle in either direction. sin(θ) will produce identical values for any two numbers which are 2π apart. The pattern produced by these functions are sine waves and cosine waves (cosine waves are basically sine waves which are out of phase by 90 degrees).

In javascript, I employ these equations in a loop, to draw each point on the circle, like so:

for (var i = 1; i <= nbr_circles; ++i) {
  dc.beginPath();
  var angle = i*2*Math.PI/nbr_circles;
  var x = cx + Math.cos(angle) * lg_rad;
  var y = cy + Math.sin(angle) * lg_rad;
  dc.arc(x, y, sm_rad, 0, 360, false);
  dc.fill();
}

In this code, lg_rad is the radius of the large ring, and sm_rad is the radius of the smaller circles I am drawing in the ring. In order to make the circles just big enough to touch, I worked out the circumference of the large circle, and then divided it by the number of circles in the ring. This is the diameter of the smaller circles. I got the radius by dividing it by 2.

var lg_rad = (width/2) * .85;               // large radius
var lg_circ = 2*Math.PI*lg_rad;             // large circumference
var sm_rad = (lg_circ / nbr_circles) / 2;   // small radius

By changing the value of nbr_circles, I can increase or decrease the number of circles in the ring, as in example 5. Try hitting the play button on this example - you'll see the circles get smaller as I increase the number with each frame. If I make the circles small enough, it closely resembles the circular outline in example 3. At those sizes, it would be far more efficient to draw individual pixels, rather than little tiny circles!

While you take a break, consider how this code could be modified to draw a spiral instead of a circle...

Have you figured it out? The basic idea is that you change the value of the larger radius (what I was calling lg_rad) as you draw each point. Take a look at the pertinent lines of code in example 6. In this example, as each dot is drawn, at an ever increasing radius, I also increment the angle by just 2 degrees.

We can tighten the spiral by changing the amount we increment the angle during each step, as in example 7. Hit the play button to see the effect. In this example, we start at a 2 degree increment, and then increase the angle by 1 degree per second (since there are 100 dots, the outer dot travels at 100 x 1 degrees or 100 degrees per second).


Watch the above animation for a couple minutes: interesting things happen!. You will notice after about 25 seconds, when the spiral has tightened up, the dots form a spoked or starfish-like pattern. Then the arms fold in, and the dots form a rose-like configuration. Then the arms straighten out again, and you see another starfish. This keeps happening. When the starfish has four arms, the angle increment is 90 degrees, or some multiple of 90 degrees like 270 degrees. The smallest angle that will give you 4 arms is 360/4 or 90 degrees. Similarly, the smallest angle that will get you 5 arms is 360/5 or 72 degrees. 360/6 gives you six arms, 360/7 gives you seven arms and so on. In other words, to get those starfish patterns, the angle increment has to be a rational fraction or (or "go evenly into") the full circle (so they can line up to form arms).

Between those starfish, which are produced by rational fractions of the circle, you get the rose-like patterns, in which the dots don't make straight spokes. You get these rose patterns when the angle increment is an irrational fraction of the full circle. Mathematicans have demonstrated that there are far more irrational numbers than rational ones, and intuitively, you can see that there are more roses than starfish in the animation.

There are a number of irrational angles that look especially nice (try the square root of 2 times π or 4.442). The one that produces the most optimal packing, and corresponds to the familiar sunflower arrangement is an angle of approximately 222.5 degrees (or 137.5 going the other way). This is the golden angle, and it is closely related to φ (phi), the golden ratio.

More precisely:

φ = (sqrt(5) + 1) / 2 - 1

golden angle = 360 φ degrees, or 2 π φ radians

If we use that angle to produce a spiral, we get one of these! Example 8 is sometimes called a fibonacci spiral, because the golden ratio is closely related to fibonacci numbers. I also call these phyllotaxy spirals, because the golden angle appears a lot in plant growth (it optimizes surface area to sunlight). Once you start noticing it, you'll see it in a lot of plants in addition to sunflowers, such as pine cones and agaves.

In the above example, you may have noticed that the dots are kind of tight in the center, and then get progressively further apart as they go out. This is because the radial increment is constant, or linear. It would be cool if we could figure out how to get them to pack tightly, like the seeds of a sunflower. It turns out, we already have the mathematical tools to accomplish this!

Consider a large circle which is made up of a bunch of tightly packed little circles - circles so tightly packed that no space remains. If all those little circles are the same size, and the area of the large circle is A, then the area of the little circles, B = A/N, where N is the number of little circles.

As we grow a phyllotaxy spiral, at each step M from 1 to N, we make something very like a circle made up of M tiny circles. The area of that circle is B*M. Since we know that area = π R2, we can deduce the desired radius from the area. R = sqrt( area / π )

We can use this technique to figure out (given the number of circles we wish to draw, and the size of the outer circle) both the size of the small circles, and how far out to draw each one. The math for working out the expanding radii isn't perfect, because there is a little space left over after the cicles are packed together. To compensate for that, we use a fudge factor which keeps the circles from overlapping by drawing them slightly smaller. I find drawing 87% of the perfect size works fine. This produces this figure.

To get even closer to a sunflower, we can make the circles grow larger as they grow outward. One way to easily accomplish this is by making the outer circles grow exactly the same amount as the inner circles shrink, so that cumulatively, the area of the circles remains the same. Here is an example.

Finally, we can use the frame_number to produce some cool animation effects with these patterns. Click and scroll to the bottom of example 11 for more info about how I did the color cycling. Have fun!

The next tutorial in this series is Double Rainbow All The Way!.

By the way, if you are in the Los Angeles area, I'll be teaching a couple of graphics and music programming workshops in Culver City, using the Processing language, in the next few weeks. More info here.


Copyright © 2024 by KrazyDad. All Rights Reserved.
Privacy Policy
Contact Krazydad
Discord server