This example shows how to draw a golden spiral (or phi spiral) in C#. The example Draw a nested series of golden rectangles in C# draws nested rectangles and connects their corners to make a square “spiral.” This example makes a smooth spiral.

If you check the Circular Spiral box, the program approximates the phi spiral by using circular arcs. For each square that is removed from a rectangle, it uses the following code to draw an arc connecting two of the square’s corners.

if (chkCircularSpiral.Checked) { // Draw a circular arc from the spiral. RectangleF rect; switch (orientation) { case RectOrientations.RemoveLeft: rect = new RectangleF( (float)x, (float)y, (float)(2 * hgt), (float)(2 * hgt)); gr.DrawArc(Pens.Red, rect, 180, 90); break; case RectOrientations.RemoveTop: rect = new RectangleF( (float)(x - wid), (float)y, (float)(2 * wid), (float)(2 * wid)); gr.DrawArc(Pens.Red, rect, -90, 90); break; case RectOrientations.RemoveRight: rect = new RectangleF( (float)(x + wid - 2 * hgt), (float)(y - hgt), (float)(2 * hgt), (float)(2 * hgt)); gr.DrawArc(Pens.Red, rect, 0, 90); break; case RectOrientations.RemoveBottom: rect = new RectangleF( (float)x, (float)(y + hgt - 2 * wid), (float)(2 * wid), (float)(2 * wid)); gr.DrawArc(Pens.Red, rect, 90, 90); break; } }

This code simply draws an appropriate arc depending on the position of the square inside its rectangle.

If you check the True Spiral box, the program draws a logarithmic spiral with growth factor φ so its radius increases by a factor of the golden ratio φ for every quarter turn. (Actually I wonder if the growth factor shouldn’t be the factor by which it grows in a complete circle.)

In any case, the program uses the following code to draw a logarithmic spiral.

if (chkTrueSpiral.Checked && points.Count > 1) { // Draw the true spiral. PointF start = points[0]; PointF origin = points[points.Count - 1]; float dx = start.X - origin.X; float dy = start.Y - origin.Y; double radius = Math.Sqrt(dx * dx + dy * dy); double theta = Math.Atan2(dy, dx); const int num_slices = 1000; double dtheta = Math.PI / 2 / num_slices; double factor = 1 - (1 / phi) / num_slices * 0.78; List<PointF> new_points = new List<PointF>(); // Repeat until dist is too small to see. while (radius > 0.1) { PointF new_point = new PointF( (float)(origin.X + radius * Math.Cos(theta)), (float)(origin.Y + radius * Math.Sin(theta))); new_points.Add(new_point); theta += dtheta; radius *= factor; } gr.DrawLines(Pens.Blue, new_points.ToArray()); }

This code sets the spiral’s outermost point to be the first point used by the square spiral. It uses the last point found (where the rectangles become vanishingly small) as the spiral’s origin.

Next the code calculates the factor by which it will reduce the radius for each change in angle `dtheta`. With a change in angle of π / 2, the radius should ideally decrease by a factor of 1 – (1 / φ). The program divides both `dtheta` and this factor by `num_slices` to make a smooth curve.

I honestly don’t know why I need the factor of 0.78 here to make the curve fit the rectangles well. If you leave it out, the curve draws a nice spiral but it lies well inside the rectangles. If you have any ideas, post a comment below.

After setting up the parameters, the program enters a loop where it generates a new point on the spiral and multiplies the spiral’s radius by the scale factor to move to the next point. It continues the loop until the radius becomes too small to see and then connects the points.

With the mystery factor of 0.78, the spiral does a good job of fitting the rectangles. If you draw both spirals at the same time, you’ll see that the circular approximation is remarkably close to the true spiral.

Download the example program to see the rest of the code.

“If you draw both spirals at the same time, you’ll see that the circular approximation is remarkably close to the true spiral.”

I noticed that you say ‘remarkably close’. I believe that some of the fitting errors are related to underflow when converting from float to double.

“I honestly don’t know why I need the factor of 0.78 here to make the curve fit the rectangles well. If you leave it out, the curve draws a nice spiral but lies well inside the rectangles”

So, I can’t help but wonder if there is not some relation here…

I have not had time to investigate this, but I did confirm that the reason the spiral will not fit to the boxes when certain ratios of width and height are true is related.

Well the circular arc approximation is

notthe same as the true spiral. The true spiral’s radius of curvature changes continuously throughout the spiral but the circular approximation’s curvature only changes when you move to a new box and start using a new circle.The thing I find amazing is how close that approximation seems to be to the true spiral.

The fact that I can’t get the “true” spiral to fit the boxes exactly might be due to rounding errors, but in this kind of graphics I would expect a rounding error to make the drawing off by no more than one pixel.

I really don’t know what the factor of 0.78 is all about (but I don’t have time to really dig into it and it seems low priority).

“I don’t have time to really dig into it and it seems low priority”

Understood.

I am sure that you had or would consider the underflow possibility.

http://www.vb-helper.com/bug_floating_point_errors.html

ðŸ™‚

Here is some another link for reference in case others are not familiar with the possibility of “arithmetic underflow” when dealing with floating point representation of numbers.

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Thanks for the excellent posts as usual!