Title: Make a spiral of Theodorus in Python
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.
def draw(self):
'''Draw the spiral.'''
# Get the points on the spiral.
num_triangles = int(self.num_triangles_entry.get())
edge_points = find_theodorus_points(num_triangles)
# Draw the spiral.
self.draw_theodorus_spiral(edge_points)
This code gets the number of triangles that you entered. It then calls the find_theodorus_points method described shortly to get the points on the outside of the spiral of Theodorus. It finishes by calling the draw_theodorus_spiral method (also described shortly) to draw the spiral.
The following sections describe the find_theodorus_points and draw_theodorus_spiral methods plus their helper routines.
find_theodorus_points
The following code shows the find_theodorus_points function.
def find_theodorus_points(num_triangles):
'''Find points on the spiral of Theodorus.'''
# Find the edge points.
edge_points = []
# Add the first point.
theta = math.pi / 2;
radius = 1;
for i in range(num_triangles + 2):
radius = math.sqrt(i)
point = (radius * math.cos(theta), radius * math.sin(theta))
edge_points.append(point)
theta -= 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. The variable radius keeps track of the length of the triangles' side lengths.
The code initializes theta to π / 2 so the angle starts pointing to the right. It sets radius to 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.
This glosses over the way the program finds the edge points. The equations x = radius * math.cos(theta) and y = radius * math.sin(theta) find the coordinates of the point (x, y) that is distance radius away from the origin and at angle theta. For more information on how that works, see the Math Is Fun post Polar and Cartesian Coordinates.
Because of the way those equations work, this code finds the points assuming the spiral's center is at the origin (0, 0). The transform_points function described shortly moves the points so they are nicely centered on the program's Canvas widget.
|
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.
draw_theodorus_spiral
The following code shows the draw_theodorus_spiral method, which draws the spiral.
def draw_theodorus_spiral(self, edge_points):
'''Draw the spiral of Theodorus.'''
# Clear the canvas.
self.canvas.delete(tk.ALL)
# Transform the points so they fit nicely on the canvas.
margin = 10
rect = [margin, margin,
self.canvas.winfo_width() - margin - 1,
self.canvas.winfo_height() - margin - 1]
edge_points, dx, dy = transform_points(edge_points, rect)
# Draw the spiral.
for i in range(len(edge_points)-1, 1, -1):
triangle = [
(dx, dy),
(edge_points[i][0], edge_points[i][1]),
(edge_points[i-1][0], edge_points[i-1][1]),
]
self.canvas.create_polygon(triangle, fill='white', outline='red')
This method uses the list of edge points to draw the spiral.
First, the code calls the transform_points function to adjust the edge points so they are centered on the program's Canvas widget. That is probably the most important part of this example from a practical standpoint, so I'll describe it shortly.
Next, the code loops through the edge points, uses them to define a triangle, and draws the triangle. The code loops through the edge points backward so the first triangles near the spiral's middle are drawn on top of the later triangles. If you draw them in the other order, the center triangles may be completely covered by later triangles so the spiral isn't as cool.
transform_points
The following transform_points function transforms a list of points so they fit nicely within a target rectangle.
def transform_points(points, rect):
'''Scale and translate the points to fit nicely in the rectangle.'''
# Get the bounds.
xs = [point[0] for point in points]
xmin = min(xs)
xmax = max(xs)
ys = [point[1] for point in points]
ymin = min(ys)
ymax = max(ys)
# Get the scale factor.
xscale = (rect[2] - rect[0]) / (xmax - xmin)
yscale = (rect[3] - rect[1]) / (ymax - ymin)
scale = min(xscale, yscale)
# Figure out how to translate the points.
point_cx = scale * (xmin + xmax) / 2
point_cy = scale * (ymin + ymax) / 2
rect_cx = (rect[0] + rect[2]) / 2
rect_cy = (rect[1] + rect[3]) / 2
dx = rect_cx - point_cx
dy = rect_cy - point_cy
# Return the transformed points and the transformed origin.
points = [(point[0] * scale + dx, point[1] * scale + dy)
for point in points]
return points, dx, dy
To make the points fit within the rectangle, this function must do two things. First, it needs to scale the points so they fit within the rectangle. That may enlarge or shrink the area covered by the points. Second, it must translate the points so they are centered within the rectangle.
The code first uses a list comprehension to get the points' X coordinates. It then uses min and max to get the smallest and largest X coordinates. It repeats those steps to get the smallest and largest Y coordinates.
Next, the code calculates scale factors that would make the points' X and Y coordinates just fit within the rectangle. It takes the smaller of the X and Y scale factors and uses that to scale the points. That makes the points fit within fit completely within the rectangle in both the X and Y directions.
The function then figures out how it should translate the points to center them on the rectangle. First it calculates the points' center X and Y coordinates scaled to fit the rectangle. It then finds the center of the rectangle. Finally it sets dx and dy to subtract the points' center and then add the rectangle's center. This moves the point to center the edge points and then move their center to the middle of the rectangle.
Finally, the function uses a list comprehension to transform the points by scaling them and then adding the dx and dy offsets. It then returns the transformed list.
The function also returns the coordinates of the origin (0, 0) in the original coordinate system after it has been transformed. That tells the calling code where to draw the center of the spiral.
Conclusion
The code to find the points on the spiral of Theodorus isn't too long as long as you remember a little trigonometry.
Download the example to experiment with it and to see additional details.
|