-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
DOC: Start to document interactive figures #4779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
cfa717a
206f99c
fa36c95
8b28c01
7189eed
98ae26c
6263284
09deda3
ec4230a
d1a7a3e
b49973a
bb8058a
bc4ecd5
49f49fa
295d71d
c3c5013
d9df057
7b0b327
5a426d6
8ab0a65
d70d5ad
757e040
b0690ff
cc3ac5b
afe9edd
e09cab9
04f2fc6
9d842bf
41e19a8
eda2ed3
7cf68e8
bfa3589
6edcb47
9b492bd
6d4747f
f05ea3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,27 @@ | ||
.. _plotting-guide-interactive: | ||
|
||
************************************************ | ||
Interactive Figures and Asynchronous Programming | ||
************************************************ | ||
|
||
One of the most powerful uses of matplotlib is interactive | ||
figures. At the most basic matplotlib has the ability to zoom and pan | ||
a figure to inspect your data, however there is also a full mouse and | ||
keyboard event handling system to enable building sophisticated interactive | ||
.. currentmodule:: matplotlib | ||
|
||
|
||
================================================== | ||
Interactive Figures and Asynchronous Programming | ||
================================================== | ||
|
||
Matplotlib supports rich interactive figures. At the most basic | ||
matplotlib has the ability to zoom and pan a figure to inspect your | ||
data 'baked in', but this is backed by a full mouse and keyboard event | ||
handling system to enable users to build sophisticated interactive | ||
graphs. | ||
|
||
This page is meant to be a rapid introduction to the relevant details | ||
of integrating the matplotlib with a GUI event loop. For further | ||
details see `Interactive Applications using Matplotlib | ||
This is meant to be a rapid introduction to the relevant details of | ||
integrating the matplotlib with a GUI event loop. For further details | ||
see `Interactive Applications using Matplotlib | ||
<http://www.amazon.com/Interactive-Applications-using-Matplotlib-Benjamin/dp/1783988843>`__. | ||
|
||
Fundamentally, all user interaction (and networking) is implemented as | ||
an infinite loop waiting for events from the OS and then doing | ||
something about it. For example, a minimal Read Evaluate Print Loop | ||
(REPL) is :: | ||
an infinite loop waiting for events from the user (via the OS) and | ||
then doing something about it. For example, a minimal Read Evaluate | ||
Print Loop (REPL) is :: | ||
|
||
exec_count = 0 | ||
while True: | ||
|
@@ -33,108 +36,167 @@ exception!), but is representative of the event loops that underlie | |
all terminals, GUIs, and servers [#f1]_. In general the *Read* step is | ||
waiting on some sort of I/O, be it user input from a keyboard or mouse | ||
or the network while the *Evaluate* and *Print* are responsible for | ||
interpreting the input and then doing something about it. | ||
|
||
In practice most users do not work directly with these loops, and | ||
instead framework that provides a mechanism to register callbacks | ||
[#2]_. This allows users to write reactive, event-driven, programs | ||
without having to delve into the nity-grity [#f3]_ details of I/O. | ||
Examples include ``observe`` and friends in `traitlets`, the | ||
``Signal`` / ``Slot`` framework in Qt and the analogs in Gtk / Tk / | ||
Wx, the request functions in web frameworks, or Matplotlib's | ||
native :ref:`event handling system <event-handling-tutorial>`. | ||
|
||
|
||
interpreting the input and then **doing** something about it. | ||
|
||
In practice users do not work directly with these loops and instead | ||
use a framework that provides a mechanism to register callbacks [#2]_. | ||
This allows users to write reactive, event-driven, programs without | ||
having to delve into the nity-grity [#f3]_ details of I/O. Examples | ||
include ``observe`` and friends in `traitlets`, the ``Signal`` / | ||
``Slot`` framework in Qt (and the analogs in other GUI frameworks), | ||
the 'request' functions in web frameworks, and Matplotlib's native | ||
:ref:`event handling system <event-handling-tutorial>`. | ||
|
||
All GUI frameworks (Qt, Wx, Gtk, tk, QSX, or web) have some method of | ||
capturing user interactions and passing them back to the application, | ||
although the exact details vary between frameworks. Matplotlib has a | ||
'backend' (see :ref:`what-is-a-backend`) for each GUI framework which | ||
converts the native UI events into Matplotlib events. This allows | ||
Matplotlib user to write GUI-independent interactive figures. | ||
|
||
references to trackdown: | ||
- link to cpython REPL loop | ||
- link to cpython REPL loop (pythonrun.c::PyRunInteractiveLoopFlags) | ||
- link to IPython repl loop (Ipython.terminal.interactiveshell.py :: TerminalInteractiveShell.mainloop | ||
and Ipython.terminal.interactiveshell.py :: TerminalInteractiveShell.interact) | ||
- curio / trio / asycio / twisted / tornado event loops | ||
- Beazly talk or two on asyncio | ||
|
||
Command Prompt Integration | ||
========================== | ||
|
||
GUI to Matplotlib Bridge | ||
------------------------ | ||
Integrating a GUI window and a CLI introduces a conflict: there are | ||
two infinite loops that want to be waiting for user input in the same | ||
process. In order for both the prompt and the GUI widows to be responsive | ||
we need a method to allow the loops to 'timeshare'. | ||
|
||
When using | ||
1. let the GUI main loop block the python process | ||
2. intermittently run the GUI loop | ||
|
||
|
||
Command Prompt | ||
-------------- | ||
Blocking | ||
-------- | ||
|
||
To handle asynchronous user input every GUI framework has an event | ||
loop. At the most basic this is a stack that can have events to be | ||
processed. In order for the GUI to be responsive this loop must be | ||
run. To manage this in python there are two basic methods: | ||
The simplest "integration" is to start the GUI event loop in | ||
'blocking' mode and take over the CLI. While the GUI event loop is | ||
running you can not enter new commands into the prompt (your terminal | ||
may show the charters entered into stdin, but they will not be | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. characters |
||
processed by python), but the figure windows will be responsive. Once | ||
the event loop is stopped (leaving any still open figure windows | ||
non-responsive) you will be able to use the prompt again. Re-starting | ||
the event will make any open figure responsive again. | ||
|
||
1. let the GUI main loop block the python process | ||
2. intermittently run the GUI loop for a period of time | ||
|
||
To start the event loop until all open figures are closed us | ||
`pyplot.show(block=True)`. To start the event loop for a fixed amount | ||
of time use `pyplot.pause`. | ||
|
||
Without using ``pyplot`` you can start and stop the event loops via | ||
``fig.canvas.start_event_loop`` and ``fig.canvas.stop_event_loop``. | ||
|
||
|
||
.. warning | ||
|
||
By using `Figure.show` it is possible to display a figure on the | ||
screen with out starting the event loop and not being in | ||
interactive mode. This will likely result in a non-responsive | ||
figure and may not even display the rendered plot. | ||
|
||
|
||
|
||
.. autosummary:: | ||
:template: autosummary.rst | ||
:nosignatures: | ||
|
||
pyplot.show | ||
pyplot.pause | ||
|
||
backend_bases.FigureCanvasBase.start_event_loop | ||
backend_bases.FigureCanvasBase.stop_event_loop | ||
|
||
figure.Figure.show | ||
|
||
Blocking | ||
******** | ||
|
||
Explicitly | ||
---------- | ||
|
||
If you have open windows (either due to a `plt.pause` timing out or | ||
from calling `figure.Figure.show`) that have pending UI events (mouse | ||
clicks, button presses, or draws) you can explicitly process them by | ||
calling ``fig.canvas.flush_events()``. This will not run the GUI | ||
event loop, but instead synchronously processes all UI events current | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. currently waiting |
||
waiting to be processed. The exact behavior is backend-dependent but | ||
typically events on all figure are processed and only events waiting | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all figures There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I understood this fully (but it may be me). I understood this as most backends have a two step process: 1. add events to some internal matplotlib queue for processing 2. Events waiting to be processed are then handled. Is this correct? |
||
to be processed (not those added during processing) will be handled. | ||
|
||
.. autosummary:: | ||
:template: autosummary.rst | ||
:nosignatures: | ||
|
||
backend_bases.FigureCanvasBase.flush_events | ||
|
||
Interactive | ||
*********** | ||
----------- | ||
|
||
While running the GUI event loop in a blocking mode or explicitly | ||
handling UI events is useful, we can do better! We really want to be | ||
able to have a usable prompt **and** interactive figure windows. We | ||
can do this using the concept of 'input hook' (see :ref:`below | ||
<Eventloop integration mechanism>` for implementation details) that | ||
allows the GUI event loop to run and process events while the prompt | ||
is waiting for the user to type (even for an extremely fast typist, a | ||
vast majority of the time the prompt is simple idle waiting for human | ||
finders to move). This effectively gives us a simultaneously GUI | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fingers |
||
windows and prompt. | ||
|
||
This time-share technique only allows the event loop to run while | ||
python is otherwise idle and waiting for user input. If you want the | ||
GUI to be responsive during long running code it is necessary to | ||
periodically flush the GUI event queue as described :ref:`above | ||
<Explicitly>`. In this case it is your code, not the REPL, which is | ||
blocking process so you need to handle the time-share manually. | ||
Conversely, a very slow figure draw will block the prompt until it | ||
finishes. | ||
|
||
Full embedding | ||
============== | ||
|
||
It is also possible to go the other direction and fully embed figures | ||
it a rich native application. Matplotlib provides classes which can | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "in a rich" |
||
be directly embedded in GUI applications (this is how the built-in | ||
windows are implemented!). See :ref:`user_interfaces` for more details | ||
on how to do this. | ||
|
||
|
||
Scripts | ||
------- | ||
======= | ||
|
||
- if you want always reactive figures while the script runs, you have to | ||
call `flush_event` | ||
- if you want to have reactive figures that block the script until they are closed (ex for | ||
collecting user input before continuing use | ||
There are several use-cases for using interactive figures in scripts: | ||
|
||
- progress updates as a long running script progresses | ||
- capture user input to steer the script | ||
- streaming updates from a data source | ||
|
||
Full embedding | ||
-------------- | ||
In the first case, it is the same as :ref:`above | ||
<Explicitly>` where you explicitly call :: | ||
|
||
- just let the underlying GUI event loop handle eve | ||
fig.canvas.flush_events() | ||
|
||
Web | ||
--- | ||
periodically to allow the event loop to process UI and draw events and | ||
:: | ||
|
||
The Weeds | ||
========= | ||
fig.canvas.draw_idle() | ||
|
||
when you have updated the contents of the figure. | ||
|
||
The GUI event loop | ||
------------------ | ||
The more frequently you call ``flush_events`` the more responsive your | ||
figure will feel but at the cost of spending more resource on the | ||
visualization and less on your computation. | ||
|
||
The second case is very much like :ref:`Blocking` above. By using ``plt.show(block=True)`` or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. incomplete sentence (I figure you're aware but still flagging it) |
||
|
||
The python capi provides a hook, `PyOS_InputHook`, to register a | ||
function to be run "The function will be called when Python's | ||
interpreter prompt is about to become idle and wait for user input | ||
from the terminal.". This hook can be used to integrate a second | ||
event loop (the GUI event loop) with the python input prompt loop. | ||
Such hooks are usually included with the python bindings for GUI | ||
toolkits and may be registered on import. IPython also includes hooks | ||
for all of the GUI frameworks supported by matplotlib. The hook | ||
functions typically exhaust all pending events on the GUI event queue, | ||
run the main loop for a short fixed amount of time, or run the event | ||
loop until a key is pressed on stdin. | ||
|
||
matplotlib does not currently do any management of `PyOS_InputHook` | ||
due to the wide range of ways that matplotlib is used. This | ||
management is left to the code using matplotlib. Interactive figures, | ||
even with matplotlib in 'interactive mode', may not work in the | ||
vanilla python repl if an appropriate `PyOS_InputHook` is not | ||
registered. We suggest using ``IPython``, which in addition to | ||
improving the command line, ensures that such a `PyOS_InputHook` | ||
function is registered for you GUI backend of choice. | ||
|
||
A drawback of relying on `PyOS_InputHook` is that the GUI event loop | ||
is only processing events while python is otherwise idle and waiting | ||
for user input. If you want the GUI to be responsive during long | ||
running code it is necessary to periodically flush the GUI event | ||
queue. To achieve this, almost all of the of the GUI-based ``Canvas`` | ||
classes provide a `flush_event` method. By periodically calling this | ||
method the GUI will be updated and appear to be responsive. | ||
|
||
In both cases, to schedule a re-draw of the figure at some point in | ||
the future use ``fig.canvas.draw_idle()``. This will defer the actual | ||
rendering of the figure until the GUI is ready to update its | ||
on-screen representation. | ||
The third case you will have to integrate updating the ``Aritist`` | ||
tacaswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
instances, calling ``draw_idle``, and the GUI event loop with your | ||
data I/O. | ||
|
||
Stale Artists | ||
============= | ||
|
@@ -150,14 +212,100 @@ figure. | |
|
||
|
||
Interactive Mode | ||
---------------- | ||
================ | ||
|
||
|
||
Draw Idle | ||
========= | ||
|
||
In almost all cases, we recommend using | ||
`backend_bases.FigureCanvasBae.draw_idle` over | ||
`backend_bases.FigureCanvasBae.draw`. ``draw`` forces a rendering of | ||
the figure where as ``draw_idle`` schedules a rendering the next time | ||
tacaswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
the GUI window is going to re-paint the screen. This improves | ||
performance by only rendering pixels that will be shown on the screen. If | ||
you want to be sure that the screen is updated as soon as possible do :: | ||
|
||
fig.canvas.draw_idle() | ||
fig.canvas.flush_events() | ||
|
||
|
||
|
||
.. autosummary:: | ||
:template: autosummary.rst | ||
:nosignatures: | ||
|
||
backend_bases.FigureCanvasBase.draw | ||
backend_bases.FigureCanvasBase.draw_idle | ||
backend_bases.FigureCanvasBase.flush_events | ||
|
||
Threading | ||
========= | ||
|
||
Unfortunately, most GUI frameworks require that all updates to the | ||
screen happen on the main thread which makes pushing periodic updates | ||
to the window to a background thread problematic. Although it seems | ||
backwards, it is easier to push your computations to a background | ||
thread and periodically update the figure from the main thread. | ||
|
||
In general Matplotlib is not thread safe, but the consequences of | ||
drawing while updating artists from another thread should not be worse | ||
than a failed draw. This should not be fatal and so long as the | ||
Artists end up consistent the figure can eventually be drawn cleanly. | ||
|
||
Web | ||
=== | ||
|
||
|
||
|
||
The Weeds | ||
QuLogic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
========= | ||
|
||
|
||
Eventloop integration mechanism | ||
------------------------------- | ||
|
||
CPython / readline | ||
~~~~~~~~~~~~~~~~~~ | ||
|
||
The python capi provides a hook, `PyOS_InputHook`, to register a | ||
function to be run "The function will be called when Python's | ||
interpreter prompt is about to become idle and wait for user input | ||
from the terminal.". This hook can be used to integrate a second | ||
event loop (the GUI event loop) with the python input prompt loop. | ||
The hook functions typically exhaust all pending events on the GUI | ||
event queue, run the main loop for a short fixed amount of time, or | ||
run the event loop until a key is pressed on stdin. | ||
|
||
|
||
Matplotlib does not currently do any management of `PyOS_InputHook` | ||
due to the wide range of ways that matplotlib is used. This | ||
management is left to the code using Matplotlib. Interactive figures, | ||
even with matplotlib in 'interactive mode', may not work in the | ||
vanilla python repl if an appropriate `PyOS_InputHook` is not | ||
registered. | ||
|
||
Input hooks, and helpers to install them, are usually included with | ||
the python bindings for GUI toolkits and may be registered on import. | ||
IPython also ships input hook functions for all of the GUI frameworks | ||
Matplotlib supports which can be installed via ``%matplotlib``. This | ||
is the recommended method of integrating Matplotlib and a prompt. | ||
|
||
|
||
|
||
IPython / prompt toolkit | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
With IPython >= 5.0 IPython has changed from using cpython's readline | ||
based prompt to a ``prompt_toolkit`` based prompt. ``prompt_toolkit`` | ||
has the same conceptual input hook, which is feed into pt via the | ||
tacaswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:meth:`IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook` | ||
method. The source for the prompt_toolkit input hooks lives at | ||
tacaswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:mod:`IPython.terminal.pt_inputhooks` | ||
|
||
Blitting | ||
======== | ||
|
||
|
||
.. ruberic:: Fotenotes | ||
.. rubric:: Fotenotes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo |
||
|
||
.. [#f1] A limitation of this design is that you can only wait for one | ||
input, if there is a need to multiplex between multiple sources | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitty-gritty