8000 DOC: Start to document interactive figures by tacaswell · Pull Request #4779 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

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

Merged
merged 36 commits into from
May 21, 2020
Merged
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cfa717a
DOC: Start to document interactive figures
tacaswell Jul 24, 2015
206f99c
DOC: improve text
tacaswell Jul 31, 2015
fa36c95
DOC: address comments
tacaswell Jan 7, 2017
8b28c01
DOC/WIP: updates to docs on how event loops work
tacaswell Jul 28, 2017
7189eed
DOC: add blocking_input docs
tacaswell Dec 28, 2017
98ae26c
WIP: Lots of text changes to interactive guide
tacaswell Dec 28, 2017
6263284
MNT: add warnings to Figure.show
tacaswell Dec 29, 2017
09deda3
DOC: re-arrange shell.rst and interactive.rst
tacaswell Jan 3, 2018
ec4230a
DOC/WIP: more edits and content
tacaswell Jul 10, 2018
d1a7a3e
WIP: more notes
tacaswell Mar 2, 2019
b49973a
DOC: merge the blocking API docs together
tacaswell Apr 28, 2020
bb8058a
DOC: remove rst files that have been merged into interactive.rst
tacaswell Apr 28, 2020
bc4ecd5
DOC: fix and sort intersphinx
tacaswell Apr 28, 2020
49f49fa
DOC: re-write pyplot.show docstring
tacaswell Apr 29, 2020
295d71d
DOC: plt.pause is no longer experimental
tacaswell Apr 29, 2020
c3c5013
DOC: update pyplot documentation
tacaswell Apr 30, 2020
d9df057
DOC: Lots of editing to interactive figure documentation
tacaswell Apr 30, 2020
7b0b327
DOC: it's -> its
tacaswell Apr 30, 2020
5a426d6
DOC: spelling
tacaswell Apr 30, 2020
8ab0a65
DOC: edits from review
tacaswell May 1, 2020
d70d5ad
DOC: update skipped references
tacaswell May 1, 2020
757e040
DOC: correct many spelling / grammer / clarity issues
tacaswell May 3, 2020
b0690ff
DOC: remove badly named and superfluous heading
tacaswell May 7, 2020
cc3ac5b
DOC: address review comments
tacaswell May 7, 2020
afe9edd
DOC: edits from review
tacaswell May 7, 2020
e09cab9
DOC: more small edits
tacaswell May 7, 2020
04f2fc6
DOC: fix spyder spelling
tacaswell May 7, 2020
9d842bf
DOC: edits from review
tacaswell May 7, 2020
41e19a8
MNT: remove unused import
tacaswell May 7, 2020
eda2ed3
DOC: edits from review
tacaswell May 9, 2020
7cf68e8
DOC: remove non-existent function call
tacaswell May 9, 2020
bfa3589
DOC: edits and duplicate removal
tacaswell May 9, 2020
6edcb47
DOC: windows not widows
tacaswell May 9, 2020
9b492bd
DOC: Revert all changes to blocking_input_api.rst
tacaswell May 9, 2020
6d4747f
DOC: reduce jargon and correct grammar
tacaswell May 20, 2020
f05ea3d
DOC: grammar / wording corrections from review
tacaswell May 20, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP: Lots of text changes to interactive guide
  • Loading branch information
tacaswell committed May 1, 2020
commit 98ae26cc192205c431e5a72c4e7b1ec4d6f2c188
332 changes: 240 additions & 92 deletions doc/users/interactive_guide.rst
10000
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:
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitty-gritty

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all figures

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fingers
"simultaneously a"

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
1356 Copy link
Contributor

Choose a reason for hiding this comment

The 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``
instances, calling ``draw_idle``, and the GUI event loop with your
data I/O.

Stale Artists
=============
Expand All @@ -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
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
=========


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
:meth:`IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook`
method. The source for the prompt_toolkit input hooks lives at
:mod:`IPython.terminal.pt_inputhooks`

Blitting
========


.. ruberic:: Fotenotes
.. rubric:: Fotenotes
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand Down
0