Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ec4230a

Browse files
committed
DOC/WIP: more edits and content
1 parent 09deda3 commit ec4230a

File tree

1 file changed

+135
-67
lines changed

1 file changed

+135
-67
lines changed

doc/users/interactive_guide.rst

Lines changed: 135 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.. _plotting-guide-interactive:
1+
.. _interactive_figures_and_eventloops:
22

33
.. currentmodule:: matplotlib
44

@@ -7,17 +7,22 @@
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+
2126
Fundamentally, all user interaction (and networking) is implemented as
2227
an infinite loop waiting for events from the user (via the OS) and
2328
then doing something about it. For example, a minimal Read Evaluate
@@ -33,26 +38,29 @@ Print Loop (REPL) is ::
3338

3439
This is missing many niceties (for example, it exits on the first
3540
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.
4045

4146
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
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
5158
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+
5664

5765
references 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

7175
Command 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

8698
The 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
90102
processed by python), but the figure windows will be responsive. Once
91103
the event loop is stopped (leaving any still open figure windows
92104
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.
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
98110
of 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

129143
If you have open windows (either due to a `plt.pause` timing out or
130144
from calling `figure.Figure.show`) that have pending UI events (mouse
131145
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+
137197

138198
.. autosummary::
139199
:template: autosummary.rst
140200
:nosignatures:
141201

142202
backend_bases.FigureCanvasBase.flush_events
203+
backend_bases.FigureCanvasBase.draw_idle
204+
143205

144206
Interactive
145207
-----------
146208

147209
While running the GUI event loop in a blocking mode or explicitly
148210
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.
157228

158229
This time-share technique only allows the event loop to run while
159230
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
174245
on how to do this.
175246

176247

248+
.. _interactive_scripts :
249+
177250
Scripts
178251
=======
179252

@@ -202,7 +275,7 @@ visualization and less on your computation.
202275
The second case is very much like :ref:`Blocking` above. By using ``plt.show(block=True)`` or
203276

204277
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
206279
data 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

Comments
 (0)