Basics of GIFs with Python’s Matplotlib | by Thiago Carvalho | Towards Data Science

There are plenty of ways to build animations in Matplotlib. They even have an Animation class with functions and methods to support this task.
But I often find those methods over-complicated, and many times I want to get something together without too much complexity.
In this article, I’ll go through the basics of creating charts, saving them as images, and using Imageio to create a GIF.
Without further ado, let’s import our libraries and start building our GIF.
import osimport numpy as npimport matplotlib.pyplot as pltimport imageio
We can use a line chart to start with something simple since it only requires a list with the y values.
y = np.random.randint(30, 40, size=(40))plt.plot(y)plt.ylim(20,50)
Cool, we used NumPy to create a list of random integers within the range 30 to 40 containing 40 numbers.
The idea is to display the values in our line chart one by one. We’ll use a loop, slicing the array and incrementing the number of selected values at each loop.
Let’s try without the loop first, just plotting, saving and displaying the image.
## ONE ##plt.plot(y[:-3])plt.ylim(20,50)plt.savefig('1.png')plt.show()## TWO ##plt.plot(y[:-2])plt.ylim(20,50)plt.savefig('2.png')plt.show()## THREE ##plt.plot(y[:-1])plt.ylim(20,50)plt.savefig('3.png')plt.show()## FOUR ##plt.plot(y)plt.ylim(20,50)plt.savefig('4.png')plt.show()
Cool, now that we have four frames saved to our working directory. Imageio can help us build our first GIF.
# Build GIFwith imageio.get_writer('mygif.gif', mode='I') as writer:    for filename in ['1.png', '2.png', '3.png', '4.png']:        image = imageio.imread(filename)        writer.append_data(image)
It looks weird, but there’s our GIF.
Now let’s try with a loop. We can also systematically create the filenames and add a pause at the end so our animation will display the complete chart for a while before repeating.
filenames = []for i in y:    # plot the line chart    plt.plot(y[:i])    plt.ylim(20,50)        # create file name and append it to a list    filename = f'{i}.png'    filenames.append(filename)        # save frame    plt.savefig(filename)    plt.close()# build gifwith imageio.get_writer('mygif.gif', mode='I') as writer:    for filename in filenames:        image = imageio.imread(filename)        writer.append_data(image)        # Remove filesfor filename in set(filenames):    os.remove(filename)
Great! Now that we know the very basics, let’s try this with a bar chart.
We can’t just add one value at a time for bar charts, or else our gif would take forever.
Here we’ll have to replace all values for y to make all bars move simultaneously.
Let’s apply the same code we used before with those small adjustments. We’ll create the values for the x-axis, a list of lists for the y-axis, and use a bar chart instead of a line chart.
x = [1, 2, 3, 4, 5]coordinates_lists = [[0, 0, 0, 0, 0],                     [10, 30, 60, 30, 10],                     [70, 40, 20, 40, 70],                     [10, 20, 30, 40, 50],                     [50, 40, 30, 20, 10],                     [75, 0, 75, 0, 75],                     [0, 0, 0, 0, 0]]filenames = []for index, y in enumerate(coordinates_lists):    # plot charts    plt.bar(x, y)    plt.ylim(0,80)        # create file name and append it to a list    filename = f'{index}.png'    filenames.append(filename)        # repeat last frame    if (index == len(coordinates_lists)-1):        for i in range(15):            filenames.append(filename)                # save frame    plt.savefig(filename)    plt.close()# build gifwith imageio.get_writer('mygif.gif', mode='I') as writer:    for filename in filenames:        image = imageio.imread(filename)        writer.append_data(image)        # Remove filesfor filename in set(filenames):    os.remove(filename)
Ok, that didn’t go well.
Pausing at the end of the GIF is not helping so much. We need to pause for each plot and then proceed to the next.
Also, the bars are just jumping to their next position. We could slow down this movement to make the transition smoother.
There are plenty of ways to make the movement smoother. The easiest is to divide the distance between the current and next locations by the number of frames our transition will have.
# frames between transitionsn_frames = 10x = [1, 2, 3, 4, 5]coordinates_lists = [[0, 0, 0, 0, 0],                     [10, 30, 60, 30, 10],                     [70, 40, 20, 40, 70],                     [10, 20, 30, 40, 50],                     [50, 40, 30, 20, 10],                     [75, 0, 75, 0, 75],                     [0, 0, 0, 0, 0]]print('Creating charts\n')filenames = []for index in np.arange(0, len(coordinates_lists)-1):    # get current and next y coordinates    y = coordinates_lists[index]    y1 = coordinates_lists[index+1]        # calculate the distance to the next position    y_path = np.array(y1) - np.array(y)    for i in np.arange(0, n_frames + 1):        # divide the distance by the number of frames         # and multiply it by the current frame number        y_temp = (y + (y_path / n_frames) * i)        # plot        plt.bar(x, y_temp)        plt.ylim(0,80)        # build file name and append to list of file names        filename = f'images/frame_{index}_{i}.png'        filenames.append(filename)        # last frame of each viz stays longer        if (i == n_frames):            for i in range(5):                filenames.append(filename)        # save img        plt.savefig(filename)        plt.close()print('Charts saved\n')# Build GIFprint('Creating gif\n')with imageio.get_writer('mybars.gif', mode='I') as writer:    for filename in filenames:        image = imageio.imread(filename)        writer.append_data(image)print('Gif saved\n')print('Removing Images\n')# Remove filesfor filename in set(filenames):    os.remove(filename)print('DONE')
Nice! We can definitely improve on the aesthetics of our chart. Let’s try adding some detail.
n_frames = 10bg_color = '#95A4AD'bar_color = '#283F4E'gif_name = 'bars'x = [1, 2, 3, 4, 5]coordinates_lists = [[0, 0, 0, 0, 0],                     [10, 30, 60, 30, 10],                     [70, 40, 20, 40, 70],                     [10, 20, 30, 40, 50],                     [50, 40, 30, 20, 10],                     [75, 0, 75, 0, 75],                     [0, 0, 0, 0, 0]]print('Creating charts\n')filenames = []for index in np.arange(0, len(coordinates_lists)-1):    y = coordinates_lists[index]    y1 = coordinates_lists[index+1]    y_path = np.array(y1) - np.array(y)    for i in np.arange(0, n_frames + 1):        y_temp = (y + (y_path / n_frames) * i)        # plot        fig, ax = plt.subplots(figsize=(8, 4))        ax.set_facecolor(bg_color)        plt.bar(x, y_temp, width=0.4, color=bar_color)        plt.ylim(0,80)        # remove spines        ax.spines['right'].set_visible(False)        ax.spines['top'].set_visible(False)        # grid        ax.set_axisbelow(True)        ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)        # build file name and append to list of file names        filename = f'images/frame_{index}_{i}.png'        filenames.append(filename)                # last frame of each viz stays longer        if (i == n_frames):            for i in range(5):                filenames.append(filename)        # save img        plt.savefig(filename, dpi=96, facecolor=bg_color)        plt.close()print('Charts saved\n')# Build GIFprint('Creating gif\n')with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:    for filename in filenames:        image = imageio.imread(filename)        writer.append_data(image)print('Gif saved\n')print('Removing Images\n')# Remove filesfor filename in set(filenames):    os.remove(filename)print('DONE')
Awesome! There’s definitely lots of room for improvement. You can add a title, change the way we transition by using some interpolation, and even make the bars move on the x-axis.
To work with scatter plots, we’ll need to consider both the x and y-axis. We won’t necessarily have the same number of points to display on every frame, so we need to correct it to make the transitions.
coordinates_lists = [[[0],[0]],                     [[100,200,300],[100,200,300]],                     [[400,500,600],[400,500,600]],                     [[400,500,600,400,500,600],[400,500,600,600, 500,400]],                     [[500],[500]],                     [[0],[0]]]gif_name = 'movie' n_frames=10bg_color='#95A4AD'marker_color='#283F4E' marker_size = 25print('building plots\n')filenames = []for index in np.arange(0, len(coordinates_lists)-1):    # get current and next coordinates    x = coordinates_lists[index][0]    y = coordinates_lists[index][1]    x1 = coordinates_lists[index+1][0]    y1 = coordinates_lists[index+1][1]    # Check if sizes match    while len(x) < len(x1):        diff = len(x1) - len(x)        x = x + x[:diff]        y = y + y[:diff]    while len(x1) < len(x):        diff = len(x) - len(x1)        x1 = x1 + x1[:diff]        y1 = y1 + y1[:diff]    # calculate paths    x_path = np.array(x1) - np.array(x)    y_path = np.array(y1) - np.array(y)    for i in np.arange(0, n_frames + 1):                        # calculate current position        x_temp = (x + (x_path / n_frames) * i)        y_temp = (y + (y_path / n_frames) * i)        # plot        fig, ax = plt.subplots(figsize=(6, 6), subplot_kw = dict(aspect="equal"))        ax.set_facecolor(bg_color)                    plt.scatter(x_temp, y_temp, c=marker_color, s = marker_size)        plt.xlim(0,1000)        plt.ylim(0,1000)        # remove spines        ax.spines['right'].set_visible(False)        ax.spines['top'].set_visible(False)        # grid        ax.set_axisbelow(True)        ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)        ax.xaxis.grid(color='gray', linestyle='dashed', alpha=0.7)        # build file name and append to list of file names        filename = f'images/frame_{index}_{i}.png'        filenames.append(filename)        if (i == n_frames):            for i in range(5):                filenames.append(filename)        # save img        plt.savefig(filename, dpi=96, facecolor=bg_color)        plt.close()# Build GIFprint('creating gif\n')with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:    for filename in filenames:        image = imageio.imread(filename)        writer.append_data(image)print('gif complete\n')print('Removing Images\n')# Remove filesfor filename in set(filenames):    os.remove(filename)print('done')
And that’s it!
We explored how to create and save the charts with Matplotlib, how to use Imageio to build the GIFs, and then we checked some different chart types and some of the challenges we may find while handling each of them.
I’ve used those same concepts to write letters in a scatter plot. You can check more about that project here.
Thanks for reading my article!