Plotting Live Data with Python using Matplotlib

Created:  
Updated:   07Aug2024   06:05:49   UTC 2024-08-07T06:05:49Z
Rating:   (0 reviewsThis article has not been rated yet)

This tutorial covers live plotting with Python using the Matplotlib module. When data is being piped onto a computer from a device such as a microcontroller, it can be plotted in real-time as soon the data is available, allowing you to visualize the data live. The examples in this tutorial will be made as simple as possible without any external connections by representing external devices with a random number generator inside Python.

Two different methods for creating live plots will be covered. The first method has timestamp labels for each data point that is redrawn on the plot every frame. The second method, known as blitting, has higher performance by only redrawing the changing data points each frame, while preserving the unchanging background, axes, and axes labels, but involves a trade-off by sacrificing the timestamp labels.

Setup

Both Python and the Matplotlib package must first be installed on the computer to run the examples in this article.

Python Installation

Python can be downloaded from python.org. A detailed tutorial on how to install Python on Windows, Mac, or Linux can be found at realpython.com.

Matplotlib Installation

Instructions for installing the Matplotlib package can be found on their home page (matplotlib.org) and installation guide page (matplotlib.org). You can install Matplotlib with either pip or conda. The pip command is given below.

> pip install matplotlib

Live Plotting with Timestamps

The computer Python code given below generates random numbers in the range -0.5 to +0.5 and plots the data every second.

Python Matplotlib Live Plotting with Timestamps Code
            

Imported Libraries

The Python code starts by importing the following libraries.

import os:
For obtaining the current filename of the script being ran using os.path.basename (docs.python.org).
import matplotlib.pyplot as plt:
A state-based interface to matplotlib that provides an implicit, MATLAB-like, way of plotting. It acts as the figure GUIGraphical User Interface manager used to open figures on your screen and make changes to the figure axes. More details about the functionality of matplotlib.pyplot can be found at matplotlib.org.
import matplotlib.animation as animation:
A class to make a live animation in Matplotlib. The animation is advanced by a timer (typically from the host GUI framework) which the Animation object holds the only reference to. The animation object has a method FuncAnimation() that handles 'blitting' (to dramatically improve the live performance), to be non-blocking, not repeatedly start/stop the GUI event loop, handle repeats, multiple animated axes, and easily save the animation to a movie file. More details on Matplotlib's animation can be found at matplotlib.org.
from matplotlib import style:
Different styles can be set in Matplotlib that define the visual appearance of the plot. Documentation on Matplotlib's style APIApplication Programming Interface can be found at matplotlib.org.. The "fivethirtyeight" styling (matplotlib.org) is a popular choice for styling plots, which tries to replicate the styles from the website FiveThirtyEight.com that focuses on opinion poll analysis, politics, economics, and sports blogging in the United States. The 'dark_background' style (matplotlib.org) was chosen to be consistent with the dark theme of this website.
import datetime as datetime:
The datetime module is needed for the x-tick label time stamps of the data in the plot.
import random:
Python's random library (docs.python.org) is imported to create random numbers to represent data from external devices. By default, the random numbers are generated from the current system time, which will give different results each time the script is ran. Reproducability of the results is preferred for development, testing, and debugging, which can be accomplished by initializing the random number generator with a seed: random.seed(a=10) documented at docs.python.org.

Read Data Function: read_data()

The read_data() function simulates data from an external device by generating random numbers in the range -0.5 to +0.5 along with a timestamp. The code in this function can be replaced, for example, by PySerial read calls to the serial port to collect data from external hardware such as a microcontroller.

Animation Function: FuncAnimation()

The animation function call FuncAnimation() repeatedly calls a function you define for the plot animation.

    ani = animation.FuncAnimation(
                                  fig=fig,
                                  func=animate,
                                  fargs=(xs, ys),
                                  interval=1000,
                                  )
          
fig:
This is the figure object obtained when creating the figure.
func:
The function to call each frame. This is assigned to the function animate(i, xs, ys), where i is the frame iterator.
fargs:
The input arguments to the function assigned to func. These are additional arguments passed to each call to func. This is set to the x samples and y samples (xs, ys).
interval:
The time interval in milliseconds between frames. This is set to 1000 milliseconds (1 second). If this is not set, the default value of 200 milliseconds is used.
ani:
The output of the animation function. The animation function needs to persist, so it must be assigned to a variable. Otherwise, the Animation object will be garbage-collected and the animation stops.

More arguments can be used with the FuncAnimation function, which is documented at matplotlib.org.

To avoid cluttering the plot, the number of samples plotted in a frame is limited to only 10 samples at a time with x_len = 10, where the plot only shows the last 10 samples. The lists that hold the samples are truncated to hold the last 10 samples with xs = xs[-xlen:] and ys = ys[-xlen:].

The x tick labels have a time format of Hour:Minute:Seconds. Since these tick labels are unique for each data point, they must be updated (redrawn) on the plot for every frame. They are also rotated 45 degrees to avoid overlap on the x-axis. Recreating the x-tick labels and rotating them on the plot for every frame is extra overhead that slows down the plot drawing, limiting how quickly the animate function can be called. If these labels are not needed, higher performance can be gained by using blitting where only the plot line is updated every frame.

Output

The first 15 seconds (15 frames) of the live plotting output is shown in the figure below. The first 10 frames build up on samples to fill out the plot, then the plot only displays the latest 10 samples for every frame.

Live Plotting with Timestamps Output

Timing and Performance

In this section the timing performance of the animation will be measured. The runtime of the plotting routine will vary depending on the computer used, but as will be shown later the speed results of this code can be compared to an example using blitting ran on the same machine. The timing performance code is given below along with the plot results showing the time between frames.

The time interval between animation calls has be set to zero (interval=0) in order to measure only the plotting time rather than the frame delay. Since the animation is now running at such a high speed (less than a second), the data timestamps has been extended out to milliseconds to produce unique labels for each point. The time library has been imported to use time.perf_counter() to capture the time the animation frames occur in the time_array. The number of frames is set to nframes = 1000. After the frame times are recorded, the timing_plot(time_array) is called to produce the timing plot, which shows 40.3ms per frame (24.8fpsFrames Per Second) on average.

Timing of Plotting with Timestamps Code
              

Timing Results of Live Plotting with Timestamps

Live Plotting with Blitting

The performance of live plotting can be significantly improved by using a method known as blitting, a technique that only redraws the graphical elements that change while preserving the unchanging elements. Matplotlib takes the existing rasterized figure and 'blits' any changing elements on top. Most of the time consumed by Matplotlib when making a plot is drawing the background, axes, and labels, so not having to draw these elements for each frame can save a significant amounts of time plotting.

There is a trade-off determining which elements of a plot should remained fixed and which elements to update. If measurements from a microcontroller are made at known fixed intervals, then the x-axis time labels can be sacrificed by removing them completely from the plot. The y-axis tick labels can be tentatively fixed at an expected range and adjusted dynamically only when the data falls outside of that range. The background, title, and axes labels should remain fixed. This is the design that will be given in the code below.

This code example generates random numbers in the range -0.5 to +0.5 every 200 milliseconds and plots the data as it becomes available using blitting. In Matplotlib, blitting is set by passing in the argument blit=True to the animation function.

Python Matplotlib Live Plotting with Blitting
            

Output

The first 6 seconds (150 frames) of the live plotting output is shown in the figure below. The first 10 frames build up on samples to fill out the plot, then the plot only displays the latest 10 samples for every frame.

Live Plotting with Blitting Output

Timing and Performance

In this section the timing performance benefits of blitting will be measured. The runtime of the plotting routine will vary depending on the computer used, but this will show the speed improvements compared to the example given by redrawing timestamps. The timing performance code is given below along with the plot results showing the time between frames.

The time interval between animation calls has be set to zero (interval=0) in order to measure only the plotting time rather than the frame delay. The time library has been imported to use time.perf_counter() to capture the time the animation frames occur in the time_array. The number of frames is set to nframes = 1000. After the frame times are recorded, the timing_plot(time_array) is called to produce the timing plot, which shows an average speedup from 40.3ms per frame (24.8fpsFrames Per Second) to 8.8ms per frame (113.6fpsFrames Per Second) with blitting.

Timing of Plotting with Blitting Code
              

Timing Results of Live Plotting with Blitting

Conclusion

Two different methods for creating live plots were covered. The first method had timestamp labels for each data point that were redrawn on the plot every frame. This would be a good approach for slower data rates (> 200ms per frame or < 5fps) where you want to track measurements that do not occur at regular time intervals.

The second method, known as blitting, only redrew the changing data points for each frame, while preserving the unchanging background, axes, and axes labels. The blitting method had a trade-off of sacrificing the timestamp labels, but gained a 5x performance improvement on average in these examples. This would be a good approach for high speed applications (between 20ms and 200ms per frame, or between 50fps and 5fps) where the data sample occur at regular time intervals.

References

A summary of the references used in this article is given below.

References
Reference Link
Python Download docs.python.org
Tutorial on how to install Python on
Windows, Mac, or Linux
realpython.com
Matplotlib Documentation matplotlib.org
Matplotlib Installation Guide matplotlib.org
Matplotlib pyplot Documentation matplotlib.org
Matplotlib Animation Documentation matplotlib.org
Matplotlib Styles API Documentation matplotlib.org
Python os Module Documentation docs.python.org
Python datetime Module Documentation docs.python.org
Python Random Module for Random Numbers docs.python.org
Python random.seed() Documentation docs.python.org
Matplotlib animation.FuncAnimation() Documentation matplotlib.org
Python time.perf_counter() Library docs.python.org

Related Content


Python PySerial I/O

Created:   27Oct2022   23:29:36   UTC 2022-10-27T23:29:36Z
Updated:   07Aug2024   04:39:18   UTC 2024-08-07T04:39:18Z
Rating:  (0 reviewsThis article has not been rated yet)

PySerial functional interface and how it works under the hood.


Recording Data on a Computer to a CSV File using Python and PySerial

Created:   22Oct2022   19:21:09   UTC 2022-10-22T19:21:09Z
Updated:   07Aug2024   05:23:19   UTC 2024-08-07T05:23:19Z
Rating:  (0 reviewsThis article has not been rated yet)

Read in data to a computer from a microcontroller over USB using PySerial and storing the data to a CSV file. Microcontroller programs are given in both Arduino code and MicroPython.

Products


Article Rating

Sign in to rate this article

Sign In


(0) Comments

Sign in to leave a comment

Sign In