Title: Color rings of nested circles in a spiral pattern in Python
This example is a fairly simple modification of my post Recursively draw rings of nested circles in Python. That post's example drew a series of nested rings of circles where the colors of the rings gradually grow darker as the rings get smaller. If you study the result for a while, you may notice that the circles also naturally form a set of nested spirals. This post uses different shapes to color the spirals to make them more obvious.
Coloring Spirals
The following draw_nested_circles method draws the outer circle and the first ring.
def draw_nested_circles(dr, max_depth, outline, colors,
draw_outer, n, cx, cy, R):
'''Draw a circle containing a sequence of nested circle rings.'''
# Draw the outer circle.
if draw_outer:
rect = (cx - R, cy - R, cx + R, cy + R)
dr.ellipse(rect, outline=outline, fill=colors[0]) # Color 0.
# Draw the first ring of inside circles.
centers = get_inner_circles(n, cx, cy, R)
for i, (x, y, r) in enumerate(centers):
rect = (x-r, y-r, x+r, y+r)
dr.ellipse(rect, outline=outline, fill=colors[i])
# Draw the remaining rings of circles.
G = math.dist((cx, cy), (x, y))
g = r
color1 = 1
draw_nested_levels(dr, max_depth, 2, colors, True,
color1, outline, n, cx, cy, G, g)
This code first draws the outermost circle that contains all of the others. It then enumerates the circles returned by get_inner_circles, which includes the outermost ring of circles. The code uses each circle's index i to assign it a color shade.
Next, the code calls the following draw_nested_levels function to draw the smaller rings of circles.
def draw_nested_levels(dr, max_depth, depth, colors, is_offset,
color1, outline, n, cx, cy, G, g):
'''Recursively draw more levels.'''
# See if we're done.
if depth >= max_depth:
return
# Get next level.
centers = get_nested_circles(is_offset, n, cx, cy, G, g)
for i, (x, y, r) in enumerate(centers):
rect = (x-r, y-r, x+r, y+r)
dr.ellipse(rect, outline=outline,
fill=colors[(i + color1) % len(colors)])
# Recurse.
is_offset = not is_offset
G = math.dist((cx, cy), (x, y))
g = r
if is_offset:
color1 += 1
draw_nested_levels(dr, max_depth, depth + 1, colors, is_offset,
color1, outline, n, cx, cy, G, g)
After checking that the depth is smaller than the maximum desired depth of recursion, the code enumerates the circles returned by get_nested_circles to draw the next ring. It uses each circle's index i to pick its color. (See the first snippet highlighted in blue.) The code adds color1 to the index to make this ring's colors begin with the color shade after the one used by the previous ring.
Having drawn this ring, the function prepares to recursively call itself to draw the next ring. To do that, it must set color1, the first color that the next ring should use.
If this ring's is_offset value is true, then this ring's circles have been shifted by one position to make the circles' centers line up properly. In that case, the code adds 1 to color1 to make the next ring's colors line up with this ring's colors. If is_offset is false, the code leaves color1 unchanged. (Before I figured this out, it caused a rather confusing bug. Remove this test to see what happens.)
The function then calls itself recursively to draw rings of smaller circles.
Conclusion
I had thought this would be my last digression into rings of circles, but I found one more variation that I want to try, so that's coming next. I promise I'll get back to Apollonian circles soon.
Download the example to experiment with it and to see additional details.
|