by Anand | tags: [ fibonacci python golden-spiral golden-curve matplotlib visualization mathematics geometry ]
The Golden Spiral with Python & Matplotlib
I love visualization. I love mathematics. I love to ramble and do fun stuff on a Friday evening. So here we are. And what are we going to do today ?
Today, we will plot that beautiful mathematical curve, the Golden Spiral from first principles. We will do this as usual using my favorite visualization tools, namely matplotlib using Python.
Rather than using formulas, we will do this from first-principles.
Getting Started
First you should read a bit on the Golden Spiral and its relationship with Fibonacci numbers. The Wikipedia article linked here provides a good start. If you want to explore more, visit the Golden Curve page on the website mathcurve.com as well.
Now you know that the Golden spiral can be generated by drawing successive Fibonacci golden
rectangles of dimension
$$ f_{n-1} \ast f_n $$ where $ f_n $ is the nth
fibonacci number and connecting the points using consecutive quarter-circle arcs inscribed in each square.
Golden Rectangles
First, we need to generate the Golden rectangles. As a firt step, we will write a modified fibonacci function.
def fibonacci(n=10):
""" Generate pairs of fibonacci numbers upto 10 starting with 1,2 ... """
a, b = 0,1
for i in range(n):
c = a + b
yield (c,b)
a,b=b,c
This function generates pairs of fibonacci numbers upto a given count. We generate pairs as they are useful in generating the rectangles. Remember that each golden rectangle is of the dimension $ f_{n-1} \ast f_n $.
Here is the function that uses the pairs to generate rectangles.
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
def golden_rectangles(max_n=10):
""" Generate and plot successive golden rectangles """
# List of fibonacci numbers as (fn, fn-1) pair
fibs = list(fibonacci(max_n))
# Reverse as we need to generate rectangles
# from large -> small
fibs.reverse()
# Create a sub-plot
fig, ax = plt.subplots(1)
last_x, last_y = fibs[0]
# Make the plot size large enough to hold
# the largest fibonacci number on both
# x and y-axis.
ax.set_xlim(0, last_x + 10)
ax.set_ylim(0, last_y + 10)
# Turn off the axes
plt.axis('off')
# First rectangle is centered at (0,0)
origin = [0, 0]
# Rectangles
rects = []
for i,(cur_fn, prev_fn) in enumerate(fibs):
# Plot upto max_n + 1 rectangles
if i > max_n: break
if i in fourth_series(max_n):
# Every 4th rectangle from the 2nd
# rectangle onwards has its origin-x
# point shifted by the fibonacci value
origin[0] = origin[0] + cur_fn
elif i in sixth_series(max_n):
# Every 6th rectangle from the 5th
# rectangle onwards has its origin-y
# point shifted by the fibonacci value
origin[1] = origin[1] + cur_fn
if i%2 == 1:
# Every 2nd rectangle has its orientation
# switched from lxb to bxl
cur_fn, prev_fn = prev_fn, cur_fn
rectangle = Rectangle(origin, cur_fn, prev_fn, angle=0.0, antialiased=True)
rects.append(rectangle)
# Add the rectangles to the plot - we need transparency
# so that the embedded rectangles all show up
rect_pcs = PatchCollection(rects, facecolor='g', alpha=0.4,
edgecolor='black')
ax.add_collection(rect_pcs)
plt.show()
Rectangle is the class provided by matplotlib library to plot rectangles. We use a so-called PatchCollection object to collect all the rectangles and render them in one go. Note how we added transparency value of 0.4 to the rectangle patch collection for the successive rectangles to show up.
To follow the code in its entirety you should have a fairly good understanding of how the golden rectangles are generated. Hopefully the code comments also help.
We need two helper functions for the code to be complete.
def fourth_series(n=10):
""" Generate 1, 5, 9 ... upto n elements """
x = 1
for i in range(n):
yield x
x += 4
def sixth_series(n=10):
""" Generate 4, 10, 16 ... upto n elements """
x=4
for i in range(n):
yield x
x += 6
If you run the code, you get a figure which displays the golden rectangles, one embedded inside the other.
Golden Spiral
Using the above code, we can now plot the golden spiral. The idea here is to connect successive vertices of the rectangles using circular arcs to generate a spiral. The trick here is to identify the correct vertices of the rectangles which can be used to generate the arcs as the origin.
We re-use the same code as above and just rename it as … golden_curve
. Just remember to add this extra import on top.
from matplotlib.patches import Arc
Here is the new function.
def golden_curve(max_n=10):
""" Plot the golden curve """
# List of fibonacci numbers as (fn, fn-1) pair
fibs = list(fibonacci(max_n))
# Reverse as we need to generate rectangles
# from large -> small
fibs.reverse()
fig, ax = plt.subplots(1)
last_x, last_y = fibs[0]
# Make the plot size large enough to hold
# the largest fibonacci number on both
# x and y-axis.
ax.set_xlim(0, last_x + 10)
ax.set_ylim(0, last_y + 10)
plt.axis('off')
origin = [0, 0]
p = 0
# Data for plotting arcs
arc_points = []
rects = []
# Starting offset angle
angle = 90
for i,(cur_fn, prev_fn) in enumerate(fibs):
if i > max_n: break
# Current arc's radius
arc_radius = cur_fn
if i in fourth_series(max_n):
# Every 4th rectangle from the 2nd
# rectangle onwards has its origin-x
# point shifted by the fibonacci value
origin[0] = origin[0] + cur_fn
elif i in sixth_series(max_n):
# Every 6th rectangle from the 5th
# rectangle onwards has its origin-y
# point shifted by the fibonacci value
origin[1] = origin[1] + cur_fn
if i%2 == 0:
# Every 2nd rectangle has its orientation
# switched from lxb to bxl
cur_fn, prev_fn = prev_fn, cur_fn
rectangle = Rectangle(origin, prev_fn, cur_fn, angle=0.0, antialiased=True)
rects.append(rectangle)
if i == 0: continue
if i % 8 == 0:
p += 1
continue
if len(arc_points) == 8: continue
r1 = rectangle
# Calculate the rectangle's co-ordinates
coords = [r1.get_xy(), [r1.get_x()+r1.get_width(), r1.get_y()],
[r1.get_x()+r1.get_width(), r1.get_y()+r1.get_height()],
[r1.get_x(), r1.get_y()+r1.get_height()]]
# Successive arcs are centered on the points of rectangles
# which is calculated as the p % 4 the item
arc_points.append((coords[p % 4], arc_radius, angle))
# Every turn of the spiral we go clockwise by 90 degrees
# means the starting angle reduces by 90.
angle -= 90
# Reset to 0
if angle == -360: angle = 0
p += 3
for center, radius, angle in arc_points:
print('Plotting arc at center',center,'radius',radius, 'angle',angle)
arc = Arc(center, radius*2, radius*2, angle=angle,
theta1=0, theta2=90.0, edgecolor='black',
antialiased=True)
ax.add_patch(arc)
rect_pcs = PatchCollection(rects, facecolor='g', alpha=0.4,
edgecolor='black')
ax.add_collection(rect_pcs)
plt.show()
Note: Updated rectangle skipping code (24-02-2019)
And here is the golden curve it generates!
We keep the golden rectangles on to show perspective w.r.t the original image. We can remove the rectangles by disabling the alpha channel completely, turing off edge colors and using a light background for the rectangles.
Just change the last-but-second line to,
rect_pcs = PatchCollection(rects, facecolor='beige')
The golden curve now shows up in its glory minus the rectangles…
I suggest to play around the various parameters of the Arc and Rectangle class in matplotlib to try and create various effects and also render the image in other colors than given here.
Summary
I hope this was fun and insightful at the same time. I usually have a lot of fun developing code like this which combines the simple elements of mathematics with that of visualization using Python code demonstrating something elementary but beautiful at the same time.
The code will take some reading to understand and if you have any doubts, feel free to get in touch with me through the blog or via twitter.
As usual, the visuzalition code is available at our Visualization Repo.
Note that name and e-mail are required for posting comments