
Visualisation is always an important usage of Python for Data Science and Data Analytics. Sometimes, we want to let our plots move for more advanced presentation and insights. However, most animated visualisation libraries require some extra effort to learn, such as matplotlib.animation
. When we just want to generate some animated graph in a quick and easy way, this might not be the best approach.
In this article, I will introduce a relatively less scalable but much easier way to animate our Python plots, which is using the ImageIO library. This library is commonly used to manipulate images in Python, as well as to combine multiple images for generating GIFs. It is surprisingly easy to use.
Before we can use the ImageIO library, we need to install it using pip
as follows.
pip install imageio
1. An Example of Line Chart

Let’s start with a basic line chart for this demo. To simplify it, I want to generate a NumPy array with 50 integers. Then, these integers can be plotted as a line chart.
import numpy as np
import matplotlib.pyplot as plt
import imageio
np.random.seed(0)
SIZE = 50
y = np.random.randint(-5, 5, SIZE)
plt.plot(y)
plt.ylim(-10, 10)
plt.show()

In the code above, I set the random seed so that you can reproduce exactly the same result as mine if you want. The SIZE
is defined as a constant so that you can change it as your preference.
The integers are generated in the range [-5,5]. Also, to make the graph easier to read, I would like to add the ylim(-10, 10)
so that all the points are in the middle part of the chart.

Now, we have this static basic line chart. Let’s say that we want to animate the line chart by plotting the points one by one. It also would be better to have the result in a GIF, so we can embed it anywhere we want.
Of course, the appropriate method is to use the matplotlib.animation
module. However, the quick and easy way I would introduce is to generate the frames as individual PNG images. Then, we can use ImageIO to combine them together as a GIF.
1.1 Generate Frames
In fact, generating the frames is not quite difficult. The idea is to plot the line chart with 2 points, 3 points, … and 50 points. Each plot should be saved into a separate image.
The code is as follows
for i in range(2, SIZE+1):
plt.plot(y[0:i])
plt.ylim(-10, 10)
plt.savefig(f'line-{i}.png')
plt.close()

The for-loop starts with 2 points because a single point cannot be generated as a valid line chart. It will stop at SIZE+1
so that the range()
will be terminated at the exact number of points. In other words, the last i
is 50 in our case.
It is important to define ylim
in every frame. Otherwise, the y axis will be automatically determined so that it will be different in the frames.
After the plot has been configured, we can save the figure as a PNG image with a predefined filename pattern. This filename pattern will be used later to populate the frames. Finally, we need to close the plot by calling plt.close()
. So, the next frame in the next loop can be generated correctly.
After running this piece of code, 49 PNG files will be generated in the current working directory.

1.2 Generating GIF
The next step will be combining them together into a GIF. The ImageIO library provides a "writer" that we can easily append the images as frames.
with imageio.get_writer('line.gif', mode='i') as writer:
for i in range(2, SIZE+1):
image = imageio.imread(f'line-{i}.png')
writer.append_data(image)

We can use the with-statement so we don’t need to worry about closing the stream. The name of the GIF file will be called line.gif
. The flag mode='i'
is a hint that tells ImageIO the input will be images.
Then, we can have another for-loop to get the index. So, we can use the index to obtain the filenames of the frames. Once we have the filename, just append it to the current writer.
After we run the above code, the GIF file line.gif
should already be generated.


BTW, if the frames are not useful anymore, we can use the following code to delete them.
import os
for i in range(2, SIZE+1):
os.remove(f'line-{i}.png')

1.3 A Small Improvement
The GIF above shown might not be perfect. Specifically, the x-axis is also moving with the frames. It may be OK depending on what we want to achieve, but suppose we want to fix the x-axis in the GIF, we can do it definitely.
This example also shows that the method that is being introduced here is quite flexible. Basically, when we want to have some features in our animated charts, we do not rely on any APIs of the library, but on how the frames were generated.
In this case, we just need to add xlim()
configuration in the original code to generate the frames with a fixed x-axis.
for i in range(2, SIZE+1):
plt.plot(y[0:i])
plt.ylim(-10, 10)
plt.xlim(0, 50)
plt.savefig(f'line-{i}.png')
plt.close()

After that, the code of ImageIO for generating the GIF does not even need to be changed, because we have updated the content of the frames but the number of images is still the same.
Finally, the new GIF looks as follows.

2. A Fancier Example of Bar Chart

The method has been well introduced in the above example of the line chart. However, I just want to show that the method has more potential. This time, let’s animate a bar chart.
Before the demo can start, we need to define an x-axis and a list of the y-axis. The reason why we need a list of y-axis values is that we want to let the GIF shows multiple frames and each frame is a bar chart. In other words, they have the same x-axis but different y values.
x_axis = [1, 2, 3]
y_axis_list = [
[0, 0, 0],
[1, 2, 3],
[3, 2, 1],
[5, 5, 5],
[7, 7, 7],
[9, 2, 9],
[2, 9, 2],
[1, 1, 1],
[9, 9, 9],
[0, 0, 0]
]

The above code is just an example. There is no pattern, just made them up 🙂
2.1 Generate Bar Chart GIF
Then, let’s generate the frames bar chart.
png_files = []
for i, y_axis in enumerate(y_axis_list):
# Create Plot
plt.bar(x_axis, y_axis)
plt.ylim(0,10)
# Create PNG file
filename = f'bar-{i}.png'
png_files.append(filename)
# Save Figure
plt.savefig(filename)
plt.close()
with imageio.get_writer('bar.gif', mode='i') as writer:
for filename in png_files:
image = imageio.imread(filename)
writer.append_data(image)

This time I don’t want to be bothered with the range()
method in the for-loop. We can define a list png_files
to contain all the filenames of the frames. So, later on, when we want to generate the GIF from the PNG images, we just need to get the filenames from this list.
The code for generating the GIF is nothing different from the previous example. This also shows that the method is quite generic.
The GIF generated is as follows.

Ummm, it works, but not ideal. In fact, it can’t be read at all.
2.2 Generate GIF with Smooth Transition
Thinking from the perspective of animation. Good looking animation is nothing but more FPS and smooth transition. The basic idea is to add more transitioning frames between two states.
Therefore, we can do the following.
smooth_coef = 10
png_files = []
for i in range(0, len(y_axis_list)-1):
# Get Current & Next Frame
y_axis_curr = y_axis_list[i]
y_axis_next = y_axis_list[i+1]
# Generate Middle Frames
y_diff = np.array(y_axis_next) - np.array(y_axis_curr)
for j in range(0, smooth_coef+1):
y_axis = (y_axis_curr + (y_diff / smooth_coef) * j)
# Create Plot
plt.bar(x_axis, y_axis)
plt.ylim(0,10)
# Create PNG file
filename = f'bar-{i}-{j}.png'
png_files.append(filename)
# Stretch the last frame
if j == smooth_coef:
for _ in range(5):
png_files.append(filename)
# Save Figure
plt.savefig(filename)
plt.close()

Let me explain this. The smooth_coef
stands for the "smooth coefficient". It means that we want to add 10 transition frames between every two major frames. The major frames are exactly the y-axis in the demo dataset.
In the for-loop, we will get the current y-axis and the next one. After that, we can use the next y-values to subtract the current one. So, the result will be the "difference" between these two major frames. Then, if we let the "difference" divide by the smooth coefficient, we will get the stepwise difference. In summary, we can calculate the differences between each transitioning frame. All the transitioning frames added up will turn the current frame into the next frame.
y_axis = (y_axis_curr + (y_diff / smooth_coef) * j)
Another trick is to let the GIF "stop" at the critical frame for a while, so we can see it for a bit longer time. This can be done by repeating the filenames at certain times so that the frame will be repeated. In the generated GIF, we will see the frame multiple times which also means a longer time.
Let’s have a look at the result.

That’s much better!
3. The Method is Generic

Unlike any specific plotting libraries such as the matplotlib.animation
, the ImageIO-GIF method is much more generic. That is, we are not even limited by using what libraries. As long as the figure can be saved into image files, we can use this method to let it move.
A quick example is as follows. We can use Seaborn to generate the bar chart.
import seaborn as sns
sns.set_style('darkgrid')
The Seaborn module is imported and the style is set. Then, the only part of the code we need to change is as follows.
sns.barplot(x_axis, y_axis)

The resulting GIF looks as follows.

Summary

In this article, I have introduced a method for animating Python visualisation into GIF without using any purpose-built plot animation libraries. The idea is to generate the plot frame by frame into image files. Then, these image files can be combined together as a GIF using the ImageIO module.
The method is also quite generic. We don’t need to learn any API or methods for certain animation styles. All we need to do is to generate different frames to achieve the requirements. Also, it even does not limit to any visualisation libraries because it relies on the images generated as frames.
Please be noted that this method might not be a regular one for plotting. One issue of it could be the scalability because we need to generate many individual files as frames before we can get the GIF.
If you feel my articles are helpful, please consider joining Medium Membership to support me and thousands of other writers! (Click the link above)