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 Fibonacci Rectangles

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!

Golden Spiral/Curve

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…

Golden Spiral/Curve Alone

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