1
- .. _ plotting-guide-interactive :
1
+ .. _ interactive_figures_and_eventloops :
2
2
3
3
.. currentmodule :: matplotlib
4
4
7
7
Interactive Figures and Asynchronous Programming
8
8
==================================================
9
9
10
- Matplotlib supports rich interactive figures. At the most basic
11
- matplotlib has the ability to zoom and pan a figure to inspect your
12
- data 'baked in', but this is backed by a full mouse and keyboard event
13
- handling system to enable users to build sophisticated interactive
14
- graphs.
15
-
16
- This is meant to be a rapid introduction to the relevant details of
17
- integrating the matplotlib with a GUI event loop. For further details
18
- see `Interactive Applications using Matplotlib
10
+ Matplotlib supports rich interactive figures by embedding figures into
11
+ a GUI window. The basic interactions of panning and zooming in as
12
+ Axis to inspect your data is 'baked in' to Matplotlib. This is
13
+ supported by a full mouse and keyboard event handling system to enable
14
+ you to build sophisticated interactive graphs.
15
+
16
+ This is meant to be an introduction to the low-level details of how
17
+ integrating the Matplotlib with a GUI event loop works. For a more
18
+ practical introduction the Matplotlib event API see `Interactive
19
+ Tutorial <https://github.com/matplotlib/interactive_tutorial> `__ and
20
+ `Interactive Applications using Matplotlib
19
21
<http://www.amazon.com/Interactive-Applications-using-Matplotlib-Benjamin/dp/1783988843> `__.
20
22
23
+ Event Loops
24
+ ===========
25
+
21
26
Fundamentally, all user interaction (and networking) is implemented as
22
27
an infinite loop waiting for events from the user (via the OS) and
23
28
then doing something about it. For example, a minimal Read Evaluate
@@ -33,26 +38,29 @@ Print Loop (REPL) is ::
33
38
34
39
This is missing many niceties (for example, it exits on the first
35
40
exception!), but is representative of the event loops that underlie
36
- all terminals, GUIs, and servers [#f1 ]_. In general the *Read * step is
37
- waiting on some sort of I/O, be it user input from a keyboard or mouse
38
- or the network while the *Evaluate * and *Print * are responsible for
39
- interpreting the input and then **doing ** something about it.
41
+ all terminals, GUIs, and servers [#f1 ]_. In general the *Read * step
42
+ is waiting on some sort of I/O -- be it user input or the network --
43
+ while the *Evaluate * and *Print * are responsible for interpreting the
44
+ input and then **doing ** something about it.
40
45
41
46
In practice users do not work directly with these loops and instead
42
- use a framework that provides a mechanism to register callbacks [#2 ]_.
43
- This allows users to write reactive, event-driven, programs without
44
- having to delve into the nity-grity [#f3 ]_ details of I/O. Examples
45
- include ``observe `` and friends in `traitlets `, the ``Signal `` /
46
- ``Slot `` framework in Qt (and the analogs in other GUI frameworks),
47
- the 'request' functions in web frameworks, and Matplotlib's native
47
+ use a framework that provides a mechanism to register callbacks to be
48
+ called in response to specific event. For example "when the user
49
+ clicks on this button, please run this function" or "when the user
50
+ hits the 'z' key, please run this other function". This allows users
51
+ to write reactive, event-driven, programs without having to delve into
52
+ the nity-gritty [#f2 ]_ details of I/O. Examples of this pattern the
53
+ ``Signal `` / ``Slot `` framework in Qt (and the analogs in other GUI
54
+ frameworks), the 'request' functions in flask, and Matplotlib's
48
55
:ref: `event handling system <event-handling-tutorial >`.
49
56
50
- All GUI frameworks (Qt, Wx, Gtk, tk, QSX , or web) have some method of
57
+ All GUI frameworks (Qt, Wx, Gtk, tk, OSX , or web) have some method of
51
58
capturing user interactions and passing them back to the application,
52
- although the exact details vary between frameworks. Matplotlib has a
53
- 'backend' (see :ref: `what-is-a-backend `) for each GUI framework which
54
- converts the native UI events into Matplotlib events. This allows
55
- Matplotlib user to write GUI-independent interactive figures.
59
+ although the exact details vary. Matplotlib has a :ref: `backnd
60
+ <what-is-a-backend>` for each GUI framework which use these mechanisms
61
+ to convert the native UI events into Matplotlib events so we can
62
+ develop framework-independent interactive figures.
63
+
56
64
57
65
references to trackdown:
58
66
- link to cpython REPL loop (pythonrun.c::PyRunInteractiveLoopFlags)
@@ -62,26 +70,30 @@ references to trackdown:
62
70
- Beazly talk or two on asyncio
63
71
64
72
65
-
66
- Interactive Mode
67
- ================
68
-
69
73
.. _cp_integration :
70
74
71
75
Command Prompt Integration
72
76
==========================
73
77
74
- Integrating a GUI window and a CLI introduces a conflict: there are
75
- two infinite loops that want to be waiting for user input in the same
76
- process. In order for both the prompt and the GUI widows to be responsive
77
- we need a method to allow the loops to 'timeshare'.
78
+ Integrating a GUI window into a CLI introduces a conflict: there are
79
+ two infinite loops that want to be waiting for user input on the main
80
+ thread in the same process. Python wants to be blocking the main
81
+ thread it it's REPL loop and the GUI framework wants to be running
82
+ it's event loop. In order for both the prompt and the GUI widows to
83
+ be responsive we need a method to allow the loops to 'timeshare' :
84
+
85
+ 1. let the GUI main loop block the python process when you want
86
+ interactive windows
87
+ 2. let the CLI main loop block the python process and intermittently
88
+ run the GUI loop
78
89
79
- 1. let the GUI main loop block the python process
80
- 2. intermittently run the GUI loop
90
+ The inverse problem, embedding an interactive prompt into a GUI, is
91
+ also a way out of this problem by letting the GUI event loop always
92
+ run the show, but that is essentially writing a full application.
81
93
82
94
83
- Blocking
84
- --------
95
+ Blocking the Prompt
96
+ -------------------
85
97
86
98
The simplest "integration" is to start the GUI event loop in
87
99
'blocking' mode and take over the CLI. While the GUI event loop is
@@ -90,10 +102,10 @@ may show the charters entered into stdin, but they will not be
90
102
processed by python), but the figure windows will be responsive. Once
91
103
the event loop is stopped (leaving any still open figure windows
92
104
non-responsive) you will be able to use the prompt again. Re-starting
93
- the event will make any open figure responsive again.
105
+ the event loop will make any open figure responsive again.
94
106
95
107
96
- To start the event loop until all open figures are closed us
108
+ To start the event loop until all open figures are closed use
97
109
`pyplot.show(block=True) `. To start the event loop for a fixed amount
98
110
of time use `pyplot.pause `.
99
111
@@ -104,11 +116,13 @@ Without using ``pyplot`` you can start and stop the event loops via
104
116
.. warning
105
117
106
118
By using `Figure.show` it is possible to display a figure on the
107
- screen with out starting the event loop and not being in
108
- interactive mode. This will likely result in a non-responsive
109
- figure and may not even display the rendered plot.
119
+ screen without explicitly starting the event loop and not being in
120
+ interactive mode. This may work but will likely result in a
121
+ non-responsive figure and may not even display the rendered plot.
110
122
111
123
124
+ This technique can be very useful if you want to write a script that
125
+ pauses for user interaction, see :ref: `interactive_script `.
112
126
113
127
.. autosummary ::
114
128
:template: autosummary.rst
@@ -123,37 +137,94 @@ Without using ``pyplot`` you can start and stop the event loops via
123
137
figure.Figure.show
124
138
125
139
126
- Explicitly
127
- ----------
140
+ Explicitly running the Event Loop
141
+ ---------------------------------
128
142
129
143
If you have open windows (either due to a `plt.pause ` timing out or
130
144
from calling `figure.Figure.show `) that have pending UI events (mouse
131
145
clicks, button presses, or draws) you can explicitly process them by
132
- calling ``fig.canvas.flush_events() ``. This will not run the GUI
133
- event loop, but instead synchronously processes all UI events current
134
- waiting to be processed. The exact behavior is backend-dependent but
135
- typically events on all figure are processed and only events waiting
136
- to be processed (not those added during processing) will be handled.
146
+ calling ``fig.canvas.flush_events() ``. This will run the GUI event
147
+ loop, until all UI events currently waiting have been processed. The
148
+ exact behavior is backend-dependent but typically events on all figure
149
+ are processed and only events waiting to be processed (not those added
150
+ during processing) will be handled.
151
+
152
+ For example ::
153
+
154
+ import time
155
+ import matplotlib.pyplot as plt
156
+ import numpy as np
157
+ plt.ion()
158
+
159
+ fig, ax = plt.subplots()
160
+ fig.canvas.show()
161
+ th = np.linspace(0, 2*np.pi, 512)
162
+ ax.set_ylim(-1.5, 1.5)
163
+
164
+ ln, = ax.plot(th, np.sin(th))
165
+
166
+ def slow_loop(N, ln):
167
+ for j in range(N):
168
+ time.sleep(.1) # to simulate some work
169
+ ln.figure.canvas.flush_events()
170
+
171
+ slow_loop(100, ln)
172
+
173
+ Will be a bit laggy, as we are only processing user input every 100ms
174
+ (where as 20-30ms is what feels "responsive"), but it will respond.
175
+
176
+
177
+ If you make changes to the plot and want it re-rendered you will need
178
+ to call `~.FigureCanvasBase.draw_idle() ` to request that the canvas be
179
+ re-drawn. This method can be thought of *draw_soon * in analogy to
180
+ `asyncio.BaseEventLoop.call_soon `.
181
+
182
+ We can add this our example above as ::
183
+
184
+ def slow_loop(N, ln):
185
+ for j in range(N):
186
+ time.sleep(.1) # to simulate some work
187
+ if j % 10:
188
+ ln.set_ydata(np.sin(((j // 10) % 5 * th)))
189
+ ln.figure.canvas.draw_idle()
190
+
191
+ ln.figure.canvas.flush_events()
192
+
193
+ slow_loop(100, ln)
194
+
195
+
196
+
137
197
138
198
.. autosummary ::
139
199
:template: autosummary.rst
140
200
:nosignatures:
141
201
142
202
backend_bases.FigureCanvasBase.flush_events
203
+ backend_bases.FigureCanvasBase.draw_idle
204
+
143
205
144
206
Interactive
145
207
-----------
146
208
147
209
While running the GUI event loop in a blocking mode or explicitly
148
210
handling UI events is useful, we can do better! We really want to be
149
- able to have a usable prompt **and ** interactive figure windows. We
150
- can do this using the concept of 'input hook' (see :ref: `below
151
- <Eventloop integration mechanism>` for implementation details) that
152
- allows the GUI event loop to run and process events while the prompt
153
- is waiting for the user to type (even for an extremely fast typist, a
154
- vast majority of the time the prompt is simple idle waiting for human
155
- finders to move). This effectively gives us a simultaneously GUI
156
- windows and prompt.
211
+ able to have a usable prompt **and ** interactive figure windows.
212
+
213
+ We can do this using the 'input hook' feature of the interactive
214
+ prompt. This hook is called by the prompt as it waits for the user
215
+ type (even for a fast typist the prompt is mostly waiting for the
216
+ human to think and move their fingers). Although the details vary
217
+ between prompts the logic is roughly
218
+
219
+ 1. start to wait for keyboard input
220
+ 2. start the GUI event loop
221
+ 3. as soon as the user hits a key, exit the GUI event loop and handle the key
222
+ 4. repeat
223
+
224
+ This gives us the illusion of simultaneously having an interactive GUI
225
+ windows and an interactive prompt. Most of the time the GUI event
226
+ loop is running, but as soon as the user starts typing the prompt
227
+ takes over again.
157
228
158
229
This time-share technique only allows the event loop to run while
159
230
python is otherwise idle and waiting for user input. If you want the
@@ -174,6 +245,8 @@ windows are implemented!). See :ref:`user_interfaces` for more details
174
245
on how to do this.
175
246
176
247
248
+ .. _interactive_scripts :
249
+
177
250
Scripts
178
251
=======
179
252
@@ -202,7 +275,7 @@ visualization and less on your computation.
202
275
The second case is very much like :ref: `Blocking ` above. By using ``plt.show(block=True) `` or
203
276
204
277
The third case you will have to integrate updating the ``Aritist ``
205
- instances, calling ``draw_idle ``, and the GUI event loop with your
278
+ instances, calling ``draw_idle ``, and flushing the GUI event loop with your
206
279
data I/O.
207
280
208
281
.. _stale_artists :
@@ -322,17 +395,12 @@ method. The source for the prompt_toolkit input hooks lives at
322
395
then the loop would look something like ::
323
396
324
397
fds = [...]
325
- while True: # Loop
326
- inp = select(fds) # Read
327
- eval(inp) # Evaluate / Print
328
-
398
+ while True: # Loop
399
+ inp = select(fds).read() # Read
400
+ eval(inp) # Evaluate / Print
329
401
330
- .. [#f2 ] asyncio has a fundamentally different paradigm that uses
331
- coroutines instead of callbacks as the user-facing interface,
332
- however at the core there is a select loop like the above
333
- footnote the multiplexes between the running tasks.
334
402
335
- .. [#f3 ] These examples are agressively dropping many of the
403
+ .. [#f2 ] These examples are agressively dropping many of the
336
404
complexities that must be dealt with in the real world such as
337
405
keyboard interupts [link], timeouts, bad input, resource
338
406
allocation and cleanup, etc.
0 commit comments