# Making Gifs with Matplotlib and Recaman¶

This notebook is meant to be a tutorial on generating an animated gif using matplotlib. The subject to be animated is an implementation of the Racaman Sequence.

In :
import matplotlib.pyplot as plt
from matplotlib import patches
from matplotlib import animation
from matplotlib import rc
import os
from typing import List, Union
%matplotlib inline
from IPython.display import HTML


## Define the Sequence¶

First we need to create an implementation of the Recaman Sequence. Here the set object is used to maintain a list of values of n, that have already been seen. While a set (sequence) is a useful object to check for member presence (due to hashing), a list (recaman_series) is used to maintain sequence ordering.

In :
def recaman(n: int) -> List[int]:
"""
Compute the values in the Recaman Sequence up to a N members
Args:
n (int): number of sequence members to compute

Returns:
recaman_series (list): The Recaman Sequence up to N members
"""

# Check to see if the sequence count is <= 0
if n <= 0 or not isinstance(n, int):
raise ValueError(f'Invalid Input, must be positive integer')

# initialize a set (monitor previous seen values) and a list (container for sequence results)
sequence = {0}
recaman_series = 

previous_value = 0
for i in range(1, n):
# check backwards direction since the sequence must go back if possible
current_value = previous_value - i
# If the value is negative or already exists.
if (current_value < 0 or current_value in sequence):
current_value = previous_value + i

recaman_series.append(current_value)
previous_value = current_value  # move the current value forward
return recaman_series


We want to grab a small set of the sequence to make sure it returns the expected results

In :
N = 15
print(f'The first {N} members of the Recaman Sequence are: {recaman(N)}')

The first 15 members of the Recaman Sequence are: [0, 1, 3, 6, 2, 7, 13, 20, 12, 21, 11, 22, 10, 23, 9]


## Plotting¶

In order to plot the sequence in the semi-circle style, we need to first be able to calculate the appropriate plot arcs. This is done using matplotlib's patches library to generate the appropriate arc segment for each member of the sequence.

In :
def compute_arcs(sequence: List[int]) -> List:
"""
Compute each of the arc segments for each member of a Recaman Sequence
to be plotted using matplotlib
Args:
sequence (list): A Recaman Sequence

Returns:
arcs (list): A collection of matplotlib arc segments for
each member of the sequence

"""
arcs = []
for i in range(len(sequence) - 1):
# midway point should be between sequence members on the x-axis
arc_center = (0.5 * (sequence[i] + sequence[i + 1]), 0)
# arc heigh and width is proportional to the sequence element number
arc_width = abs(sequence[i + 1] - sequence[i])
arc_height = abs(sequence[i + 1] - sequence[i])
# slternate orientation of the arc (convex up or down)
arc_angle = (pow(-1, i + 1) + 1) / 2 * 180
# arc will always span 180 degrees
start_angle = 180
end_angle = 360
# create an arc segment
cur_arc = patches.Arc(
arc_center,
arc_width,
arc_height,
angle=arc_angle,
theta1=start_angle,
theta2=end_angle,
linewidth=1,
fill=False
)
arcs.append(cur_arc)
return arcs


With the ability to calculate the arc segment for each member of the sequence, a figure can be plotted

In :
def plot_recaman(sequence: List[int]) -> None:
"""
Plot a semi-circular representation of a Recaman Sequence
Args:
sequence (list): A Recaman Sequence

Returns:
N/A
"""

# create the arcs associated with each sequence member
arcs = compute_arcs(sequence)
# create a figure and all each of the arc segments in order
fig, ax = plt.subplots(figsize=(9.5, 6))
for arc in arcs:
# set the plot limits to encompas the entire sequence
buffer_factor = 1.1  # buffer to give the figure breathing room
ax.set_xlim(-(buffer_factor - 1) * max(sequence), buffer_factor * max(sequence))
ax.set_ylim(-0.5 * buffer_factor * len(sequence), 0.5 * buffer_factor * len(sequence))
ax.set_aspect('equal')
ax.set_title(f'Recaman Sequence\nN=({len(sequence)})')
fig.tight_layout()
plt.show()


In :
N = 25
plot_recaman(recaman(N)) ## Animated Gif¶

To add some interest to the plot, we can animate it so that each arc segment is drawn for each member of the sequence.

In :
def animate_recaman(sequence: List,
duration_sec: int = 5,
bitrate: int = 1000,
dpi: int = 100,
embedded: bool = False,
) -> Union[str, HTML]:
"""
Animate the plotting of a Recaman Sequence
Args:
sequence (list): Recaman sequence
duration_sec (int): duration of the animation, determines frames per second (fps)
bitrate (int): bits per second used to compress the output animation
dpi (int): dots per inch of the resulting animtaion
embedded (bool): flag to embed the animation as html5 (for use in a notebook)

Returns:
save_filename (str): The file name of the saved animation
html_video (HTML): A HTML5 encoded video of the animation

"""
fig, ax = plt.subplots(dpi=dpi)
edge_factor = 1.05  # set a 5% buffer to give the graph breathing room
# set the width of the plot to cover the sequence span while centering it
ax.set_xlim(-(edge_factor - 1) * max(sequence), edge_factor * max(sequence))
#set the height of the graph to cover the arcs
ax.set_ylim(-0.5 * edge_factor * len(sequence), 0.5 * edge_factor * len(sequence))
ax.set_aspect('equal')
ax.set_title(f"Recaman's Squence\nN={len(sequence)}")
# add a watermark to the figure
fig.text(0.95, 0.05, '@BrentonMallen',
fontsize=12, color='black',
ha='right', va='bottom', alpha=0.75)

# generate the arcs to plot
arcs = compute_arcs(sequence)
# calculate the fps based off desired duration
fps = len(sequence) // duration_sec

if embedded:

def animate(i):
"""
Function used to update the figure
"""

anim = animation.FuncAnimation(fig,
animate,
frames=arcs,
interval=100,
blit=True
)
plt.close()  # prevent the static figure from displaying (the final frame)
html_video = HTML(anim.to_html5_video())
return html_video

GifWriter = animation.ImageMagickFileWriter(fps,
bitrate=bitrate
)
save_filename = f'recaman_{len(sequence)}.gif'
with GifWriter.saving(fig, save_filename, dpi=dpi):
for arc in arcs:
GifWriter.grab_frame()
return f'Animation saved to: {save_filename}'


## Embedding the Animation¶

To display the animation in this notebook, it has to be created as a HTML5 video and embedded. To do this, the FuncAnimation function is used. It differs from the ImageMagickFileWriter (used to save a Gif as a file) in that it requires a set of frames and a function to definte the animation.

In this case, frames in FuncAnimation is the list of arcs generated (an iterable) and the function animate is used to add each arc to the figure as it updates for each frame.

In :
# specify that we want the animation to be displayed using html5 (html is default)
rc('animation', html='html5')

animate_recaman(recaman(100), embedded=True)

Out:

Saving The Gif to File

The animate_recaman function is written in a way that allows for saving the gif off to a file or converting it to an HTML5 video so that it can be embedded in a notebook (as seen here)

Note:

The GifWriter will generate temporary png files in the same directory for each frame and then combine those to generate the gif. If for some reason, the GifWriter fails, those files will remain and have to be manually deleted.