Skip to main content

Displaying Plots Inside Loops

In the early stages of some programming projects, I often like to explore the problem visually: to run a series of simulations with different values of the input parameters and create a series of graphs in order to gain some intuition about the problem. A convenient approach is to embed some plotting commands in a loop.

For example, suppose I want to see what the first ten Bessel functions look like. It seems that the following script should do what I want:

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import jn                # Import Bessel function.

r = np.linspace(0,20,101)

for n in range(10):
    plt.plot(r, jn(n,r))                    # Draw nth Bessel function.
    plt.title("Bessel function J[%d](r)." % n)
    input("Press <Enter> to continue.")     # Wait for user input to continue.
    plt.cla()                               # Clear axes for next plot.

However, when I run the script, all I see is an empty plot window, even though I have to hit <Enter> ten times at the command prompt!

This behavior is due to the way Python handles events in the graphical user interface (GUI) used to display plots. Basically, Python creates the figure, but moves on to the next command before giving me a chance to look at it. What I need is some way to force Python to display the plot until I am ready to move on. (Asking for input at the command line does not accomplish this.)

plt.waitforbuttonpress()

PyPlot provides the perfect function: plt.waitforbuttonpress(). Referring to its documentation, we see that this function is a “blocking call to interact with the figure.” In other words, Python is not allowed to execute any other commands until the user interacts with the figure. The documentation also explains that there is an optional timeout argument, and the function returns different values depending on what happens:

  • True — The user pressed a key on the keyboard.
  • False — The user clicked the mouse.
  • None — The (optional) timer elapsed.

Clicking the mouse on buttons like “Zoom” or “Save Figure” do not return a value. Only a mouse click within the actual plot causes the function to return False.

The following loop will force Python to display each plot until I press a button on the keyboard or click with the mouse:

for n in range(10):
    plt.plot(r, jn(n,r))                # Draw nth Bessel function.
    plt.title("Bessel function J[%d](r)." % n)
    plt.waitforbuttonpress()            # Display plot and wait for user input.
    plt.cla()                           # Clear axes for next plot.

When I run the script, I can see each Bessel function, but if I try to zoom in on the figure, Python advances to the next plot. I can get around this by placing the command inside an “infinite” loop and taking advantage of the different return values of the function:

for n in range(10):
    plt.plot(r, jn(n,r))                    # Draw nth Bessel function.
    plt.title("Bessel function J[%d](r)." % n)
    while True:
        if plt.waitforbuttonpress(): break  # Exit loop if user presses a key.
    plt.cla()                               # Clear axes for next plot.

At each iteration of the while loop, Python waits for the result of plt.waitforbuttonpress(). If I click the mouse to zoom or move the axes, the return value is False, so the loop executes again. However, if I press a key on the keyboard, the return value is True and Python breaks out of the loop. I can use the mouse to interact with each plot as much as I like, then press a key to move on to the next one when I am ready.

You can accomplish the same task in fewer lines with a different while loop:

while not plt.waitforbuttonpress(): pass

This while loop does nothing at each iteration (pass) and continues to cycle so long as the return value of plt.waitforbuttonpress() evaluates to False.

Exercises

Rewrite the script so that Python will display each plot for 5 seconds unless the user presses a key or clicks the mouse (inside the active portion of the plot window), at which point the countdown timer resets.

Rewrite the script so that Python will display each plot until the user presses a key on the keyboard or there are 10 seconds of inactivity — whichever comes first.

Comments

Popular posts from this blog

Paths in Python

How do you get your Python interpreter to find modules that are not located in your current working directory? The answer is … you tell it where to look. When you type something like from my_module import my_function Python searches a collection of directories (i.e., folders) on your computer. If the directory containing <my_module.py> is not in this collection, you will receive an ImportError . This can be frustrating if you know the file exists, and even more so if you know where it exists. In this post, we will take a brief look at how to add paths to the collection of directories searched by Python. Paths A path is essentially a set of directions to a file: /Users/username/modules/my_module.py [Mac OS X, Linux] C:\modules\my_module.py [Windows] It tells your operating system how to navigate from a fixed starting point — the “root directory” / in Unix-based systems, or C:\ in Windows — through a collection of folders to the de

Illuminating Surface Plots

Matplotlib provides functions for visualizing three-dimensional data sets. One useful tool is a surface plot. A surface plot is a two-dimensional projection of a three-dimensional object. Much like a sketch artist, Python uses techniques like perspective and shading to give the illusion of a three-dimensional object in space. In this post, I describe how you can control the lighting of a surface plot. Surface Plots First, let’s look at some of the options available with the default three-dimensional plotting tools. This script will create a surface plot of a Bessel function. Its ripples will emphasize the effects of lighting later. import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # Import 3D plotting tools. from scipy.special import jn # Import Bessel function. # Define grid of points. points = np.linspace(-10, 10, 51) X, Y = np.meshgrid(points, points) R = np.sqrt(X**2 + Y**2) Z = jn(0,R) # Create 3D surface plo

Raising a Figure Window to the Foreground

This post describes a utility function that will raise a plot window to the foreground of your screen. The function will only work with the Qt graphics backend, so I will start with a brief overview of graphics backends. If you just want to use the function as a black box, you can do the following: Set the Graphics Backend to “Qt” in the Spyder preferences menu. Copy this function into your working directory. Graphics Backends You may have have written a script to produce precisely the data you need, but a lot of processing is required to transform these numbers into a figure. You need to create a plot window, draw a figure inside of it, and manage all of the attributes that control a figure’s appearance: title, axis labels, line widths, colors, tick marks, etc. All of this happens in the background when you type a command like plt.plot(x,y) . A graphics backend is the software that Python uses to physically draw a figure on your computer screen. If you have already import