Skip to main content

Speeding Up Matplotlib Animations in Tkinter

This will be a short technical post covering the process I used to optimize GUI (Graphical User Interface) updates for my kinematics simulator application (pictured below). Using these brief techniques, I was able to reduce the time spent rendering UI updates by 98.8%. For anyone who's interested, the project can be found here. It uses matplotlib for animated plots embedded within a tkinter GUI.

If you're looking to embed matplotlib inside a GUI application, or even if you're working on standalone animated plots, I'm hoping this post will provide some useful tips for improving performance.


Initial Performance

For background, my goal was to achieve an update frequency of about 20fps (or around 50ms per frame). Looking at the logs shown below, it's clear that this wasn't achievable with the initial design. The overall elapsed time for each frame is shown, along with a breakdown between computing the frame data and actually updating (drawing) the plots. Initially, the average time to render a frame was 94.22ms, with nearly 99.8% of the time being spent updating the plots. 


Asynchronous Canvas Updates

There were two main issues causing this latency, the first of which is a relatively simple fix for huge performance gains. And that is asynchronously drawing to the matplotlib canvas. For context, I'm using FigureCanvasTkAgg, a special canvas class provided by matplotlib that allows you to embed plots directly into a tkinter GUI application as a standard widget (see Embed in Tk for more information). After updating the plot data, you need to invoke the draw() method on the canvas object in order to update the UI.

The problem is that the draw() method is synchronous, meaning it blocks execution until it's able to finish drawing to the canvas. Luckily, there's is an alternative, which is the canvas' draw_idle() method. Instead of updating the UI immediately, the draw_idle() method schedules a redraw to happen asynchronously (in the background) when the GUI is idle. Simply replacing draw() with draw_idle() resulted in a significant 80% decrease in time to render frames, bringing the new average frame time to about 18.14ms. Though this easily allows for enough frames to reach the 20fps goal, plot drawing is still consuming about 99% of the frame time, so there's more we can do.

It should be noted that if you're using FuncAnimation or another animation controller provided by matplotlib, it will already use draw_idle() to update the canvas on each frame. However, in my case I developed my own animation controller to allow for more control over the animation's lifecycle and better integration with the tkinter GUI.

Dynamic Plot Updates

The next issue was the method by which I was updating the plot data. Initially, I was clearing and re-plotting the Axes for each iteration of the loop. Admittedly, this is very bad practice, but it's relatively common because its easy to code (using the same functions for initializing and updating the plots) and the alternative is slightly less intuitive.

You'll notice that the Axes.plot() method returns a Line2D object (or Line3D in the case of a 3D plot). This line object has a method called set_data() which takes the same arguments as the Axes.plot() method but is intended specifically for updating an existing line (see matplotlib.animation). 

Assuming the structure of your plot isn't changing between updates, this is the preferred method for updating plot data to maximize rendering performance. This is because recreating plots requires allocating new artists, re-computing transforms, as well as re-parsing and redrawing the figure layout, background grids, titles, and axes. Updating an existing line object avoids most of that work because only the underlying data changes, allowing matplotlib to simply read the new numbers and render them. See the example below for how this would be used in the code.

# when initializing the plot
line, = ax.plot(arm_config[:, 0], arm_config[:, 1])

# later when updating the plot
line.set_data(
    arm_config[:,0],
    arm_config[:,1]
)

In the case of a 3D plot, the implementation will differ slightly because the set_data() method only accepts coordinates for two dimensions. For that reason, it's recommended to use the set_data_3d() method which accepts x, y and z coordinates (see mpl_toolkits.mplot3d.art3d.Line3D for more information).

Changing the code to utilize set_data() reduced the average frame time to 1.12ms, another massive improvement. Now plot drawing is consuming only 83% of the frame time for forward kinematics, and for inverse kinematics the computation time actually ends up exceeding the time needed to draw the plots.

Conclusion

The changes described above were responsible for a reduction in frame rendering time of over 98%, from 94.22ms to 1.12ms. This would allow the animation to achieve well over 20fps if the application requires it, and at the very least provides much needed leeway for more complex computations (such as inverse kinematics). While the exact gains will vary depending on the application, the result demonstrates how avoiding unnecessary redraws and updates can dramatically improve the performance of matplotlib animations.







Comments

Popular posts from this blog

Why IoT Security Is Still Behind in 2025: What You Should Know If You Want Your Deployments to Last

Over the past year, campaigns including  BadBox 2.0 ,  Matrix , and  LapDogs  have been documented compromising tens of millions of connected devices worldwide. There are billions of devices now online and threats are continuing to emerge. It's clear that IoT security is still lagging behind its rapid pace of adoption. There are certainly benefits to this widespread adoption. IoT is enabling real-time, data-driven decision making at a remarkable scale. Applications such as wearable devices, environmental monitoring, remote patient care, and predictive maintenance wouldn’t exist without IoT. Analysts project IoT connections  exceeding 40 billion within the next decade, a trend that highlights the need to build security into every layer of the ecosystem. The State of IoT Security Systematic security measures for IoT are only beginning to be widely implemented, driven in large part by emerging regulations and increased demands for security. Historically, ma...