Title: Draw the spiral of Theodorus in C#
The spiral of Theodorus (which is also called the square root spiral, Einstein spiral, and Pythagorean spiral) was first devised by the Greek mathematician Theodorus of Cyrene during the 5th century BC. The spiral consists of a sequence of right triangles where the ith triangle has side lengths 1, √i, and √(i+1). Each triangle's edge of length √(i+1) is shared with the next triangle as shown in the following picture from Wikipedia.
When you fill in the number of triangles that you want to draw and click Draw, the following code executes.
private void btnDraw_Click(object sender, EventArgs e)
{
// Get the spiral of Theodorus's points.
int num_triangles = int.Parse(txtNumTriangles.Text);
List<PointF> edge_points = FindTheodorusPoints(num_triangles);
// Draw the spiral of Theodorus.
picSpiral.Image = DrawTheodorusSpiral(
edge_points, picSpiral.ClientSize,
chkOutline.Checked, chkFill.Checked);
}
This code gets the number of triangles that you entered. It then calls the FindTheodorusPoints method described shortly to get the points on the outside of the spiral of Theodorus. It finishes by calling the DrawSpiral method (also described shortly) to draw the spiral.
The following sections describe the FindTheodorusPoints and DrawSpiral methods and their helper routines.
FindTheodorusPoints
The following code shows the FindTheodorusPoints method.
// Find points on the spiral of Theodorus.
private List<PointF> FindTheodorusPoints(int num_triangles)
{
// Find the edge points.
List<PointF> edge_points = new List<PointF>();
// Add the first point.
float theta = 0;
float radius = 1;
for (int i = 1; i <= num_triangles; i++)
{
radius = (float)Math.Sqrt(i);
edge_points.Add(new PointF(
radius * (float)Math.Cos(theta),
radius * (float)Math.Sin(theta)));
theta = (float)Math.Atan2(1, radius);
}
return edge_points;
}
This method first creates an edge_points list to hold the triangles' outer vertices.
The variable theta keeps track of the angle that the points make with respect to the spiral's center. Variable radius keeps track of the length of the triangles' side lengths.
The code sets theta = 0 and radius = 1, and then enters a loop to find the leading edge point for each triangle. Inside the loop, the code uses theta and radius to find the triangle's point and adds it to the list.
Each triangle's inner angle (the one by the spiral's center) is the arc tangent of the opposite side (which always has length 1) and the adjacent side. The adjacent side for the ith triangle has length radius = √i, so the program subtracts Atan2(1, radius) from theta to prepare for the next point on the spiral. (The code subtracts this value instead of adding it so the angles increase counterclockwise for the triangles.)
After it finishes finding the edge points, the method returns them.
DrawTheodorusSpiral
The following code shows the DrawTheodorusSpiral method, which draws the spiral.
// Draw the spiral of Theodorus.
private Bitmap DrawTheodorusSpiral(List<PointF> edge_points,
Size size, bool outline_triangles, bool fill_triangles)
{
// Make the bitmap and associated Graphics object.
int wid = size.Width;
int hgt = size.Height;
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.Clear(Color.White);
// Make brushes.
Color[] colors = RainbowColors(255);
Brush[] brushes = ColorsToBrushes(colors);
// Scale and center.
float xmin, xmax, ymin, ymax;
GetBounds(edge_points, out xmin, out xmax,
out ymin, out ymax);
RectangleF drawing_rect = new RectangleF(
xmin, ymin, xmax  xmin, ymax  ymin);
RectangleF target_rect = new RectangleF(
5, 5, wid  10, hgt  10);
MapDrawing(gr, drawing_rect, target_rect, false);
// Draw.
using (Pen pen = new Pen(Color.Black, 0))
{
int num_brushes = brushes.Length;
for (int i = edge_points.Count  1; i > 0; i)
{
PointF[] points =
{
new PointF(0, 0),
new PointF(
edge_points[i].X,
edge_points[i].Y),
new PointF(
edge_points[i  1].X,
edge_points[i  1].Y),
};
if (fill_triangles)
gr.FillPolygon(brushes[i % num_brushes],
points);
if (outline_triangles)
gr.DrawPolygon(pen, points);
}
}
}
return bm;
}
This method draws the spiral of Theodorus on a bitmap of a specified size and returns the bitmap.
It starts by creating a bitmap of the correct size, making an associated Graphics object, and clearing it.
The method then calls the RainbowColors method to make some colors. It then uses the ColorsToBrushes method to convert those colors into an array of brushes. Both of those methods are described shortly.
The code then calls the GetBounds method (also described shortly) to get bounds for the spiral's edge points. It uses the bounds to make a rectangle representing the drawing area. It then passes the drawing rectangle and a rectangle that represents the bitmap's surface (minus a margin) to the MapDrawing method.
The MapDrawing method applies translation and scaling transformations to the Graphics object to make drawing commands it inside the target area. For more information on this method, see the post Scale a drawing to fit a target area in C#.
Now the method draws the spiral of Theodorus. It creates a black pen with thickness 0. (Pens with thickness 0 are not scaled even if the Graphics object includes a scaling transformation.)
The method then enters a loop that runs from the last triangle point to the first. The loops runs lasttofirst instead of firsttolast because later triangles may overlap earlier ones. The picture on the right shows the spiral of Theodorus with 30 triangles. You can see that the outer ones overlap the inner ones. Drawing the triangles lasttofirst lets you see pieces of all of the triangles.
After it fills a triangle, the method outlines it.
As it draws each triangle, the code checks the outline_triangles and fill_triangles values to see whether it should outline or fill the triangles. (Draw the spiral without filling it if you want to see the overlapping outlines of the triangles.)
RainbowColors
The following code shows the RainbowColors method.
// Return an array of rainbow colors.
private Color[] RainbowColors(byte alpha)
{
return new Color[]
{
Color.FromArgb(alpha, 255, 0, 0),
Color.FromArgb(alpha, 255, 255, 0),
Color.FromArgb(alpha, 255, 128, 0),
Color.FromArgb(alpha, 0, 255, 0),
Color.FromArgb(alpha, 0, 255, 255),
Color.FromArgb(alpha, 0, 0, 255),
Color.FromArgb(alpha, 255, 0, 255),
};
}
This method simply builds an array holding a predefined set of colors. The method takes an alpha parameter in case you want to make the colors semitransparent. (I originally thought that would be useful in this example, but it just made the result more cluttered.)
ColorsToBrushes
The following code shows the ColorsToBrushes helper method.
// Convert colors to brushes.
private Brush[] ColorsToBrushes(Color[] colors)
{
int num_colors = colors.Length;
Brush[] brushes = new Brush[num_colors];
for (int i = 0; i < num_colors; i++)
brushes[i] = new SolidBrush(colors[i]);
return brushes;
}
This method simply loops through an array of colors and makes a SolidBrush for each.
GetBounds
The following code shows the GetBounds method.
// Get the points' bounds.
private void GetBounds(List<PointF> points,
out float xmin, out float xmax,
out float ymin, out float ymax)
{
// Find the bounds.
xmin = points[0].X;
xmax = xmin;
ymin = points[0].Y;
ymax = ymin;
foreach (PointF point in points)
{
if (xmin > point.X) xmin = point.X;
if (xmax < point.X) xmax = point.X;
if (ymin > point.Y) ymin = point.Y;
if (ymax < point.Y) ymax = point.Y;
}
}
This method simply loops through an array of PointF and finds their minimum and maximum X and Y values. (You could use LINQ to do something similar, but it wouldn't be any easier to read and it would be slightly slower.)
Summary
Download the example to see additional details and to experiment with the program. For example, you might try using semitransparent colors, with or without the triangle outlines. If you fill around 10,000 or more triangles without outlining them, you can also see an interesting wave effect flowing through the spiral's rings.
For more information on the spiral of Theodorus, see the Wikipedia article Spiral of Theodorus.
Download the example to experiment with it and to see additional details.
