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.
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
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...
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
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
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...
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...
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
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...