1- .. _ plotting-guide-interactive :
1+ .. _ interactive_figures_and_eventloops :
22
33.. currentmodule :: matplotlib
44
77 Interactive Figures and Asynchronous Programming
88==================================================
99
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
1921<http://www.amazon.com/Interactive-Applications-using-Matplotlib-Benjamin/dp/1783988843> `__.
2022
23+ Event Loops
24+ ===========
25+
2126Fundamentally, all user interaction (and networking) is implemented as
2227an infinite loop waiting for events from the user (via the OS) and
2328then doing something about it. For example, a minimal Read Evaluate
@@ -33,26 +38,29 @@ Print Loop (REPL) is ::
3338
3439This is missing many niceties (for example, it exits on the first
3540exception!), 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.
4045
4146In 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
4855:ref: `event handling system <event-handling-tutorial >`.
4956
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
5158capturing 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+
5664
5765references to trackdown:
5866 - link to cpython REPL loop (pythonrun.c::PyRunInteractiveLoopFlags)
@@ -62,26 +70,30 @@ references to trackdown:
6270 - Beazly talk or two on asyncio
6371
6472
65-
66- Interactive Mode
67- ================
68-
6973.. _cp_integration :
7074
7175Command Prompt Integration
7276==========================
7377
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
7889
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.
8193
8294
83- Blocking
84- --------
95+ Blocking the Prompt
96+ -------------------
8597
8698The simplest "integration" is to start the GUI event loop in
8799'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
90102processed by python), but the figure windows will be responsive. Once
91103the event loop is stopped (leaving any still open figure windows
92104non-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.
94106
95107
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
97109`pyplot.show(block=True) `. To start the event loop for a fixed amount
98110of time use `pyplot.pause `.
99111
@@ -104,11 +116,13 @@ Without using ``pyplot`` you can start and stop the event loops via
104116.. warning
105117
106118 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.
110122
111123
124+ This technique can be very useful if you want to write a script that
125+ pauses for user interaction, see :ref: `interactive_script `.
112126
113127.. autosummary ::
114128 :template: autosummary.rst
@@ -123,37 +137,94 @@ Without using ``pyplot`` you can start and stop the event loops via
123137 figure.Figure.show
124138
125139
126- Explicitly
127- ----------
140+ Explicitly running the Event Loop
141+ ---------------------------------
128142
129143If you have open windows (either due to a `plt.pause ` timing out or
130144from calling `figure.Figure.show `) that have pending UI events (mouse
131145clicks, 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+
137197
138198.. autosummary ::
139199 :template: autosummary.rst
140200 :nosignatures:
141201
142202 backend_bases.FigureCanvasBase.flush_events
203+ backend_bases.FigureCanvasBase.draw_idle
204+
143205
144206Interactive
145207-----------
146208
147209While running the GUI event loop in a blocking mode or explicitly
148210handling 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.
157228
158229This time-share technique only allows the event loop to run while
159230python 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
174245on how to do this.
175246
176247
248+ .. _interactive_scripts :
249+
177250Scripts
178251=======
179252
@@ -202,7 +275,7 @@ visualization and less on your computation.
202275The second case is very much like :ref: `Blocking ` above. By using ``plt.show(block=True) `` or
203276
204277The 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
206279data I/O.
207280
208281.. _stale_artists :
@@ -322,17 +395,12 @@ method. The source for the prompt_toolkit input hooks lives at
322395 then the loop would look something like ::
323396
324397 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
329401
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.
334402
335- .. [#f3 ] These examples are agressively dropping many of the
403+ .. [#f2 ] These examples are agressively dropping many of the
336404 complexities that must be dealt with in the real world such as
337405 keyboard interupts [link], timeouts, bad input, resource
338406 allocation and cleanup, etc.
0 commit comments