Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Interactive Applications using Matplotlib

You're reading from  Interactive Applications using Matplotlib

Product type Book
Published in Mar 2015
Publisher
ISBN-13 9781783988846
Pages 174 pages
Edition 1st Edition
Languages

Chapter 2. Using Events and Callbacks

 

Wait time is the worst

I can hardly sit

No one has the time

Someone is always late

 
 --The Strokes, Call Me Back (2011)

The callback system in Matplotlib is central to its interactivity. Unless you are working with the interactive plotting mode on, execution of the script stops when plt.show() is called. Without the ability to execute any additional code, the only way to program interactivity is to register actions to be taken upon some event such as a button click, mouse cursor motion, or key press. Matplotlib's callback system has a base set of events and many callbacks that we have already discussed, such as the default keymap discussed in the previous chapter and the ability to pan a plot. Furthermore, it is possible to add new kinds of events, giving the developer full access to Matplotlib's cross-platform callback system.

Making the connection


The callback system is figure-oriented. Any GUI action that can trigger a callback can only happen to whichever figure window is currently in focus. There are no global actions that can trigger callbacks across multiple figures. The callback function will get an Event object that contains pertinent information about the fired event. In the following example, we will hook up two events to a figure: a keyboard button press and a mouse button press. There are two callback functions, each printing out some of the available pieces of information for their respective events:

Code: chp2/basic_mpl_connect.py

from __future__ import print_function 
import matplotlib.pyplot as plt 

def process_key(event): 
    print("Key:", event.key) 
def process_button(event): 
    print("Button:", event.x, event.y, event.xdata, event.ydata, event.button) 

fig, ax = plt.subplots(1, 1) 
fig.canvas.mpl_connect('key_press_event', process_key) 
fig.canvas.mpl_connect('button_press_event', process_button...

The big event


What is the purpose of interactive plotting? Why is it important for Matplotlib to provide this feature? It is important because you want to interact with your data. What you plot in the figure is a visual representation of your data, and giving it interactivity brings that data an extra step closer to the real world by providing your users the means to interact with that data in a more physically intuitive manner. It is all about data exploration.

So far, for our project, we have only developed the means to display our storm and radar data. While we could simply use these viewers and then manually edit the associated shapefile, it would not be practical. We should be able to provide users the means to interrogate their data. To do this, we will use pick_event to add the ability to select and deselect some tracks. As a simple example, we will make a track thicker when it is selected and make it thinner when it is deselected (or simply, selected again). Let's build upon the track...

Breaking up is the easiest thing to do


Try the previous radar example again. This time, go forward a few frames and then zoom in with the zoom tool. Now go back a frame.

Go ahead, I'll wait.

Surprised? Remember that Matplotlib has its own built-in keymap. In the default keymap, the left arrow means to go back to a previous view. When we zoomed in and then pressed the left arrow key, not only did we go back a frame via our callback, but we also went back to the original view prior to zooming via Matplotlib's default keymap. The default keymap is a very important and useful feature for providing basic interactivity for most users. However, when developing your own application using Matplotlib, you might want to disable Matplotlib's keymap entirely. The following example shows how to do that while demonstrating the next important feature of the callback system: disconnecting a callback. In this example, you can now press any non-system key or combination of keys without ever triggering a built...

Keymapping


We can see that our application is going to grow in complexity very soon add we continue to add features. Our current manner of keymapping is probably not going to be easily maintainable as the number of actions increase. Let's take a moment to implement something better. The most essential feature of a keymap is to tie a predefined action to an arbitrary key or key combination. This seems like the perfect job for a dictionary. Furthermore, as the keymap grows, it will become important to be able to display the keymap in a helpful manner to your users. Each key/action pair will need to come with a description that can later be displayed on demand. Also, keeping in mind that our ControlSys class is likely to grow in complexity soon, let's implement this keymap feature as a separate class that ControlSys will inherit. The code is as follows:

Source: chp2/stormcell_anim_with_keymap.py

class KeymapControl:
    def __init__(self, fig):
        self.fig = fig
        # Deactivate the...

Picking


We demonstrated pick events earlier, showing how to select a storm track, changing its thickness, but we haven't incorporated picking into our current design yet. Much in the same vein as the KeymapControl class, let's create a PickControl class that will keep a list of pick functions (pickers) and manage their connection to the callback system for us:

Source: chp2/select_stormcells.py

class PickControl:
    def __init__(self, fig):
        self.fig = fig
        self._pickers = []
        self._pickcids = []

    def connect_picks(self):
        for i, picker in enumerate(self._pickers):
            if self._pickcids[i] is None:
                cid = self.fig.canvas.mpl_connect('pick_event', picker)
                self._pickcids[i] = cid

    def disconnect_picks(self):
        for i, cid in enumerate(self._pickcids):
            if cid is not None:
                self.fig.canvas.mpl_disconnect(cid)
                self._pickcids[i] = None

    def add_pick_action(self, picker...

Data editing


Sometimes, it is easy to lose sight of your original goals when developing an application. We have been so focused on adding all sorts of bells and whistles to this project that we have forgotten about its most important purpose: the editing of storm cells and their tracks. Our application, so far, is strictly a data viewer, and a limited one at that. We are not able to interrogate the display for more information about the features we see, nor are we able to save our changes, much less even make any changes. We demonstrated a track deletion feature back in the beginning of the chapter, but that information could not be linked back to the source dataset for modification.

It is at the beginning of an application development that one must be very careful. It is very easy to conflate the display with the data, as we have already done. Conflating data and display can cause two kinds of problems. First, your data can become tied up in some obscure, inscrutable display object that...

User events


We have now seen our storm cell editing application grow in complexity as we start adding even just a few features. The stormcell editor has a gamut of interactive features and can delete storm cells and save the edited stormcells. And for good measure, this application is cross-platform and can work using just about any of the major GUI toolkits that are available, all in approximately 200 lines of code. Now, before we start patting ourselves on the back, there is still a lot more to be done. The editor still does not display any storm tracks, which will be another set of artists to manage along with the storm cell polygons. The track and storm cell display will need to be maintained together as they share a common underlying data. For example, the selection of a track should also trigger a selection of a storm cell in that track, and vice versa. The deletion of a storm cell should also trigger an update of its track line.

Managing all of this in more traditional procedural coding...

Editor events


Let's now re-imagine our existing features as a set of events:

  • Change frame

  • Select storm

  • Deselect storm

  • Hide storm cells

  • Delete storm cell

  • Save storm data

  • Display help

We will add two new methods to the ControlSys class: _connect() and _emit(). They are merely shorthand for the mpl_connect() command and the recently introduced process() method. In the constructor, we will connect some methods to the events we have just listed. In the case of the help method and the storm saving method, the methods originally supplied to the keymap will be connected to these two new events, and the keymap will instead merely call _emit() of the respective events. This can give a taste of fully customizable keymaps in the future. Meanwhile, this is what our constructor and the two new _emit() and _connect() methods now look like:

Source: chp2/stormcell_editor2.py

class ControlSys(KeymapControl, PickControl):
    def __init__(self, fig, raddisp, data, polygons, stormdata):
        self.fig = fig
    ...

Summary


We have come a long way in this chapter learning about the Matplotlib events and the built-in callback system. Our project application has grown in complexity significantly, requiring multiple refactors along the way. First, we connected to Matplotlib's GUI-based event system, particularly the key press and pick events. We also learned how to disconnect a callback function, both our own and Matplotlib's default keymap handler.

Then, you learned how to develop a more general keymap handler that even manages its own help documentation that could be produced on demand. After that, we added in a similar artist pick handler that allowed for different kinds of pickers to be used. With these two controllers working independently of each other, they were able to produce interactive features that they could not do on their own.

Next, we took a step back from our project and re-examined its goals as a storm track/cell editor. We examined the implications and the ease of conflating data and display...

lock icon The rest of the chapter is locked
You have been reading a chapter from
Interactive Applications using Matplotlib
Published in: Mar 2015 Publisher: ISBN-13: 9781783988846
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime}