Chapter 4. Event Handling and Interactive Plots

This chapter marks the beginning of our transition into specialized topics. While we will continue making references to the preceding overview, the subsequent chapters will focus on taking you deeper into the domain of advanced matplotlib usage.

In this chapter, we will delve into matplotlib events and interactive plots and cover the following topics:

  • A general overview of event-based systems
  • The anatomy of an event loop
  • The GUI toolkit event loop basics
  • Event loops in IPython and matplotlib
  • Event handling in matplotlib
  • Events for mouse, keyboard, axes, figures, and picking
  • Compound events

The material presented here will be pulled from matplotlib's artist and backend layers, and it should provide a nice starting point for the vast world of user interaction in data visualization.

To follow along with this chapter's code, clone the notebook's repository and start up IPython by using the following code:

$ git clone https://github.com/masteringmatplotlib/interaction.git
$ cd interaction
$ make

Since we will be using the IPython Notebook backend—which is also known as nbagg—for interactive plots, we will not enter the usual %matplotlib inline command:

In [1]: import matplotlib
        matplotlib.use('nbagg')

We'll use the regular imports as well as a few other imports in the following examples:

In [2]: import random
        import sys
        import time
        import numpy as np
        import matplotlib as mpl
        import matplotlib.pyplot as plt
        import seaborn as sns
        from IPython.display import Image
        from typecheck import typecheck

        sys.path.append("../lib")
        import topo

Event loops in matplotlib

In the chapter on matplotlib architecture, we mentioned the event loops that the GUI toolkits use and which are integrated with matplotlib. As you dig through the matplotlib libraries, you will eventually come across several mentions with regard to the event loops that are not from the GUI toolkits. So that we can better understand the difference between these and, more importantly, the event handling and user interaction with plots, we're going to spend some time learning more about these event loops in matplotlib.

Event-based systems

In general, event loops are part of a recurring set of patterns that are used together in various computing frameworks. Often, they comprise of the following:

  • An incoming event
  • A mechanism that is used to respond to an event
  • A looping construct (for example, the while loop, listener, and the message dispatch mechanism)

Event-based systems are typically asynchronous in nature, allowing events to be sent to the system and then responded to in any order without the slower operations preventing the faster ones from being executed sooner. It is this characteristic that makes the event-based systems such a compelling option for software architects and developers. When used properly, asynchronous programming can lead to significant improvements in the perceived performance of an application.

Event-based systems take many forms. Some common examples for the same include the following:

  • Asynchronous networking libraries
  • Toolkits that are used to build graphical user interfaces
  • Enterprise messaging systems
  • Game engines

It is a combination of the first two examples with which we will occupy ourselves in this chapter. Before we tackle this though, let's take a look at the basic workings of event-based systems, and in particular, the event loop.

The event loop

So, what did we mean previously when we said a looping construct? Well, the simplest loop that you can create in Python is the following:

while True:
    pass

This will just run forever until you press the Ctrl + C keys or the power goes out. However, this particular loop does nothing useful. Here's another loop that will run until its event fires a change of a value from True to False:

In [4]: x = True
        while x:
            time.sleep(1)
            if random.random() < 0.1:
                x = False

So, what relation do these simple loops have with the loops that power toolkits, such as GTK and Qt, or frameworks, such as Twisted and Tornado, possess? Usually, the event systems have the following:

  • A way to start the event loop
  • A way to stop the event loop
  • Means to register events
  • Means to respond to events

During each run, a loop will usually check a data structure to see whether there are any new events that occurred since the last time it looped. In a network event system, each loop will check to see whether any file descriptors are ready to read or write. In a GUI toolkit, each loop will check to see whether any clicks or button presses occurred.

Given the preceding simple criteria, let's explore another slightly more sophisticated and minimally demonstrative event loop. To keep this small, we are not going to integrate with socket or GUI events. The event that our loop will respond to will be quite minimal indeed—a keyboard interrupt:

In [5]: class EventLoop:
            def __init__(self):
                self.command = None
                self.status = None
                self.handlers = {"interrupt": self.handle_interrupt}
                self.resolution = 0.1

            def loop(self):
                self.command = "loop"
                while self.command != "stop":
                    self.status = "running"
                    time.sleep(self.resolution)

            def start(self):
                self.command = "run"
                try:
                    self.loop()
                except KeyboardInterrupt:
                    self.handle_event("interrupt")

            def stop(self):
                self.command = "stop"

            @typecheck
            def add_handler(self, fn: callable, event: str):
                self.handlers[event] = fn

            @typecheck
            def handle_event(self, event: str):
                self.handlers[event]()

            def handle_interrupt(self):
                print("Stopping event loop ...")
                self.stop()

Here's what we did:

  • We created a class that maintains a data structure for event handlers
  • We added a default handler for the interrupt event
  • We also created a loop method
  • We created methods to start and stop the loop via an attribute change
  • In the start method, we checked for an interrupt signal and fired off an interrupt handler for the signal
  • We created a method to add event handlers to the handler data structure (should we want to add more)

Now, you can run this loop in the notebook by using the following code:

In [*]: el = EventLoop()
        el.start()

When you evaluate this cell, IPython will display the usual indicator that a cell is running, in other words, the In [*] cell. When you're satisfied that the loop is merrily looping, go to the IPython Notebook menu and navigate to Kernel | Interrupt. The cell with a loop in it will finish, and the asterisk will be replaced by the input number. The interrupt handler will be printed out as a status message as well:

In [6]: el = EventLoop()
        el.start()
Stopping event loop ...

Though this event loop is fairly different from the power networking libraries or GUI toolkits, it's very close (both in nature as well as the code) to the default event loops matplotlib provides for its canvas objects. As such, this is a perfect starting place if you want to have a deeper understanding of matplotlib. To continue in this vein, reading the matplotlib backend source code will serve you well.

The preceding practical background information should be enough for you to more fully appreciate the following few sections.

GUI toolkit main loops

How are the main loops in the GUI toolkits different from the preceding ones? Well, for one, the GTK and Qt event systems aren't written in Python. The GTK main loop is written in C, and it is a part of one of the libraries underlying GTK—GLib. More importantly, the GUI toolkits are designed to respond to the user input from many types of devices, and sometimes, at a very fine-grained level. The loop demonstrated previously is a simple, generic loop that allows one to define any sort of event, which can also be connected to a callback.

However, even with all the differences, they can still be mapped to each other. The GLib event loop documentation provides the following diagram:

GUI toolkit main loops

In the simple EventLoop class, setting the initial value of the handlers attribute is similar to what GLib can do in a prepare() or query() call. Our version of the check() call is really just a part of the Python control flow; we took the easy route with a try/except method wrapped around the loop call. The only way the class can monitor the outside world for events is through a KeyboardInterrupt exception. Our dispatch() is the call to the registered handler for the interrupt event. Hence, despite the simplicity of our example, one can see how it relates to the fully featured, real-world event systems.

An in-depth exploration of the GUI main loops is well beyond the scope of this book, but before we leave the topic entirely behind, let's look at what's running under IPython's bonnet.

IPython Notebook event loops

IPython originally started as an advanced read-eval-print loop (REPL) for Python. After about a decade of becoming a powerful tool for Python scientific computing, using NumPy and matplotlib, and providing parallel computing features, IPython added support for HTML notebooks. Since then, the popularity of IPython Notebook and the user-created content that runs on it has exploded. The matplotlib development community has responded with a matplotlib backend that enables interaction in the browser, which is similar to what users might expect when running a GTK or Qt backend.

The IPython Notebook requires two main components—an HTTP server and a web client, such as your browser. The functionality of a websocket-enabled HTTP server is provided by Tornado, a Python web framework, and an asynchronous networking library. Your browser provides the infrastructure necessary to support the websocket communications with the web server. Through ZeroMQ, IPython provides the means to send the data it receives over websockets to the rest of the components in the IPython architecture.

Some of these components have their own event loops. They include the following:

  • The browser's event loop
  • Tornado's HTTP Server event loop
  • IPython's PyZMQ event loops (for instance, pollers)

However, what this picture is missing is how this relates to matplotlib. In order for a web-based matplotlib backend to work, it needs the following:

  • A means to send data and receive results for display
  • A figure manager that establishes two-way communications with IPython
  • A figure canvas with its own event loop
  • A timer class to get events to the Tornado ioloop in IPython Notebook app
  • An event loop for each canvas

matplotlib event loops

The last bullet item may cause confusion. So, it is important to make this clear—the event loops that matplotlib uses for its canvases are completely different and quite separate from the GUI event loops. In fact, the default figure canvas event loop is very similar to the loop method in the EventLoop class from the previous section.

The GUI and matplotlib event loops have different code. Yet, they are still connected. In the case of an IPython Notebook app, the web browser's JavaScript events and websockets ultimately communicate with the IPython kernel, which in turn communicates with the instances of matplotlib figure managers, figures, and canvases.

Understanding how these pieces work together and communicate is the key to mastering the matplotlib events, both for the creation of more advanced plots, as well as the inevitable debugging that they will need. The best resource for a deeper understanding of these events is the matplotlib source code. Spend time getting to know how figures and canvases respond to events, messages get sent from one class to another, how matplotlib generalizes the event experience across the GUI toolkits, and how this affects the toolkit of your choice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset