From cfa717a43d5ad553da053cc3c969f797864755fe Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 24 Jul 2015 01:59:04 -0400 Subject: [PATCH 01/36] DOC: Start to document interactive figures --- doc/users/interactive.rst | 1 + doc/users/interactive_guide.rst | 82 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 doc/users/interactive_guide.rst diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index e8af8df9409e..be29704aee24 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -10,3 +10,4 @@ navigation_toolbar.rst shell.rst event_handling.rst + interactive_guide diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst new file mode 100644 index 000000000000..f689798669be --- /dev/null +++ b/doc/users/interactive_guide.rst @@ -0,0 +1,82 @@ +.. _plotting-guide-interactive: + +******************* +Interactive Figures +******************* + +One of the most powerful ways to use 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 +graphs. + +This page is meant to be a rapid introduction to the relevant details of +integrating the matplotlib with a GUI event loop. + + +The GUI event loop +------------------ + +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: + +1. let the GUI main loop block the python process +2. intermittently run the GUI loop for a period of time + +Going with option 1 is going down the route of writing a bespoke GUI +application. In this case, commonly refereed to as 'embedding', the +GUI event loop is running the show and you should not use the `pyplot` +layer. Doing anything short of writing a full GUI application +requires option 2. + +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.". As an implementation detail of cpython when +using readline this times out every 0.1 seconds. Using this hook a +second event loop can be integrated with the terminal. This is done +in the cpython source for tk, by some GUI framework code (such as +pyqt), and by IPython. These function typically either exhaust all +pending events on the GUI event queue or run the main loop for a short +fixed amount of time. matplotlib does not currently do any management +of `PyOS_InputHook` due to the wide range of use-cases, this +management is left to the code using matplotlib. Due to this, +interactive figures, even with matplotlib in 'interactive mode' may +not be reliable in the vanilla python repl, we suggest using IPython +which ensures that such a function is registered for you GUI backend +of choice. + +A draw back of the above approach is that in is only useful while +python is otherwise idle and waiting for user input. The exact +methods required to force the GUI to process it's event loop varies +between frameworks. To enable writing GUI agnostic code, almost all +of the GUI-based ``Canvas`` classes provide a `flush_event` method. +By periodically calling this method the GUI can appear to be +updated and appear to be responsive. + +In both cases, scheduling 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 it's +on-screen representation. + +Stale Artists +------------- + +Artists (as of 1.5) have a ``stale`` attribute which is `True` if the +internal state of the artist has changed since the last time it was +drawn to the screen. The stale state is propagated up to the Artists +parents in the draw tree. Thus, ``fig.stale`` will report of any +artist in the figure has been modified and out of sync with what is +displayed on the screen. This is intended to be used to determine if +``draw_idle`` should be called to schedule a re-rendering of the +figure. + + +Interactive Mode +---------------- + + +Blitting +-------- From 206f99cc36aa74768c4b98e73e5e0541c1adcd34 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 30 Jul 2015 23:04:30 -0400 Subject: [PATCH 02/36] DOC: improve text --- doc/users/interactive_guide.rst | 54 ++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index f689798669be..d7f1a4dd44a8 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -4,7 +4,7 @@ Interactive Figures ******************* -One of the most powerful ways to use matplotlib is interactive +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 @@ -34,30 +34,34 @@ requires option 2. 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.". As an implementation detail of cpython when -using readline this times out every 0.1 seconds. Using this hook a -second event loop can be integrated with the terminal. This is done -in the cpython source for tk, by some GUI framework code (such as -pyqt), and by IPython. These function typically either exhaust all -pending events on the GUI event queue or run the main loop for a short -fixed amount of time. matplotlib does not currently do any management -of `PyOS_InputHook` due to the wide range of use-cases, this -management is left to the code using matplotlib. Due to this, -interactive figures, even with matplotlib in 'interactive mode' may -not be reliable in the vanilla python repl, we suggest using IPython -which ensures that such a function is registered for you GUI backend -of choice. - -A draw back of the above approach is that in is only useful while -python is otherwise idle and waiting for user input. The exact -methods required to force the GUI to process it's event loop varies -between frameworks. To enable writing GUI agnostic code, almost all -of the GUI-based ``Canvas`` classes provide a `flush_event` method. -By periodically calling this method the GUI can appear to be -updated and appear to be responsive. - -In both cases, scheduling a re-draw of the figure at some point in the -future use ``fig.canvas.draw_idle()``. This will defer the actual +from the terminal.". This hook can be used to integrate a second +event loop with the python repl. Such a 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_InptuHook` +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 achive 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 it's on-screen representation. From fa36c95e074a7db6b1e787c665625afbb3325029 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 7 Jan 2017 16:31:50 -0500 Subject: [PATCH 03/36] DOC: address comments --- doc/users/interactive_guide.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index d7f1a4dd44a8..294dd5c92ad9 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -10,8 +10,10 @@ a figure to inspect your data, however there is also a full mouse and keyboard event handling system to enable building 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. +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 +`__. The GUI event loop @@ -35,13 +37,13 @@ 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 with the python repl. Such a 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. +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 @@ -49,20 +51,20 @@ 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_InptuHook` +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 achive this, almost all of the of the GUI-based ``Canvas`` +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 it's +rendering of the figure until the GUI is ready to update its on-screen representation. Stale Artists From 8b28c01601ac3d072e7a45e67070e1f1ae2b272f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 27 Jul 2017 22:52:16 -0400 Subject: [PATCH 04/36] DOC/WIP: updates to docs on how event loops work --- doc/users/interactive_guide.rst | 116 ++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 294dd5c92ad9..8652ce177165 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -1,8 +1,8 @@ .. _plotting-guide-interactive: -******************* -Interactive Figures -******************* +************************************************ +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 @@ -15,9 +15,51 @@ of integrating the matplotlib with a GUI event loop. For further details see `Interactive Applications using Matplotlib `__. +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 :: + + exec_count = 0 + while True: + inp = input(f"[{exec_count}] > ") # Read + ret = eval(inp) # Evaluate + print(ret) # Print + exec_count += 1 # Loop + + +This is missing many niceties (for example, it exits on the first +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 `. + + + +references to trackdown: + - link to cpython REPL loop + - curio / trio / asycio / twisted / tornado event loops + - Beazly talk or two on asyncio -The GUI event loop ------------------- + +GUI to Matplotlib Bridge +------------------------ + +When using + + +Command Prompt +-------------- 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 @@ -27,11 +69,38 @@ run. To manage this in python there are two basic methods: 1. let the GUI main loop block the python process 2. intermittently run the GUI loop for a period of time -Going with option 1 is going down the route of writing a bespoke GUI -application. In this case, commonly refereed to as 'embedding', the -GUI event loop is running the show and you should not use the `pyplot` -layer. Doing anything short of writing a full GUI application -requires option 2. + +Blocking +******** + + +Interactive +*********** + +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 + + +Full embedding +-------------- + + - just let the underlying GUI event loop handle eve + +Web +--- + +The Weeds +========= + + +The GUI event loop +------------------ + The python capi provides a hook, `PyOS_InputHook`, to register a function to be run "The function will be called when Python's @@ -68,7 +137,7 @@ rendering of the figure until the GUI is ready to update its on-screen representation. Stale Artists -------------- +============= Artists (as of 1.5) have a ``stale`` attribute which is `True` if the internal state of the artist has changed since the last time it was @@ -85,4 +154,27 @@ Interactive Mode Blitting --------- +======== + + +.. ruberic:: Fotenotes + +.. [#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 + then the loop would look something like :: + + fds = [...] + while True: # Loop + inp = select(fds) # Read + eval(inp) # Evaluate / Print + + +.. [#f2] asyncio has a fundamentally different paradigm that uses + coroutines instead of callbacks as the user-facing interface, + however at the core there is a select loop like the above + footnote the multiplexes between the running tasks. + +.. [#f3] These examples are agressively dropping many of the + complexities that must be dealt with in the real world such as + keyboard interupts [link], timeouts, bad input, resource + allocation and cleanup, etc. From 7189eed4906a829ab68155a3b3a0161965a81bd3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 28 Dec 2017 12:59:26 -0500 Subject: [PATCH 05/36] DOC: add blocking_input docs --- doc/api/blocking_input.rst | 17 +++++++++++++++++ doc/api/index.rst | 1 + 2 files changed, 18 insertions(+) create mode 100644 doc/api/blocking_input.rst diff --git a/doc/api/blocking_input.rst b/doc/api/blocking_input.rst new file mode 100644 index 000000000000..e2f27dad3846 --- /dev/null +++ b/doc/api/blocking_input.rst @@ -0,0 +1,17 @@ +=========================== + ``blocking_input`` module +=========================== + +.. automodule:: matplotlib.blocking_input + :no-members: + :no-undoc-members: + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + BlockingInput + BlockingMouseInput + BlockingContourLabeler + BlockingKeyMouseInput diff --git a/doc/api/index.rst b/doc/api/index.rst index 35677bf0afdc..57cff475bd10 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -77,6 +77,7 @@ Matplotlib consists of the following submodules: backend_tools_api.rst index_backend_api.rst bezier_api.rst + blocking_input blocking_input_api.rst category_api.rst cbook_api.rst From 98ae26cc192205c431e5a72c4e7b1ec4d6f2c188 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 28 Dec 2017 15:31:21 -0500 Subject: [PATCH 06/36] WIP: Lots of text changes to interactive guide --- doc/users/interactive_guide.rst | 332 +++++++++++++++++++++++--------- 1 file changed, 240 insertions(+), 92 deletions(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 8652ce177165..aaf576d8968e 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -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 `__. 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 `. - - +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 `. + +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 +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 +waiting to be processed. The exact behavior is backend-dependent but +typically events on all figure are processed and only events waiting +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 +` 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 +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 +`. 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 +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 +` 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 -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 ============= @@ -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 .. [#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 From 62632846ec850dee97a026319fb2885e0b9e575d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 28 Dec 2017 20:50:51 -0500 Subject: [PATCH 07/36] MNT: add warnings to Figure.show --- lib/matplotlib/figure.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f7889aa5344e..a84ff1215b7f 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -395,6 +395,11 @@ def show(self, warn=True): python script, you should use `matplotlib.pyplot.show` instead, which takes care of managing the event loop for you. + .. warning + + If the GUI event loop is not running the figure will be shown, + but will not be responsive and may not draw. + Parameters ---------- warn : bool, default: True From 09deda3b09cf5636fbe6d530b62bd11db300df3f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Jan 2018 16:41:58 -0800 Subject: [PATCH 08/36] DOC: re-arrange shell.rst and interactive.rst DOC: minor tweaks to interactive docs --- doc/users/interactive.rst | 169 +++++++++++++++++++++++++++++++- doc/users/interactive_guide.rst | 14 ++- 2 files changed, 177 insertions(+), 6 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index be29704aee24..cd6501132f44 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -1,5 +1,6 @@ -.. _dump-index: +.. currentmodule:: matplotlib +.. _mpl-shell: =================== Interactive plots =================== @@ -7,7 +8,167 @@ .. toctree:: :maxdepth: 2 - navigation_toolbar.rst - shell.rst - event_handling.rst interactive_guide + event_handling.rst + + +By default, matplotlib defers drawing until explicitly asked. Drawing +can be an expensive operation, and you do not want to re-render the +figure every time a single property is changed, only once after all +the properties have changed and the figure is displayed on the screen +or saved to disk. When working in a shell, one typically wants the +figure to re-draw after every user command (ex when the prompt comes +back). *interactive* mode of :mod:`~.pyplot` takes care of arranging +such that if any open figures are :ref:`stale `, they +will be re-drawn just before the prompt is returned to the user. + +.. _ipython-pylab: + +IPython to the rescue +===================== + +We recommend using IPython for an interactive shell. In addition to +all of it's features (improved tab-completion, magics, +multiline-editing, etc), it also ensures that the GUI toolkit is +properly integrated with the command line (see :ref:`cp_integration`). +To configure the integration and enable interactive mode do:: + +.. sourcecode:: ipython + + user@machine:~ $ ipython + Python 3.6.4 (default, Dec 23 2017, 19:07:07) + Type 'copyright', 'credits' or 'license' for more information + IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help. + + In [1]: %matplotlib + iUsing matplotlib backend: Qt5Agg + + In [2]: import matplotlib.pyplot as plt + + In [3]: + +Calling + +.. sourcecode:: ipython + + In [3]: fig, ax = plt.subplots() + +will pop open a window for you and + +.. sourcecode:: ipython + + In [4]: ln, = ax.plot(range(5)) + +will show your data in the window. If you +want to change anything about the line, ex the color + +.. sourcecode:: ipython + + In [5]: ln.set_color('orange') + +will be reflected immediately. + + + +.. _other-shells: + +Other python interpreters +========================= + +If you can not or do not want to use IPython, interactive mode +should work in the vanilla python prompt + + +.. sourcecode:: python + + user@machine:~ $ python + Python 3.6.4 (default, Dec 23 2017, 19:07:07) + [GCC 7.2.1 20171128] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import matplotlib.pyplot as plt + >>> plt.ion() + >>> + +however this does not ensure that the event hook is properly installed +and your figures may not be responsive (see :ref:`cp_integration`). + +GUI python prompts may or may not work with all backends. For +example, if you use the IDLE IDE you must use the 'tkagg' backend (as +IDLE is a Tk application.. However, if you use spyder, the +interactive shell is run in a sub process of the GUI and you can use +any backend. + + +.. _controlling-interactive: + +Controlling interactive updating +================================ + + +To control and query the current state of *interactive* mode + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + pyplot.ion + pyplot.ioff + pyplot.isinteractive + +When working with a big figure in which drawing is expensive, you may +want to turn matplotlib's interactive setting off temporarily to avoid +the performance hit:: + + + >>> #create big-expensive-figure + >>> plt.ioff() # turn updates off + >>> ax.set_title('now how much would you pay?') + >>> fig.canvas.draw_idle() # force a draw + >>> fig.savefig('alldone', dpi=300) + >>> plt.close('all') + >>> plt.ion() # turn updating back on + >>> fig, ax = plt.subplots() + >>> ax.plot(rand(20), mfc='g', mec='r', ms=40, mew=4, ls='--', lw=3) + + +Default UI +========== + + +.. toctree:: + :maxdepth: 1 + + navigation_toolbar.rst + + +The windows created by :mod:`~.pyplot` have an interactive toolbar and +has a number of helpful keybindings by default. + +.. _key-event-handling: + +Navigation Keyboard Shortcuts +----------------------------- + +The following table holds all the default keys, which can be +overwritten by use of your matplotlibrc (#keymap.\*). + +================================== ================================================= +Command Keyboard Shortcut(s) +================================== ================================================= +Home/Reset **h** or **r** or **home** +Back **c** or **left arrow** or **backspace** +Forward **v** or **right arrow** +Pan/Zoom **p** +Zoom-to-rect **o** +Save **ctrl** + **s** +Toggle fullscreen **f** or **ctrl** + **f** +Close plot **ctrl** + **w** +Close all plots **shift** + **w** +Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse +Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse +Preserve aspect ratio hold **CONTROL** when panning/zooming with mouse +Toggle major grids **g** when mouse is over an axes +Toggle minor grids **G** when mouse is over an axes +Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes +Toggle y axis scale (log/linear) **l** when mouse is over an axes +================================== ================================================= diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index aaf576d8968e..69502bc3ab18 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -61,6 +61,13 @@ references to trackdown: - curio / trio / asycio / twisted / tornado event loops - Beazly talk or two on asyncio + + +Interactive Mode +================ + +.. _cp_integration: + Command Prompt Integration ========================== @@ -198,6 +205,8 @@ 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: + Stale Artists ============= @@ -210,9 +219,10 @@ displayed on the screen. This is intended to be used to determine if ``draw_idle`` should be called to schedule a re-rendering of the figure. +TODO: -Interactive Mode -================ +- notes about callbacks +- Draw Idle From ec4230a31804be38f4f36c976e1ffc239f2aa778 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 10 Jul 2018 16:40:12 -0400 Subject: [PATCH 09/36] DOC/WIP: more edits and content --- doc/users/interactive_guide.rst | 202 +++++++++++++++++++++----------- 1 file changed, 135 insertions(+), 67 deletions(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 69502bc3ab18..4da597fc800a 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -1,4 +1,4 @@ -.. _plotting-guide-interactive: +.. _interactive_figures_and_eventloops: .. currentmodule:: matplotlib @@ -7,17 +7,22 @@ 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 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 +Matplotlib supports rich interactive figures by embedding figures into +a GUI window. The basic interactions of panning and zooming in as +Axis to inspect your data is 'baked in' to Matplotlib. This is +supported by a full mouse and keyboard event handling system to enable +you to build sophisticated interactive graphs. + +This is meant to be an introduction to the low-level details of how +integrating the Matplotlib with a GUI event loop works. For a more +practical introduction the Matplotlib event API see `Interactive +Tutorial `__ and +`Interactive Applications using Matplotlib `__. +Event Loops +=========== + Fundamentally, all user interaction (and networking) is implemented as an infinite loop waiting for events from the user (via the OS) and then doing something about it. For example, a minimal Read Evaluate @@ -33,26 +38,29 @@ Print Loop (REPL) is :: This is missing many niceties (for example, it exits on the first 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. +all terminals, GUIs, and servers [#f1]_. In general the *Read* step +is waiting on some sort of I/O -- be it user input or the network -- +while the *Evaluate* and *Print* are responsible for 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 +use a framework that provides a mechanism to register callbacks to be +called in response to specific event. For example "when the user +clicks on this button, please run this function" or "when the user +hits the 'z' key, please run this other function". This allows users +to write reactive, event-driven, programs without having to delve into +the nity-gritty [#f2]_ details of I/O. Examples of this pattern the +``Signal`` / ``Slot`` framework in Qt (and the analogs in other GUI +frameworks), the 'request' functions in flask, and Matplotlib's :ref:`event handling system `. -All GUI frameworks (Qt, Wx, Gtk, tk, QSX, or web) have some method of +All GUI frameworks (Qt, Wx, Gtk, tk, OSX, 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. +although the exact details vary. Matplotlib has a :ref:`backnd +` for each GUI framework which use these mechanisms +to convert the native UI events into Matplotlib events so we can +develop framework-independent interactive figures. + references to trackdown: - link to cpython REPL loop (pythonrun.c::PyRunInteractiveLoopFlags) @@ -62,26 +70,30 @@ references to trackdown: - Beazly talk or two on asyncio - -Interactive Mode -================ - .. _cp_integration: Command Prompt Integration ========================== -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'. +Integrating a GUI window into a CLI introduces a conflict: there are +two infinite loops that want to be waiting for user input on the main +thread in the same process. Python wants to be blocking the main +thread it it's REPL loop and the GUI framework wants to be running +it's event loop. In order for both the prompt and the GUI widows to +be responsive we need a method to allow the loops to 'timeshare' : + +1. let the GUI main loop block the python process when you want + interactive windows +2. let the CLI main loop block the python process and intermittently + run the GUI loop -1. let the GUI main loop block the python process -2. intermittently run the GUI loop +The inverse problem, embedding an interactive prompt into a GUI, is +also a way out of this problem by letting the GUI event loop always +run the show, but that is essentially writing a full application. -Blocking --------- +Blocking the Prompt +------------------- The simplest "integration" is to start the GUI event loop in '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 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. +the event loop will make any open figure responsive again. -To start the event loop until all open figures are closed us +To start the event loop until all open figures are closed use `pyplot.show(block=True)`. To start the event loop for a fixed amount of time use `pyplot.pause`. @@ -104,11 +116,13 @@ Without using ``pyplot`` you can start and stop the event loops via .. 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. + screen without explicitly starting the event loop and not being in + interactive mode. This may work but will likely result in a + non-responsive figure and may not even display the rendered plot. +This technique can be very useful if you want to write a script that +pauses for user interaction, see :ref:`interactive_script`. .. autosummary:: :template: autosummary.rst @@ -123,37 +137,94 @@ Without using ``pyplot`` you can start and stop the event loops via figure.Figure.show -Explicitly ----------- +Explicitly running the Event Loop +--------------------------------- 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 -waiting to be processed. The exact behavior is backend-dependent but -typically events on all figure are processed and only events waiting -to be processed (not those added during processing) will be handled. +calling ``fig.canvas.flush_events()``. This will run the GUI event +loop, until all UI events currently waiting have been processed. The +exact behavior is backend-dependent but typically events on all figure +are processed and only events waiting to be processed (not those added +during processing) will be handled. + +For example :: + + import time + import matplotlib.pyplot as plt + import numpy as np + plt.ion() + + fig, ax = plt.subplots() + fig.canvas.show() + th = np.linspace(0, 2*np.pi, 512) + ax.set_ylim(-1.5, 1.5) + + ln, = ax.plot(th, np.sin(th)) + + def slow_loop(N, ln): + for j in range(N): + time.sleep(.1) # to simulate some work + ln.figure.canvas.flush_events() + + slow_loop(100, ln) + +Will be a bit laggy, as we are only processing user input every 100ms +(where as 20-30ms is what feels "responsive"), but it will respond. + + +If you make changes to the plot and want it re-rendered you will need +to call `~.FigureCanvasBase.draw_idle()` to request that the canvas be +re-drawn. This method can be thought of *draw_soon* in analogy to +`asyncio.BaseEventLoop.call_soon`. + +We can add this our example above as :: + + def slow_loop(N, ln): + for j in range(N): + time.sleep(.1) # to simulate some work + if j % 10: + ln.set_ydata(np.sin(((j // 10) % 5 * th))) + ln.figure.canvas.draw_idle() + + ln.figure.canvas.flush_events() + + slow_loop(100, ln) + + + .. autosummary:: :template: autosummary.rst :nosignatures: backend_bases.FigureCanvasBase.flush_events + backend_bases.FigureCanvasBase.draw_idle + 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 -` 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 -windows and prompt. +able to have a usable prompt **and** interactive figure windows. + +We can do this using the 'input hook' feature of the interactive +prompt. This hook is called by the prompt as it waits for the user +type (even for a fast typist the prompt is mostly waiting for the +human to think and move their fingers). Although the details vary +between prompts the logic is roughly + +1. start to wait for keyboard input +2. start the GUI event loop +3. as soon as the user hits a key, exit the GUI event loop and handle the key +4. repeat + +This gives us the illusion of simultaneously having an interactive GUI +windows and an interactive prompt. Most of the time the GUI event +loop is running, but as soon as the user starts typing the prompt +takes over again. 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 @@ -174,6 +245,8 @@ windows are implemented!). See :ref:`user_interfaces` for more details on how to do this. +.. _interactive_scripts : + Scripts ======= @@ -202,7 +275,7 @@ visualization and less on your computation. The second case is very much like :ref:`Blocking` above. By using ``plt.show(block=True)`` or The third case you will have to integrate updating the ``Aritist`` -instances, calling ``draw_idle``, and the GUI event loop with your +instances, calling ``draw_idle``, and flushing the GUI event loop with your data I/O. .. _stale_artists: @@ -322,17 +395,12 @@ method. The source for the prompt_toolkit input hooks lives at then the loop would look something like :: fds = [...] - while True: # Loop - inp = select(fds) # Read - eval(inp) # Evaluate / Print - + while True: # Loop + inp = select(fds).read() # Read + eval(inp) # Evaluate / Print -.. [#f2] asyncio has a fundamentally different paradigm that uses - coroutines instead of callbacks as the user-facing interface, - however at the core there is a select loop like the above - footnote the multiplexes between the running tasks. -.. [#f3] These examples are agressively dropping many of the +.. [#f2] These examples are agressively dropping many of the complexities that must be dealt with in the real world such as keyboard interupts [link], timeouts, bad input, resource allocation and cleanup, etc. From d1a7a3e638979a63f72e60369c1e9bdc865b4903 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 1 Mar 2019 23:49:42 -0500 Subject: [PATCH 10/36] WIP: more notes --- doc/conf.py | 3 ++ doc/users/interactive.rst | 106 ++++++++++++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 11 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 76c941b7d7bc..c7fd1922c8c9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -122,6 +122,9 @@ def _check_dependencies(): 'Pillow': ('https://pillow.readthedocs.io/en/stable/', None), 'pytest': ('https://pytest.org/en/stable', None), 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), + 'cycler': ('https://matplotlib.org/cycler', None), + 'ipympl': ('https://matplotlib.org/ipympl', None), + 'ipywidget': ('', None), } diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index cd6501132f44..6e58b4095996 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -1,6 +1,7 @@ .. currentmodule:: matplotlib .. _mpl-shell: + =================== Interactive plots =================== @@ -13,19 +14,36 @@ By default, matplotlib defers drawing until explicitly asked. Drawing -can be an expensive operation, and you do not want to re-render the -figure every time a single property is changed, only once after all -the properties have changed and the figure is displayed on the screen -or saved to disk. When working in a shell, one typically wants the -figure to re-draw after every user command (ex when the prompt comes -back). *interactive* mode of :mod:`~.pyplot` takes care of arranging -such that if any open figures are :ref:`stale `, they -will be re-drawn just before the prompt is returned to the user. +may be an expensive operation and some changes to the figure may leave +it in an inconsistent state, hence we only want to render the figure +just before the figure is displayed on the screen or saved to disk. +In the case of scripts this is easy to arrange, we simple do nothing +until ``savefig`` is called. However, when working interactively, +either at a terminal or in a notebook, we would like the figure to +automatically re-render when we change. Further, we would like the +figures respond to user input (mouse and keyboard) to interact with +and explore the data. + +To get this functionality we need a combination of an interactive +backend (to handle the user interaction with the figure) and an +interactive interpreter (to handle the input of user code). Together +these tools need to: + +- Take text from the user and execute it in the Python interpreter +- Ensure that any created figures are put on the screen +- Trigger a re-render of the figure when the user has mutated it +- When not actively executing user input, run the event loop so we can + get user input on the figure (see + :ref:`interactive_figures_and_eventloops` for details) + + .. _ipython-pylab: -IPython to the rescue -===================== +IPython to the rescue! +====================== + +To support interactive We recommend using IPython for an interactive shell. In addition to all of it's features (improved tab-completion, magics, @@ -72,7 +90,7 @@ will be reflected immediately. .. _other-shells: -Other python interpreters +Other Python interpreters ========================= If you can not or do not want to use IPython, interactive mode @@ -99,6 +117,72 @@ interactive shell is run in a sub process of the GUI and you can use any backend. +PyCharm +------- + +TODO: links to pycharm docs on configuring Matplotlib backends + +Spyder +------ + +TODO: links to spyder docs on configuring Matplotlib backends + +VSCode +------ + +TODO: links to vscode docs on configuring Matplotlib backends (?) + + +Jupyter Notebooks / Jupyter Lab +------------------------------- + +.. warning:: + + To get the interactive functionality described here, you must be + using an interactive backend, however he 'inline' backend is not. + It renders the figure once and inserts a static image into the + notebook everytime the cell in run. Being static these plots can + not be panned / zoomed or take user input. + +Jupyter uses a different architecture than a traditional interactive +terminal. Rather than the user interface running in the same process +as your interpreter, the user interacts with a javascript front end +running in a browser which communicates with a server which in turn +communicates with a kernel that actually runs the python. This means +for interactive figures our UI needs to also be written in javascript +and run inside of the jupyter front end. + +To get interactive figures in the 'classic' notebook or jupyter lab use +the `ipympl` backend (must be installed separately) which uses the `ipywidget` +framework and which enabled by using :: + + %matplotlib widget + +If you only need to use the classic notebook you can also use :: + + %matplotlib notebook + +which uses the `nbagg` backend which ships with Matplotlib (but does +not work with JLab because, for security reasons, you can no longer +inject arbitrary javascript into the front end). + +GUIs + jupyter +~~~~~~~~~~~~~~ + +If you are running your jupyter server locally you can use one of the +GUI backends, however if you ever move that notebook to a remote +server it will cease to work correctly. What is happening is that +when you create a figure the process running your kernel creates and +shows a GUI window. If that process is on the same computer as you, +then you will see it, however if it is running on a remote computer it +will try to open the GUI window on _that_ computer. This will either +fail by raising an exception (as many linux servers do not have an +XServer running) or run cleanly, but leave you with no way to access +your figure. + + + + .. _controlling-interactive: Controlling interactive updating From b49973a04c9c2e42ace78de2650ca9569b2e91d4 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 28 Apr 2020 15:45:29 -0400 Subject: [PATCH 11/36] DOC: merge the blocking API docs together --- doc/api/blocking_input.rst | 17 ----------------- doc/api/blocking_input_api.rst | 15 +++++++++++++-- doc/api/index.rst | 1 - 3 files changed, 13 insertions(+), 20 deletions(-) delete mode 100644 doc/api/blocking_input.rst diff --git a/doc/api/blocking_input.rst b/doc/api/blocking_input.rst deleted file mode 100644 index e2f27dad3846..000000000000 --- a/doc/api/blocking_input.rst +++ /dev/null @@ -1,17 +0,0 @@ -=========================== - ``blocking_input`` module -=========================== - -.. automodule:: matplotlib.blocking_input - :no-members: - :no-undoc-members: - -.. autosummary:: - :toctree: _as_gen - :template: autosummary.rst - :nosignatures: - - BlockingInput - BlockingMouseInput - BlockingContourLabeler - BlockingKeyMouseInput diff --git a/doc/api/blocking_input_api.rst b/doc/api/blocking_input_api.rst index 6ba612682ac4..67f1ba6d8032 100644 --- a/doc/api/blocking_input_api.rst +++ b/doc/api/blocking_input_api.rst @@ -3,6 +3,17 @@ ***************************** .. automodule:: matplotlib.blocking_input - :members: - :undoc-members: + :no-members: + :no-undoc-members: :show-inheritance: + + +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: + + BlockingInput + BlockingMouseInput + BlockingContourLabeler + BlockingKeyMouseInput diff --git a/doc/api/index.rst b/doc/api/index.rst index 57cff475bd10..35677bf0afdc 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -77,7 +77,6 @@ Matplotlib consists of the following submodules: backend_tools_api.rst index_backend_api.rst bezier_api.rst - blocking_input blocking_input_api.rst category_api.rst cbook_api.rst From bb8058aaac96b682460b65bbe9dc59cdac8d6315 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 28 Apr 2020 16:21:01 -0400 Subject: [PATCH 12/36] DOC: remove rst files that have been merged into interactive.rst Update the example of using `.. image` to point at the third party package page. --- doc/devel/documenting_mpl.rst | 6 +- doc/users/navigation_toolbar.rst | 142 --------------------------- doc/users/shell.rst | 160 ------------------------------- 3 files changed, 3 insertions(+), 305 deletions(-) delete mode 100644 doc/users/navigation_toolbar.rst delete mode 100644 doc/users/shell.rst diff --git a/doc/devel/documenting_mpl.rst b/doc/devel/documenting_mpl.rst index dcc3a2411c04..c7c02d7d4b99 100644 --- a/doc/devel/documenting_mpl.rst +++ b/doc/devel/documenting_mpl.rst @@ -304,12 +304,12 @@ Including figures and files --------------------------- Image files can directly included in pages with the ``image::`` directive. -e.g., :file:`users/navigation_toolbar.rst` displays the toolbar icons -with a call to a static image:: +e.g., :file:`thirdpartypackages/index.rst` displays the images for the third-party +packages as static images:: .. image:: ../_static/toolbar.png -as rendered on the page: :ref:`navigation-toolbar`. +as rendered on the page: :ref:`thirdparty-index`. Files can be included verbatim. For instance the ``matplotlibrc`` file is important for customizing Matplotlib, and is included verbatim in the diff --git a/doc/users/navigation_toolbar.rst b/doc/users/navigation_toolbar.rst deleted file mode 100644 index 86849ab38354..000000000000 --- a/doc/users/navigation_toolbar.rst +++ /dev/null @@ -1,142 +0,0 @@ -.. _navigation-toolbar: - -Interactive navigation -====================== - -.. image:: ../_static/toolbar.png - -All figure windows come with a navigation toolbar, which can be used -to navigate through the data set. Here is a description of each of -the buttons at the bottom of the toolbar - -.. image:: ../../lib/matplotlib/mpl-data/images/home_large.png - -.. image:: ../../lib/matplotlib/mpl-data/images/back_large.png - -.. image:: ../../lib/matplotlib/mpl-data/images/forward_large.png - -The ``Home``, ``Forward`` and ``Back`` buttons - These are akin to a web browser's home, forward and back controls. - ``Forward`` and ``Back`` are used to navigate back and forth between - previously defined views. They have no meaning unless you have already - navigated somewhere else using the pan and zoom buttons. This is analogous - to trying to click ``Back`` on your web browser before visiting a - new page or ``Forward`` before you have gone back to a page -- - nothing happens. ``Home`` always takes you to the - first, default view of your data. Again, all of these buttons should - feel very familiar to any user of a web browser. - -.. image:: ../../lib/matplotlib/mpl-data/images/move_large.png - -The ``Pan/Zoom`` button - This button has two modes: pan and zoom. Click the toolbar button - to activate panning and zooming, then put your mouse somewhere - over an axes. Press the left mouse button and hold it to pan the - figure, dragging it to a new position. When you release it, the - data under the point where you pressed will be moved to the point - where you released. If you press 'x' or 'y' while panning the - motion will be constrained to the x or y axis, respectively. Press - the right mouse button to zoom, dragging it to a new position. - The x axis will be zoomed in proportionately to the rightward - movement and zoomed out proportionately to the leftward movement. - The same is true for the y axis and up/down motions. The point under your - mouse when you begin the zoom remains stationary, allowing you to - zoom in or out around that point as much as you wish. You can use the - modifier keys 'x', 'y' or 'CONTROL' to constrain the zoom to the x - axis, the y axis, or aspect ratio preserve, respectively. - - With polar plots, the pan and zoom functionality behaves - differently. The radius axis labels can be dragged using the left - mouse button. The radius scale can be zoomed in and out using the - right mouse button. - -.. image:: ../../lib/matplotlib/mpl-data/images/zoom_to_rect_large.png - -The ``Zoom-to-rectangle`` button - Click this toolbar button to activate this mode. Put your mouse somewhere - over an axes and press a mouse button. Define a rectangular region by - dragging the mouse while holding the button to a new location. When using - the left mouse button, the axes view limits will be zoomed to the defined - region. When using the right mouse button, the axes view limits will be - zoomed out, placing the original axes in the defined region. - -.. image:: ../../lib/matplotlib/mpl-data/images/subplots_large.png - -The ``Subplot-configuration`` button - Use this tool to configure the appearance of the subplot: - you can stretch or compress the left, right, top, or bottom - side of the subplot, or the space between the rows or - space between the columns. - -.. image:: ../../lib/matplotlib/mpl-data/images/filesave_large.png - -The ``Save`` button - Click this button to launch a file save dialog. You can save - files with the following extensions: ``png``, ``ps``, ``eps``, - ``svg`` and ``pdf``. - - -.. _key-event-handling: - -Navigation Keyboard Shortcuts ------------------------------ - -The following table holds all the default keys, which can be overwritten by use of your matplotlibrc (#keymap.\*). - -================================== ================================================= -Command Keyboard Shortcut(s) -================================== ================================================= -Home/Reset **h** or **r** or **home** -Back **c** or **left arrow** or **backspace** -Forward **v** or **right arrow** -Pan/Zoom **p** -Zoom-to-rect **o** -Save **ctrl** + **s** -Toggle fullscreen **f** or **ctrl** + **f** -Close plot **ctrl** + **w** -Close all plots *unassigned* -Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse -Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse -Preserve aspect ratio hold **CONTROL** when panning/zooming with mouse -Toggle major grids **g** when mouse is over an axes -Toggle minor grids **G** when mouse is over an axes -Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes -Toggle y axis scale (log/linear) **l** when mouse is over an axes -================================== ================================================= - -If you are using :mod:`matplotlib.pyplot` the toolbar will be created -automatically for every figure. If you are writing your own user -interface code, you can add the toolbar as a widget. The exact syntax -depends on your UI, but we have examples for every supported UI in the -``matplotlib/examples/user_interfaces`` directory. Here is some -example code for GTK+ 3:: - - - import gi - gi.require_version('Gtk', '3.0') - from gi.repository import Gtk - - from matplotlib.figure import Figure - from matplotlib.backends.backend_gtk3agg import FigureCanvas - from matplotlib.backends.backend_gtk3 import ( - NavigationToolbar2GTK3 as NavigationToolbar) - - win = Gtk.Window() - win.connect("destroy", lambda x: Gtk.main_quit()) - win.set_default_size(400,300) - win.set_title("Embedding in GTK") - - vbox = Gtk.VBox() - win.add(vbox) - - fig = Figure(figsize=(5,4), dpi=100) - ax = fig.add_subplot(111) - ax.plot([1,2,3]) - - canvas = FigureCanvas(fig) # a Gtk.DrawingArea - vbox.pack_start(canvas, True, True, 0) - toolbar = NavigationToolbar(canvas, win) - vbox.pack_start(toolbar, False, False, 0) - - win.show_all() - Gtk.main() diff --git a/doc/users/shell.rst b/doc/users/shell.rst deleted file mode 100644 index 072279674b53..000000000000 --- a/doc/users/shell.rst +++ /dev/null @@ -1,160 +0,0 @@ -.. _mpl-shell: - -********************************** -Using matplotlib in a python shell -********************************** - -.. warning:: - - This page is significantly out of date - -By default, matplotlib defers drawing until the end of the script -because drawing can be an expensive operation, and you may not want -to update the plot every time a single property is changed, only once -after all the properties have changed. - -But when working from the python shell, you usually do want to update -the plot with every command, e.g., after changing the -:func:`~matplotlib.pyplot.xlabel`, or the marker style of a line. -While this is simple in concept, in practice it can be tricky, because -matplotlib is a graphical user interface application under the hood, -and there are some tricks to make the applications work right in a -python shell. - - -.. _ipython-pylab: - -IPython to the rescue -===================== - -.. note:: - - The mode described here still exists for historical reasons, but it is - highly advised not to use. It pollutes namespaces with functions that will - shadow python built-in and can lead to hard to track bugs. To get IPython - integration without imports the use of the ``%matplotlib`` magic is - preferred. See - `ipython documentation `_ - . - -Fortunately, `ipython `_, an enhanced -interactive python shell, has figured out all of these tricks, and is -matplotlib aware, so when you start ipython in the *pylab* mode. - -.. sourcecode:: ipython - - johnh@flag:~> ipython - Python 2.4.5 (#4, Apr 12 2008, 09:09:16) - IPython 0.9.0 -- An enhanced Interactive Python. - - In [1]: %pylab - - Welcome to pylab, a matplotlib-based Python environment. - For more information, type 'help(pylab)'. - - In [2]: x = randn(10000) - - In [3]: hist(x, 100) - -it sets everything up for you so interactive plotting works as you -would expect it to. Call :func:`~matplotlib.pyplot.figure` and a -figure window pops up, call :func:`~matplotlib.pyplot.plot` and your -data appears in the figure window. - -Note in the example above that we did not import any matplotlib names -because in pylab mode, ipython will import them automatically. -ipython also turns on *interactive* mode for you, which causes every -pyplot command to trigger a figure update, and also provides a -matplotlib aware ``run`` command to run matplotlib scripts -efficiently. ipython will turn off interactive mode during a ``run`` -command, and then restore the interactive state at the end of the -run so you can continue tweaking the figure manually. - -There has been a lot of recent work to embed ipython, with pylab -support, into various GUI applications, so check on the ipython -mailing `list -`_ for the -latest status. - -.. _other-shells: - -Other python interpreters -========================= - -If you can't use ipython, and still want to use matplotlib/pylab from -an interactive python shell, e.g., the plain-ole standard python -interactive interpreter, you -are going to need to understand what a matplotlib backend is -:ref:`what-is-a-backend`. - - - -With the TkAgg backend, which uses the Tkinter user interface toolkit, -you can use matplotlib from an arbitrary non-gui python shell. Just set your -``backend : TkAgg`` and ``interactive : True`` in your -:file:`matplotlibrc` file (see :doc:`/tutorials/introductory/customizing`) and fire -up python. Then:: - - >>> from pylab import * - >>> plot([1,2,3]) - >>> xlabel('hi mom') - -should work out of the box. This is also likely to work with recent -versions of the qt4agg and gtk3agg backends, and with the macosx backend -on the Macintosh. Note, in batch mode, -i.e. when making -figures from scripts, interactive mode can be slow since it redraws -the figure with each command. So you may want to think carefully -before making this the default behavior via the :file:`matplotlibrc` -file instead of using the functions listed in the next section. - -Gui shells are at best problematic, because they have to run a -mainloop, but interactive plotting also involves a mainloop. Ipython -has sorted all this out for the primary matplotlib backends. There -may be other shells and IDEs that also work with matplotlib in interactive -mode, but one obvious candidate does not: -the python IDLE IDE is a Tkinter gui app that does -not support pylab interactive mode, regardless of backend. - -.. _controlling-interactive: - -Controlling interactive updating -================================ - -The *interactive* property of the pyplot interface controls whether a -figure canvas is drawn on every pyplot command. If *interactive* is -*False*, then the figure state is updated on every plot command, but -will only be drawn on explicit calls to -:func:`~matplotlib.pyplot.draw`. When *interactive* is -*True*, then every pyplot command triggers a draw. - - -The pyplot interface provides 4 commands that are useful for -interactive control. - -:func:`~matplotlib.pyplot.isinteractive` - returns the interactive setting *True|False* - -:func:`~matplotlib.pyplot.ion` - turns interactive mode on - -:func:`~matplotlib.pyplot.ioff` - turns interactive mode off - -:func:`~matplotlib.pyplot.draw` - forces a figure redraw - -When working with a big figure in which drawing is expensive, you may -want to turn matplotlib's interactive setting off temporarily to avoid -the performance hit:: - - - >>> #create big-expensive-figure - >>> ioff() # turn updates off - >>> title('now how much would you pay?') - >>> xticklabels(fontsize=20, color='green') - >>> draw() # force a draw - >>> savefig('alldone', dpi=300) - >>> close() - >>> ion() # turn updating back on - >>> plot(rand(20), mfc='g', mec='r', ms=40, mew=4, ls='--', lw=3) From bc4ecd5f5e854d1b763575f2c3c23f1e430ef334 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 28 Apr 2020 18:08:17 -0400 Subject: [PATCH 13/36] DOC: fix and sort intersphinx --- doc/conf.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c7fd1922c8c9..dace4fc28982 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -114,17 +114,15 @@ def _check_dependencies(): missing_references_warn_unused_ignores = False intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), + 'Pillow': ('https://pillow.readthedocs.io/en/stable/', None), 'cycler': ('https://matplotlib.org/cycler', None), 'dateutil': ('https://dateutil.readthedocs.io/en/stable/', None), + 'ipykernel': ('https://ipykernel.readthedocs.io/en/latest/', None), 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), - 'Pillow': ('https://pillow.readthedocs.io/en/stable/', None), 'pytest': ('https://pytest.org/en/stable', None), + 'python': ('https://docs.python.org/3', None), 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), - 'cycler': ('https://matplotlib.org/cycler', None), - 'ipympl': ('https://matplotlib.org/ipympl', None), - 'ipywidget': ('', None), } From 49f49fa117cc78b8b5ffb154e2a9a2a5b7d1a9c4 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 29 Apr 2020 11:08:30 -0400 Subject: [PATCH 14/36] DOC: re-write pyplot.show docstring --- lib/matplotlib/pyplot.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 6c99c51df4a5..57b78b013c30 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -307,22 +307,31 @@ def draw_if_interactive(*args, **kwargs): # This function's signature is rewritten upon backend-load by switch_backend. def show(*args, **kwargs): """ - Display all figures. + Display all open figures. - When running in ipython with its pylab mode, display all - figures and return to the ipython prompt. + In non-interactive mode, **block** defaults to True. All figures + will display and show will not return until all widows are closed. + If there are no figures, return immediately. - In non-interactive mode, display all figures and block until - the figures have been closed; in interactive mode it has no - effect unless figures were created prior to a change from - non-interactive to interactive mode (not recommended). In - that case it displays the figures but does not block. + In interactive mode **block** defaults to False. This will ensure + that all of the figures are shown and immediately return. Parameters ---------- block : bool, optional - This is experimental, and may be set to ``True`` or ``False`` to - override the blocking behavior described above. + + If `True` block and run the GUI main loop until all windows + are closed. + + If `False` ensure that all windows are displayed and return + immediately. In this case, you are responsible for ensuring + that the event loop is running to have responsive figures. + + See Also + -------- + ion : enable interactive mode + ioff : disable interactive mode + """ _warn_if_gui_out_of_main_thread() return _backend_mod.show(*args, **kwargs) From 295d71d36850c80a09dd02e0e2d18cee530585e1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 29 Apr 2020 11:31:02 -0400 Subject: [PATCH 15/36] DOC: plt.pause is no longer experimental --- lib/matplotlib/pyplot.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 57b78b013c30..ffa266c285ee 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -363,11 +363,6 @@ def pause(interval): This can be used for crude animation. For more complex animation, see :mod:`matplotlib.animation`. - - Notes - ----- - This function is experimental; its behavior may be changed or extended in a - future release. """ manager = _pylab_helpers.Gcf.get_active() if manager is not None: From c3c501367c240dc9958b29585c82f084270fb464 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 29 Apr 2020 20:37:10 -0400 Subject: [PATCH 16/36] DOC: update pyplot documentation - ion - ioff - isinteractive - pause --- lib/matplotlib/pyplot.py | 49 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ffa266c285ee..38b5b2d6b45d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -338,31 +338,70 @@ def show(*args, **kwargs): def isinteractive(): - """Return whether to redraw after every plotting command.""" + """ + Return if pyplot is in "interactive mode" or not. + + If in interactive mode then: + + - newly created figures will be shown immediately + - figures will automatically redraw on change + - pyplot.show will not block by default + + If not in interactive mode then: + + - newly created figures and changes to figures will + not be reflected until explicitly asked to be + - pyplot.show will block by default + + See Also + -------- + ion : enable interactive mode + ioff : disable interactive mode + """ return matplotlib.is_interactive() def ioff(): - """Turn the interactive mode off.""" + """ + Turn the interactive mode off. + + See Also + -------- + ion : enable interactive mode + isinteractive : query current state + + """ matplotlib.interactive(False) uninstall_repl_displayhook() def ion(): - """Turn the interactive mode on.""" + """ + Turn the interactive mode on. + + See Also + -------- + ioff : disable interactive mode + isinteractive : query current state + """ matplotlib.interactive(True) install_repl_displayhook() def pause(interval): """ - Pause for *interval* seconds. + Run the GUI event loop or sleep for *interval* seconds and . If there is an active figure, it will be updated and displayed before the pause, and the GUI event loop (if any) will run during the pause. - This can be used for crude animation. For more complex animation, see + This can be used for crude animation. For more complex animation use :mod:`matplotlib.animation`. + + See Also + -------- + matplotlib.animation : Complex animation + show : show figures and optional block forever """ manager = _pylab_helpers.Gcf.get_active() if manager is not None: From d9df05757e219b90a733d75f1b54a12c23cce77b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 29 Apr 2020 20:37:42 -0400 Subject: [PATCH 17/36] DOC: Lots of editing to interactive figure documentation --- doc/users/interactive.rst | 328 +++++++++++++++----------- doc/users/interactive_guide.rst | 398 +++++++++++++++++--------------- 2 files changed, 400 insertions(+), 326 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 6e58b4095996..955f7ac0667c 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -2,64 +2,72 @@ .. _mpl-shell: -=================== - Interactive plots -=================== +===================== + Interactive Figures +===================== .. toctree:: - :maxdepth: 2 - interactive_guide - event_handling.rst +When working with data it is often invaluable to be able to +interact with your plots In many cases the built in pan/zoom +and mouse-location tools are sufficient, but if they are not +you can make use of the Matplotlib event system to build a customized +data exploration tool. -By default, matplotlib defers drawing until explicitly asked. Drawing -may be an expensive operation and some changes to the figure may leave -it in an inconsistent state, hence we only want to render the figure -just before the figure is displayed on the screen or saved to disk. -In the case of scripts this is easy to arrange, we simple do nothing -until ``savefig`` is called. However, when working interactively, -either at a terminal or in a notebook, we would like the figure to -automatically re-render when we change. Further, we would like the -figures respond to user input (mouse and keyboard) to interact with -and explore the data. +Matplotlib ships with :ref:`backends ` binding to +several GUI toolkits (Qt, Tk, Wx, Gtk, OSX, js) and third party +packages provide bindings to `kivy +`__ and `Jupyter Lab +`__. For the figures to be +"live" the GUI event loop will need to be integrated with your prompt, the +simplest way is to use IPython (see :ref:`below `). -To get this functionality we need a combination of an interactive -backend (to handle the user interaction with the figure) and an -interactive interpreter (to handle the input of user code). Together -these tools need to: +The `.pyplot` module provides two functions for creating Figures that, +when an interactive backend is used, that are managed by Matplotlib, +embedded in GUI windows, and ready for interactive use out of the box -- Take text from the user and execute it in the Python interpreter -- Ensure that any created figures are put on the screen -- Trigger a re-render of the figure when the user has mutated it -- When not actively executing user input, run the event loop so we can - get user input on the figure (see - :ref:`interactive_figures_and_eventloops` for details) +`.pyplot.figure` + Creates a new empty `.figure.Figure` or selects an existing figure +`.pyplot.subplots` + Creates a new `.figure.Figure` and fills it with a grid of `.axes.Axes` +Matplotlib keeps a reference to all of the open figures created this +way so they will not be garbage collected. You can close all off your +open figures via ``plt.close('all')``. -.. _ipython-pylab: +For discussion of how this works under the hood see: + +.. toctree:: + :maxdepth: 1 + + interactive_guide.rst + event_handling.rst -IPython to the rescue! -====================== -To support interactive +.. _ipython-pylab: + +IPython integration +=================== We recommend using IPython for an interactive shell. In addition to all of it's features (improved tab-completion, magics, -multiline-editing, etc), it also ensures that the GUI toolkit is -properly integrated with the command line (see :ref:`cp_integration`). -To configure the integration and enable interactive mode do:: +multiline-editing, etc), it also ensures that the GUI toolkit event +loop is properly integrated with the command line (see +:ref:`cp_integration`). To configure the integration and enable +:ref:`interactive mode ` use the +``%matplotlib`` magic .. sourcecode:: ipython user@machine:~ $ ipython - Python 3.6.4 (default, Dec 23 2017, 19:07:07) + Python 3.8.2 (default, Apr 8 2020, 14:31:25) Type 'copyright', 'credits' or 'license' for more information - IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help. + IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: %matplotlib - iUsing matplotlib backend: Qt5Agg + Using matplotlib backend: Qt5Agg In [2]: import matplotlib.pyplot as plt @@ -84,149 +92,102 @@ want to change anything about the line, ex the color In [5]: ln.set_color('orange') -will be reflected immediately. - - +will be reflected immediately. If you wish to disable this behavior +you can use -.. _other-shells: - -Other Python interpreters -========================= - -If you can not or do not want to use IPython, interactive mode -should work in the vanilla python prompt - - -.. sourcecode:: python - - user@machine:~ $ python - Python 3.6.4 (default, Dec 23 2017, 19:07:07) - [GCC 7.2.1 20171128] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> import matplotlib.pyplot as plt - >>> plt.ion() - >>> - -however this does not ensure that the event hook is properly installed -and your figures may not be responsive (see :ref:`cp_integration`). - -GUI python prompts may or may not work with all backends. For -example, if you use the IDLE IDE you must use the 'tkagg' backend (as -IDLE is a Tk application.. However, if you use spyder, the -interactive shell is run in a sub process of the GUI and you can use -any backend. +.. sourcecode:: ipython + In [6]: plt.ioff() -PyCharm -------- +and -TODO: links to pycharm docs on configuring Matplotlib backends +.. sourcecode:: ipython -Spyder ------- + In [7]: plt.ion() -TODO: links to spyder docs on configuring Matplotlib backends +re-enable it. -VSCode ------- +With recent versions of ``Matplotlib`` and ``IPython`` it is +sufficient to import `matplotlib.pyplot` and call `.pyplot.ion`, but +using the magic is guaranteed to work regardless of versions. -TODO: links to vscode docs on configuring Matplotlib backends (?) -Jupyter Notebooks / Jupyter Lab -------------------------------- -.. warning:: +.. _controlling-interactive: - To get the interactive functionality described here, you must be - using an interactive backend, however he 'inline' backend is not. - It renders the figure once and inserts a static image into the - notebook everytime the cell in run. Being static these plots can - not be panned / zoomed or take user input. +Interactive mode +================ -Jupyter uses a different architecture than a traditional interactive -terminal. Rather than the user interface running in the same process -as your interpreter, the user interacts with a javascript front end -running in a browser which communicates with a server which in turn -communicates with a kernel that actually runs the python. This means -for interactive figures our UI needs to also be written in javascript -and run inside of the jupyter front end. -To get interactive figures in the 'classic' notebook or jupyter lab use -the `ipympl` backend (must be installed separately) which uses the `ipywidget` -framework and which enabled by using :: +.. autosummary:: + :template: autosummary.rst + :nosignatures: - %matplotlib widget + pyplot.ion + pyplot.ioff + pyplot.isinteractive -If you only need to use the classic notebook you can also use :: - %matplotlib notebook +.. autosummary:: + :template: autosummary.rst + :nosignatures: -which uses the `nbagg` backend which ships with Matplotlib (but does -not work with JLab because, for security reasons, you can no longer -inject arbitrary javascript into the front end). + pyplot.show + pyplot.pause -GUIs + jupyter -~~~~~~~~~~~~~~ -If you are running your jupyter server locally you can use one of the -GUI backends, however if you ever move that notebook to a remote -server it will cease to work correctly. What is happening is that -when you create a figure the process running your kernel creates and -shows a GUI window. If that process is on the same computer as you, -then you will see it, however if it is running on a remote computer it -will try to open the GUI window on _that_ computer. This will either -fail by raising an exception (as many linux servers do not have an -XServer running) or run cleanly, but leave you with no way to access -your figure. +Interactive mode controls: +- if created figures are automatically shown +- if changes to artists automatically trigger re-drawing existing figures +- if `.pyplot.show` blocks or not +if in interactive mode then: -.. _controlling-interactive: +- newly created figures will be shown immediately +- figures will automatically redraw on change +- pyplot.show will not block by default -Controlling interactive updating -================================ +If not in interactive mode then: +- newly created figures and changes to figures will + not be reflected until explicitly asked to be +- pyplot.show will block by default -To control and query the current state of *interactive* mode -.. autosummary:: - :template: autosummary.rst - :nosignatures: +If you are in non-interactive mode (or created figures while in +non-interactive mode) you may need to explicitly call `.pyplot.show` +to bring the windows onto your screen. If you only want to run the +GUI event loop for a fixed amount of time you can use `.pyplot.pause`. - pyplot.ion - pyplot.ioff - pyplot.isinteractive +Being in interactive mode is orthogonal to the GUI event loop being +integrated with your command prompt. If you have the GUI event loop +integrated with your prompt, then shown figures will be "live" while +the prompt is waiting for input, if it is not integrated than your +figures will only be "live" when the GUI event loop is running (via +`.pyplot.show`, `.pyplot.pause`, or explicitly starting the GUI main +loop). -When working with a big figure in which drawing is expensive, you may -want to turn matplotlib's interactive setting off temporarily to avoid -the performance hit:: +.. warning - >>> #create big-expensive-figure - >>> plt.ioff() # turn updates off - >>> ax.set_title('now how much would you pay?') - >>> fig.canvas.draw_idle() # force a draw - >>> fig.savefig('alldone', dpi=300) - >>> plt.close('all') - >>> plt.ion() # turn updating back on - >>> fig, ax = plt.subplots() - >>> ax.plot(rand(20), mfc='g', mec='r', ms=40, mew=4, ls='--', lw=3) + Using `.figure.Figure.show` it is possible to display a figure on + the screen without starting the event loop and without being in + interactive mode. This may work (depending on the GUI toolkit) but + will likely result in a non-responsive figure. +.. _navigation-toolbar: Default UI ========== -.. toctree:: - :maxdepth: 1 - - navigation_toolbar.rst - +The windows created by :mod:`~.pyplot` have an interactive toolbar with navigation +buttons and a readout of where the cursor is in dataspace. A number of +helpful keybindings are registered by default. -The windows created by :mod:`~.pyplot` have an interactive toolbar and -has a number of helpful keybindings by default. .. _key-event-handling: @@ -256,3 +217,90 @@ Toggle minor grids **G** when mouse is over an axes Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes Toggle y axis scale (log/linear) **l** when mouse is over an axes ================================== ================================================= + + +.. _other-shells: + +Other Python prompts +==================== + +If you can not or do not want to use IPython, interactive mode works +in the vanilla python prompt + + +.. sourcecode:: python + + >>> import matplotlib.pyplot as plt + >>> plt.ion() + >>> + +however this does not ensure that the event hook is properly installed +and your figures may not be responsive. Please consult the +documentation of your GUI toolkit for details. + + + +Jupyter Notebooks / Lab +----------------------- + +.. note:: + + To get the interactive functionality described here, you must be + using an interactive backend. The default backend in notebooks, + the inline backend, is not. `~ipykernel.pylab.backend_inline` + renders the figure once and inserts a static image into the + notebook when the cell is executed. The images are static and can + not be panned / zoomed, take user input, or be updated from other + cells. + +Jupyter uses a different architecture than a traditional interactive +terminal. Rather than the user interface running in the same process +as your interpreter, the user interacts with a javascript front end +running in a browser which communicates with a server which in turn +communicates with a kernel that executes your Python. This means +for interactive figures our UI needs to also be written in javascript +and run inside of the jupyter front end. + +To get interactive figures in the 'classic' notebook or jupyter lab +use the `ipympl `__ backend +(must be installed separately) which uses the **ipywidget** framework. +If ``ipympl`` is installed use the magic: + +.. sourcecode:: ipython + + %matplotlib widget + +to select and enable it. + +If you only need to use the classic notebook you can use + +.. sourcecode:: ipython + + %matplotlib notebook + +which uses the `.backend_nbagg` backend which ships with Matplotlib. +However nbagg does not work in Jupyter Lab due to changes in the front +end. + +GUIs + jupyter +~~~~~~~~~~~~~~ + +If you are running your jupyter server locally you can use one of the +GUI backends. However if you ever move that notebook to a remote +server it will cease to work correctly because the GUI windows will be +created on the server, not your machine. When you create a figure the +process running your kernel creates and shows a GUI window. If that +process is on the same computer as your client, then you will be able +to see and interact with the window. However if it is running on a +remote computer it will try to open the GUI window on _that_ computer. +This will either fail by raising an exception (as many servers +do not have an XServer running) or run, but leave you with no +way to access your figure. + + + +PyCharm, Spydter, and VSCode +---------------------------- + +Many IDEs have built-in integration with Matplotlib, please consult it's +documentation for configuration details. diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 4da597fc800a..b0619db5d42d 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -10,13 +10,14 @@ Matplotlib supports rich interactive figures by embedding figures into a GUI window. The basic interactions of panning and zooming in as Axis to inspect your data is 'baked in' to Matplotlib. This is -supported by a full mouse and keyboard event handling system to enable -you to build sophisticated interactive graphs. +supported by a full mouse and keyboard event handling system that +you can use to build sophisticated interactive graphs. This is meant to be an introduction to the low-level details of how integrating the Matplotlib with a GUI event loop works. For a more -practical introduction the Matplotlib event API see `Interactive -Tutorial `__ and +practical introduction the Matplotlib event API see :ref:`event +handling system `, `Interactive Tutorial +`__, and `Interactive Applications using Matplotlib `__. @@ -43,31 +44,30 @@ is waiting on some sort of I/O -- be it user input or the network -- while the *Evaluate* and *Print* are responsible for 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 to be -called in response to specific event. For example "when the user -clicks on this button, please run this function" or "when the user -hits the 'z' key, please run this other function". This allows users -to write reactive, event-driven, programs without having to delve into -the nity-gritty [#f2]_ details of I/O. Examples of this pattern the -``Signal`` / ``Slot`` framework in Qt (and the analogs in other GUI -frameworks), the 'request' functions in flask, and Matplotlib's -:ref:`event handling system `. - -All GUI frameworks (Qt, Wx, Gtk, tk, OSX, or web) have some method of -capturing user interactions and passing them back to the application, -although the exact details vary. Matplotlib has a :ref:`backnd -` for each GUI framework which use these mechanisms -to convert the native UI events into Matplotlib events so we can -develop framework-independent interactive figures. +In practice we interact with a framework that provides a mechanism to +register callbacks to be called in response to specific event rather +than directly implement the I/O loop [#f2]_. For example "when the +user clicks on this button, please run this function" or "when the +user hits the 'z' key, please run this other function". This allows +users to write reactive, event-driven, programs without having to +delve into the nity-gritty [#f3]_ details of I/O. The core event loop +is sometimes referred to as "the main loop" and is typically started, +depending on the library, by methods with names like ``_exec``, +``run``, or ``start``. -references to trackdown: - - 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 +All GUI frameworks (Qt, Wx, Gtk, tk, OSX, or web) have some method of +capturing user interactions and passing them back to the application +(for example ``Signal`` / ``Slot`` framework in Qt) but the exact +details depend on the toolkit. Matplotlib has a :ref:`backend +` for each GUI toolkit we support which use the +toolkit API to bridge the toolkit UI events into Matplotlib events +into Matplotlib's :ref:`event handling system +`. You can then use +`.FigureCanvasBase.mpl_connect` to connect your function to +Matplotlib's event handling system. This allows you to directly +interact with your data and write GUI toolkit agnostic user +interfaces. .. _cp_integration: @@ -75,55 +75,27 @@ references to trackdown: Command Prompt Integration ========================== -Integrating a GUI window into a CLI introduces a conflict: there are -two infinite loops that want to be waiting for user input on the main -thread in the same process. Python wants to be blocking the main -thread it it's REPL loop and the GUI framework wants to be running -it's event loop. In order for both the prompt and the GUI widows to -be responsive we need a method to allow the loops to 'timeshare' : +So far, so good. We have the REPL (like the IPthon terminal) that +lets us interactively send things code to the interpreter and get +results back. We also have the GUI toolkit that run an event loop +waiting for user input and let up register functions to be run when +that happens. However, if we want to do both we have a problem: the +prompt and the GUI event loop are both infinite loops that each think +*they* are in charge! In order for both the prompt and the GUI widows +to be responsive we need a method to allow the loops to 'timeshare' : 1. let the GUI main loop block the python process when you want interactive windows 2. let the CLI main loop block the python process and intermittently run the GUI loop +3. fully embed python in the GUI (but this is basically writing a full + application) -The inverse problem, embedding an interactive prompt into a GUI, is -also a way out of this problem by letting the GUI event loop always -run the show, but that is essentially writing a full application. - +.. _cp_block_the_prompt: Blocking the Prompt ------------------- -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 -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 loop will make any open figure responsive again. - - -To start the event loop until all open figures are closed use -`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 without explicitly starting the event loop and not being in - interactive mode. This may work but will likely result in a - non-responsive figure and may not even display the rendered plot. - - -This technique can be very useful if you want to write a script that -pauses for user interaction, see :ref:`interactive_script`. - .. autosummary:: :template: autosummary.rst :nosignatures: @@ -134,66 +106,37 @@ pauses for user interaction, see :ref:`interactive_script`. backend_bases.FigureCanvasBase.start_event_loop backend_bases.FigureCanvasBase.stop_event_loop - figure.Figure.show - - -Explicitly running the Event Loop ---------------------------------- - -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 run the GUI event -loop, until all UI events currently waiting have been processed. The -exact behavior is backend-dependent but typically events on all figure -are processed and only events waiting to be processed (not those added -during processing) will be handled. - -For example :: - - import time - import matplotlib.pyplot as plt - import numpy as np - plt.ion() - - fig, ax = plt.subplots() - fig.canvas.show() - th = np.linspace(0, 2*np.pi, 512) - ax.set_ylim(-1.5, 1.5) - ln, = ax.plot(th, np.sin(th)) - - def slow_loop(N, ln): - for j in range(N): - time.sleep(.1) # to simulate some work - ln.figure.canvas.flush_events() - - slow_loop(100, ln) - -Will be a bit laggy, as we are only processing user input every 100ms -(where as 20-30ms is what feels "responsive"), but it will respond. - - -If you make changes to the plot and want it re-rendered you will need -to call `~.FigureCanvasBase.draw_idle()` to request that the canvas be -re-drawn. This method can be thought of *draw_soon* in analogy to -`asyncio.BaseEventLoop.call_soon`. - -We can add this our example above as :: - - def slow_loop(N, ln): - for j in range(N): - time.sleep(.1) # to simulate some work - if j % 10: - ln.set_ydata(np.sin(((j // 10) % 5 * th))) - ln.figure.canvas.draw_idle() +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 echo the charters typed into standard in, but they will not be +sent to the Python interpreter because it is busy running the GUI +event loop), 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 loop will make any open figure responsive again (and will +process and queued up user interaction). - ln.figure.canvas.flush_events() +To start the event loop until all open figures are closed use +`.pyplot.show` as ``pyplot.show(block=True)``. To start the event +loop for a fixed amount of time (in seconds) use `.pyplot.pause`. - slow_loop(100, ln) +If you are not using `.pyplot` you can start and stop the event loops +via `.FigureCanvasBase.start_event_loop` and +`.FigureCanvasBase.stop_event_loop`. However, in most contexts where +you would not be using `.pyplot` you are embedding Matplotlib in a +large GUI application and the GUI event loop should already be running +for the application. +Away from the prompt, this technique can be very useful if you want to +write a script that pauses for user interaction, see +:ref:`interactive_scripts`. +.. _spin_event_loop: +Explicitly spinning the Event Loop +---------------------------------- .. autosummary:: :template: autosummary.rst @@ -203,8 +146,13 @@ We can add this our example above as :: backend_bases.FigureCanvasBase.draw_idle -Interactive ------------ + +This is particularly useful if you want to provide updates to a plot +during a long computation. + + +Input Hook integration +---------------------- 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 @@ -230,19 +178,21 @@ 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 -`. In this case it is your code, not the REPL, which is -blocking process so you need to handle the time-share manually. +`. 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. +finishes drawing. 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 -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. +(and a `Python interpreter +`__) in a rich +native application. Matplotlib provides classes for each toolkit +which can be directly embedded in GUI applications (this is how the +built-in windows are implemented!). See :ref:`user_interfaces` for +more details. .. _interactive_scripts : @@ -250,29 +200,86 @@ on how to do this. Scripts ======= + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + backend_bases.FigureCanvasBase.flush_events + backend_bases.FigureCanvasBase.draw_idle + + figure.Figure.ginput + pyplot.ginput + 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 -In the first case, it is the same as :ref:`above -` where you explicitly call :: - fig.canvas.flush_events() +In the first if you only need to collect points in an Axes you can use +`.figure.Figure.ginput` or more generally the tools from +`.blocking_input` the tools will take care of starting and stopping +the event loop for you. However if you have written some custom event +handling or are using `.widgets` you will need to manually run the GUI +event loop using the methods described :ref:`above `. -periodically to allow the event loop to process UI and draw events and -:: +In the second caes, if you have open windows that have pending UI +events (mouse clicks, button presses, or draws) you can explicitly +process those events by calling `.FigureCanvasBase.flush_events`. +This will run the GUI event loop until all UI events currently waiting +have been processed. The exact behavior is backend-dependent but +typically events on all figure are processed and only events waiting +to be processed (not those added during processing) will be handled. - fig.canvas.draw_idle() +For example :: -when you have updated the contents of the figure. + import time + import matplotlib.pyplot as plt + import numpy as np + plt.ion() -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. + fig, ax = plt.subplots() + fig.canvas.show() + th = np.linspace(0, 2*np.pi, 512) + ax.set_Lima(-1.5, 1.5) -The second case is very much like :ref:`Blocking` above. By using ``plt.show(block=True)`` or + ln, = ax.plot(th, np.sin(th)) + + def slow_loop(N, ln): + for j in range(N): + time.sleep(.1) # to simulate some work + ln.figure.canvas.flush_events() + + slow_loop(100, ln) + +While this will feel a bit laggy (as we are only processing user input +every 100ms where as 20-30ms is what feels "responsive") it will +respond. + +If you make changes to the plot and want it re-rendered you will need +to call `~.FigureCanvasBase.draw_idle` to request that the canvas be +re-drawn. This method can be thought of *draw_soon* in analogy to +`asyncio.loop.call_soon`. + +We can add this our example above as :: + + def slow_loop(N, ln): + for j in range(N): + time.sleep(.1) # to simulate some work + if j % 10: + ln.set_ydata(np.sin(((j // 10) % 5 * th))) + ln.figure.canvas.draw_idle() + + ln.figure.canvas.flush_events() + + slow_loop(100, ln) + + +The more frequently you call `.FigureCanvasBase.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 third case you will have to integrate updating the ``Aritist`` instances, calling ``draw_idle``, and flushing the GUI event loop with your @@ -283,27 +290,57 @@ data I/O. Stale Artists ============= -Artists (as of 1.5) have a ``stale`` attribute which is `True` if the -internal state of the artist has changed since the last time it was -drawn to the screen. The stale state is propagated up to the Artists -parents in the draw tree. Thus, ``fig.stale`` will report of any -artist in the figure has been modified and out of sync with what is -displayed on the screen. This is intended to be used to determine if -``draw_idle`` should be called to schedule a re-rendering of the -figure. +Artists (as of Matplotlib 1.5) have a **stale** attribute which is +`True` if the internal state of the artist has changed since the last +time it was rendered. By default the stale state is propagated up to +the Artists parents in the draw tree, thus if the color of a `.Line2D` +instance is changed, the `.axes.Axes` and `.figure.Figure` it is +contained in will also be marked as "stale". Thus, ``fig.stale`` will +report of any artist in the figure has been modified and out of sync +with what is displayed on the screen. This is intended to be used to +determine if ``draw_idle`` should be called to schedule a re-rendering +of the figure. + +Each artist has a `.Artist.stale_callback` attribute which holds a callback +with the signature :: + + def callback(self: Artist, val: bool) -> None: + ... + +which by default is set to a function that forwards the stale state to +the artists parent. If you wish to suppress a given artist from propagating +set this attribute to None. + +`.figure.Figure` instances do not have a containing artist and their +default callback is `None`. If you call ``.pyplot.ion` and are not in +``IPython`` we will install callback to invoke +`~.backend_bases.FigureCanvasBase.draw_idle` when ever the +`.figure.Figure` becomes stale. In ``IPython`` we use the +``'post_execute'`` hook to invoke +`~.backend_bases.FigureCanvasBase.draw_idle` on any stale figures +after having executed the users input, but before returning the prompt +to the user. If you are not using `.pyplot` you can use the callback +`Figure.stale_callback` attribute to be notified when a figure has +become stale. + + +.. _draw_idle: -TODO: +Draw Idle +========= -- notes about callbacks -- +.. autosummary:: + :template: autosummary.rst + :nosignatures: + backend_bases.FigureCanvasBase.draw + backend_bases.FigureCanvasBase.draw_idle + backend_bases.FigureCanvasBase.flush_events -Draw Idle -========= In almost all cases, we recommend using -`backend_bases.FigureCanvasBae.draw_idle` over -`backend_bases.FigureCanvasBae.draw`. ``draw`` forces a rendering of +`backend_bases.FigureCanvasBase.draw_idle` over +`backend_bases.FigureCanvasBase.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 @@ -314,31 +351,19 @@ you want to be sure that the screen is updated as soon as possible do :: -.. 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 -=== +Most GUI frameworks require that all updates to the screen, and hence +their main event loop, run on the main thread. This makes pushing +periodic updates of a plot to a background thread impossible. +Although it seems backwards, it is typically easier to push your +computations to a background thread and periodically update +the figure on the main thread. +In general Matplotlib is not thread safe. If you are going to update +`.Artist` objects in one thread and draw from another you should make +sure that you are locking in the critical sections. The Weeds @@ -351,7 +376,7 @@ Eventloop integration mechanism CPython / readline ~~~~~~~~~~~~~~~~~~ -The python capi provides a hook, `PyOS_InputHook`, to register a +The python capi provides a hook, :c:var:`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 @@ -361,12 +386,12 @@ 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. +Matplotlib does not currently do any management of +:c:var:`PyOS_InputHook` due to the wide range of ways that matplotlib +is used. This management is left to downstream libraries -- either +user code or the shell. Interactive figures, even with matplotlib in +'interactive mode', may not work in the vanilla python repl if an +appropriate :c:var:`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. @@ -375,7 +400,6 @@ Matplotlib supports which can be installed via ``%matplotlib``. This is the recommended method of integrating Matplotlib and a prompt. - IPython / prompt toolkit ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -399,8 +423,10 @@ method. The source for the prompt_toolkit input hooks lives at inp = select(fds).read() # Read eval(inp) # Evaluate / Print +.. [#f2] Or you can `write your own + `__ if you must. -.. [#f2] These examples are agressively dropping many of the +.. [#f3] These examples are agressively dropping many of the complexities that must be dealt with in the real world such as - keyboard interupts [link], timeouts, bad input, resource + keyboard interupts, timeouts, bad input, resource allocation and cleanup, etc. From 7b0b32785d3deb81ecb60b2aa64b85db0b697087 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 30 Apr 2020 01:19:48 -0400 Subject: [PATCH 18/36] DOC: it's -> its --- doc/users/interactive.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 955f7ac0667c..19a12b9133f3 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -52,7 +52,7 @@ IPython integration =================== We recommend using IPython for an interactive shell. In addition to -all of it's features (improved tab-completion, magics, +all of its features (improved tab-completion, magics, multiline-editing, etc), it also ensures that the GUI toolkit event loop is properly integrated with the command line (see :ref:`cp_integration`). To configure the integration and enable @@ -302,5 +302,5 @@ way to access your figure. PyCharm, Spydter, and VSCode ---------------------------- -Many IDEs have built-in integration with Matplotlib, please consult it's +Many IDEs have built-in integration with Matplotlib, please consult its documentation for configuration details. From 5a426d6772b78eba8c4367abce340e94f58241f2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 30 Apr 2020 01:21:22 -0400 Subject: [PATCH 19/36] DOC: spelling --- doc/users/interactive_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index b0619db5d42d..74430ce76e0c 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -412,7 +412,7 @@ method. The source for the prompt_toolkit input hooks lives at -.. rubric:: Fotenotes +.. rubric:: Footnotes .. [#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 From 8ab0a651d14690823c3a93a90ea88f349845a9e3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 1 May 2020 00:27:17 -0400 Subject: [PATCH 20/36] DOC: edits from review --- doc/users/interactive.rst | 43 ++++++-------- doc/users/interactive_guide.rst | 100 +++++++++++++++++++------------- 2 files changed, 79 insertions(+), 64 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 19a12b9133f3..888cf2464547 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -9,11 +9,10 @@ .. toctree:: -When working with data it is often invaluable to be able to -interact with your plots In many cases the built in pan/zoom -and mouse-location tools are sufficient, but if they are not -you can make use of the Matplotlib event system to build a customized -data exploration tool. +When working with data it is often invaluable to be able to interact +with your plots In many cases the built in pan/zoom and mouse-location +tools are sufficient, but you can also use the Matplotlib event system +to build a customized data exploration tools. Matplotlib ships with :ref:`backends ` binding to several GUI toolkits (Qt, Tk, Wx, Gtk, OSX, js) and third party @@ -59,7 +58,9 @@ loop is properly integrated with the command line (see :ref:`interactive mode ` use the ``%matplotlib`` magic -.. sourcecode:: ipython +.. highlight:: ipython + +:: user@machine:~ $ ipython Python 3.8.2 (default, Apr 8 2020, 14:31:25) @@ -75,33 +76,33 @@ loop is properly integrated with the command line (see Calling -.. sourcecode:: ipython +:: In [3]: fig, ax = plt.subplots() will pop open a window for you and -.. sourcecode:: ipython +:: In [4]: ln, = ax.plot(range(5)) -will show your data in the window. If you -want to change anything about the line, ex the color +will show your data in the window. If you change something about the +line, for example the color -.. sourcecode:: ipython +:: In [5]: ln.set_color('orange') -will be reflected immediately. If you wish to disable this behavior -you can use +it will be reflected immediately. If you wish to disable this behavior +use -.. sourcecode:: ipython +:: In [6]: plt.ioff() and -.. sourcecode:: ipython +:: In [7]: plt.ion() @@ -112,7 +113,7 @@ sufficient to import `matplotlib.pyplot` and call `.pyplot.ion`, but using the magic is guaranteed to work regardless of versions. - +.. highlight:: python .. _controlling-interactive: @@ -228,7 +229,7 @@ If you can not or do not want to use IPython, interactive mode works in the vanilla python prompt -.. sourcecode:: python +.. sourcecode:: pycon >>> import matplotlib.pyplot as plt >>> plt.ion() @@ -253,14 +254,6 @@ Jupyter Notebooks / Lab not be panned / zoomed, take user input, or be updated from other cells. -Jupyter uses a different architecture than a traditional interactive -terminal. Rather than the user interface running in the same process -as your interpreter, the user interacts with a javascript front end -running in a browser which communicates with a server which in turn -communicates with a kernel that executes your Python. This means -for interactive figures our UI needs to also be written in javascript -and run inside of the jupyter front end. - To get interactive figures in the 'classic' notebook or jupyter lab use the `ipympl `__ backend (must be installed separately) which uses the **ipywidget** framework. diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 74430ce76e0c..259101d54863 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -8,8 +8,8 @@ ================================================== Matplotlib supports rich interactive figures by embedding figures into -a GUI window. The basic interactions of panning and zooming in as -Axis to inspect your data is 'baked in' to Matplotlib. This is +a GUI window. The basic interactions of panning and zooming in an +Axes to inspect your data is 'baked in' to Matplotlib. This is supported by a full mouse and keyboard event handling system that you can use to build sophisticated interactive graphs. @@ -50,7 +50,7 @@ than directly implement the I/O loop [#f2]_. For example "when the user clicks on this button, please run this function" or "when the user hits the 'z' key, please run this other function". This allows users to write reactive, event-driven, programs without having to -delve into the nity-gritty [#f3]_ details of I/O. The core event loop +delve into the nitty-gritty [#f3]_ details of I/O. The core event loop is sometimes referred to as "the main loop" and is typically started, depending on the library, by methods with names like ``_exec``, ``run``, or ``start``. @@ -75,13 +75,13 @@ interfaces. Command Prompt Integration ========================== -So far, so good. We have the REPL (like the IPthon terminal) that +So far, so good. We have the REPL (like the IPython terminal) that lets us interactively send things code to the interpreter and get results back. We also have the GUI toolkit that run an event loop waiting for user input and let up register functions to be run when that happens. However, if we want to do both we have a problem: the prompt and the GUI event loop are both infinite loops that each think -*they* are in charge! In order for both the prompt and the GUI widows +*they* are in charge! In order for both the prompt and the GUI windows to be responsive we need a method to allow the loops to 'timeshare' : 1. let the GUI main loop block the python process when you want @@ -110,17 +110,21 @@ Blocking the Prompt 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 echo the charters typed into standard in, but they will not be +may echo the characters typed into the terminal, but they will not be sent to the Python interpreter because it is busy running the GUI event loop), 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 loop will make any open figure responsive again (and will -process and queued up user interaction). +process any queued up user interaction). To start the event loop until all open figures are closed use -`.pyplot.show` as ``pyplot.show(block=True)``. To start the event -loop for a fixed amount of time (in seconds) use `.pyplot.pause`. +`.pyplot.show` as :: + + pyplot.show(block=True) + +To start the event loop for a fixed amount of time (in seconds) use +`.pyplot.pause`. If you are not using `.pyplot` you can start and stop the event loops via `.FigureCanvasBase.start_event_loop` and @@ -130,25 +134,9 @@ large GUI application and the GUI event loop should already be running for the application. Away from the prompt, this technique can be very useful if you want to -write a script that pauses for user interaction, see -:ref:`interactive_scripts`. - -.. _spin_event_loop: - -Explicitly spinning the Event Loop ----------------------------------- - -.. autosummary:: - :template: autosummary.rst - :nosignatures: - - backend_bases.FigureCanvasBase.flush_events - backend_bases.FigureCanvasBase.draw_idle - - - -This is particularly useful if you want to provide updates to a plot -during a long computation. +write a script that pauses for user interaction, or displays a figure +between polling for additional data. See :ref:`interactive_scripts` +for more details. Input Hook integration @@ -197,8 +185,8 @@ more details. .. _interactive_scripts : -Scripts -======= +Scripts and functions +===================== .. autosummary:: @@ -211,21 +199,58 @@ Scripts figure.Figure.ginput pyplot.ginput + pyplot.show + pyplot.pause + 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 +- progress updates as a long running script progresses - streaming updates from a data source +Blocking functions +------------------ -In the first if you only need to collect points in an Axes you can use +If you only need to collect points in an Axes you can use `.figure.Figure.ginput` or more generally the tools from `.blocking_input` the tools will take care of starting and stopping the event loop for you. However if you have written some custom event handling or are using `.widgets` you will need to manually run the GUI event loop using the methods described :ref:`above `. -In the second caes, if you have open windows that have pending UI +You can also use the methods described in :ref:`cp_block_the_prompt` +to suspend run the GUI event loop. Once the loop exits your code will +resume. In general, anyplace you would use `time.sleep` you can use +`.pyplot.pause` instead with the added benefit of interactive figures. + +For example, if you want to poll for data you could use something like :: + + fig, ax = plt.subplots() + ln, = ax.plot([], []) + + while True: + x, y = get_new_data() + ln.set_data(x, y) + fig.canvas.draw_idle() + plt.pause(1) + +which would poll for new data and update the figure at 1Hz. + +.. _spin_event_loop: + +Explicitly spinning the Event Loop +---------------------------------- + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + backend_bases.FigureCanvasBase.flush_events + backend_bases.FigureCanvasBase.draw_idle + + + +If you have open windows that have pending UI events (mouse clicks, button presses, or draws) you can explicitly process those events by calling `.FigureCanvasBase.flush_events`. This will run the GUI event loop until all UI events currently waiting @@ -243,7 +268,7 @@ For example :: fig, ax = plt.subplots() fig.canvas.show() th = np.linspace(0, 2*np.pi, 512) - ax.set_Lima(-1.5, 1.5) + ax.set_ylim(-1.5, 1.5) ln, = ax.plot(th, np.sin(th)) @@ -281,9 +306,6 @@ The more frequently you call `.FigureCanvasBase.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 third case you will have to integrate updating the ``Aritist`` -instances, calling ``draw_idle``, and flushing the GUI event loop with your -data I/O. .. _stale_artists: @@ -312,7 +334,7 @@ the artists parent. If you wish to suppress a given artist from propagating set this attribute to None. `.figure.Figure` instances do not have a containing artist and their -default callback is `None`. If you call ``.pyplot.ion` and are not in +default callback is `None`. If you call `.pyplot.ion` and are not in ``IPython`` we will install callback to invoke `~.backend_bases.FigureCanvasBase.draw_idle` when ever the `.figure.Figure` becomes stale. In ``IPython`` we use the @@ -405,7 +427,7 @@ 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 +has the same conceptual input hook, which is feed into prompt_toolkit via the :meth:`IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook` method. The source for the prompt_toolkit input hooks lives at :mod:`IPython.terminal.pt_inputhooks` From d70d5ad905bf0b71f74e3e2437bb0408b16e4fed Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 1 May 2020 00:59:40 -0400 Subject: [PATCH 21/36] DOC: update skipped references --- doc/missing-references.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/missing-references.json b/doc/missing-references.json index 53fae53d2ba0..382d8fe91ed1 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -1,4 +1,10 @@ { + "c:var": { + "PyOS_InputHook": [ + "doc/users/interactive_guide.rst:401", + "doc/users/interactive_guide.rst:411" + ] + }, "py:attr": { "ax": [ "lib/mpl_toolkits/axes_grid1/colorbar.py:docstring of mpl_toolkits.axes_grid1.colorbar.ColorbarBase:23" @@ -453,6 +459,9 @@ "FigureCanvasQTAgg.print_figure": [ "doc/api/prev_api_changes/api_changes_2.2.0.rst:199" ], + "IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook": [ + "doc/users/interactive_guide.rst:428" + ], "_find_tails": [ "lib/matplotlib/quiver.py:docstring of matplotlib.quiver.Barbs:9" ], @@ -521,6 +530,9 @@ ] }, "py:mod": { + "IPython.terminal.pt_inputhooks": [ + "doc/users/interactive_guide.rst:428" + ], "dateutil": [ "lib/matplotlib/dates.py:docstring of matplotlib.dates:1" ], @@ -562,6 +574,9 @@ "./gallery/index.html": [ "doc/devel/contributing.rst:562" ], + "Artist.stale_callback": [ + "doc/users/interactive_guide.rst:326" + ], "Artist.sticky_edges": [ "doc/api/axes_api.rst:362::1", "lib/matplotlib/axes/_axes.py:docstring of matplotlib.axes.Axes.use_sticky_edges:2" @@ -640,6 +655,9 @@ "doc/gallery/misc/ftface_props.rst:16", "lib/matplotlib/font_manager.py:docstring of matplotlib.font_manager.ttfFontProperty:8" ], + "Figure.stale_callback": [ + "doc/users/interactive_guide.rst:336" + ], "FigureCanvas": [ "lib/matplotlib/backend_tools.py:docstring of matplotlib.backend_tools.ToolBase:25" ], @@ -966,6 +984,9 @@ "invert_yaxis": [ "lib/mpl_toolkits/mplot3d/axes3d.py:docstring of mpl_toolkits.mplot3d.axes3d.Axes3D.get_ylim3d:24" ], + "ipykernel.pylab.backend_inline": [ + "doc/users/interactive.rst:249" + ], "kde.covariance_factor": [ "lib/matplotlib/mlab.py:docstring of matplotlib.mlab.GaussianKDE:41" ], From 757e0403760b507d5818bb773ccdddfeea117a8d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 3 May 2020 16:48:04 -0400 Subject: [PATCH 22/36] DOC: correct many spelling / grammer / clarity issues Co-authored-by: Elliott Sales de Andrade --- doc/devel/documenting_mpl.rst | 2 +- doc/users/interactive.rst | 20 ++++++------- doc/users/interactive_guide.rst | 53 ++++++++++++++++----------------- lib/matplotlib/pyplot.py | 4 +-- 4 files changed, 39 insertions(+), 40 deletions(-) diff --git a/doc/devel/documenting_mpl.rst b/doc/devel/documenting_mpl.rst index c7c02d7d4b99..551b25912d8a 100644 --- a/doc/devel/documenting_mpl.rst +++ b/doc/devel/documenting_mpl.rst @@ -307,7 +307,7 @@ Image files can directly included in pages with the ``image::`` directive. e.g., :file:`thirdpartypackages/index.rst` displays the images for the third-party packages as static images:: - .. image:: ../_static/toolbar.png + .. image:: /_static/toolbar.png as rendered on the page: :ref:`thirdparty-index`. diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 888cf2464547..88f0860ca0d1 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -10,16 +10,16 @@ When working with data it is often invaluable to be able to interact -with your plots In many cases the built in pan/zoom and mouse-location +with your plots. In many cases the built in pan/zoom and mouse-location tools are sufficient, but you can also use the Matplotlib event system -to build a customized data exploration tools. +to build customized data exploration tools. Matplotlib ships with :ref:`backends ` binding to several GUI toolkits (Qt, Tk, Wx, Gtk, OSX, js) and third party packages provide bindings to `kivy `__ and `Jupyter Lab `__. For the figures to be -"live" the GUI event loop will need to be integrated with your prompt, the +"live" the GUI event loop will need to be integrated with your prompt. The simplest way is to use IPython (see :ref:`below `). The `.pyplot` module provides two functions for creating Figures that, @@ -33,7 +33,7 @@ embedded in GUI windows, and ready for interactive use out of the box Creates a new `.figure.Figure` and fills it with a grid of `.axes.Axes` Matplotlib keeps a reference to all of the open figures created this -way so they will not be garbage collected. You can close all off your +way so they will not be garbage collected. You can close all of your open figures via ``plt.close('all')``. For discussion of how this works under the hood see: @@ -52,7 +52,7 @@ IPython integration We recommend using IPython for an interactive shell. In addition to all of its features (improved tab-completion, magics, -multiline-editing, etc), it also ensures that the GUI toolkit event +multiline editing, etc), it also ensures that the GUI toolkit event loop is properly integrated with the command line (see :ref:`cp_integration`). To configure the integration and enable :ref:`interactive mode ` use the @@ -140,12 +140,12 @@ Interactive mode Interactive mode controls: -- if created figures are automatically shown -- if changes to artists automatically trigger re-drawing existing figures -- if `.pyplot.show` blocks or not +- whether created figures are automatically shown +- whether changes to artists automatically trigger re-drawing existing figures +- whether `.pyplot.show` blocks or not -if in interactive mode then: +If in interactive mode, then: - newly created figures will be shown immediately - figures will automatically redraw on change @@ -166,7 +166,7 @@ GUI event loop for a fixed amount of time you can use `.pyplot.pause`. Being in interactive mode is orthogonal to the GUI event loop being integrated with your command prompt. If you have the GUI event loop integrated with your prompt, then shown figures will be "live" while -the prompt is waiting for input, if it is not integrated than your +the prompt is waiting for input, if it is not integrated then your figures will only be "live" when the GUI event loop is running (via `.pyplot.show`, `.pyplot.pause`, or explicitly starting the GUI main loop). diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 259101d54863..cb524b13428c 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -13,9 +13,9 @@ Axes to inspect your data is 'baked in' to Matplotlib. This is supported by a full mouse and keyboard event handling system that you can use to build sophisticated interactive graphs. -This is meant to be an introduction to the low-level details of how -integrating the Matplotlib with a GUI event loop works. For a more -practical introduction the Matplotlib event API see :ref:`event +This guide is meant to be an introduction to the low-level details of +how Matplotlib integration with a GUI event loop works. For a more +practical introduction to the Matplotlib event API see :ref:`event handling system `, `Interactive Tutorial `__, and `Interactive Applications using Matplotlib @@ -45,7 +45,7 @@ while the *Evaluate* and *Print* are responsible for interpreting the input and then **doing** something about it. In practice we interact with a framework that provides a mechanism to -register callbacks to be called in response to specific event rather +register callbacks to be run in response to specific events rather than directly implement the I/O loop [#f2]_. For example "when the user clicks on this button, please run this function" or "when the user hits the 'z' key, please run this other function". This allows @@ -60,10 +60,9 @@ All GUI frameworks (Qt, Wx, Gtk, tk, OSX, or web) have some method of capturing user interactions and passing them back to the application (for example ``Signal`` / ``Slot`` framework in Qt) but the exact details depend on the toolkit. Matplotlib has a :ref:`backend -` for each GUI toolkit we support which use the -toolkit API to bridge the toolkit UI events into Matplotlib events -into Matplotlib's :ref:`event handling system -`. You can then use +` for each GUI toolkit we support which uses the +toolkit API to bridge the toolkit UI events into Matplotlib's :ref:`event +handling system `. You can then use `.FigureCanvasBase.mpl_connect` to connect your function to Matplotlib's event handling system. This allows you to directly interact with your data and write GUI toolkit agnostic user @@ -77,8 +76,8 @@ Command Prompt Integration So far, so good. We have the REPL (like the IPython terminal) that lets us interactively send things code to the interpreter and get -results back. We also have the GUI toolkit that run an event loop -waiting for user input and let up register functions to be run when +results back. We also have the GUI toolkit that runs an event loop +waiting for user input and lets us register functions to be run when that happens. However, if we want to do both we have a problem: the prompt and the GUI event loop are both infinite loops that each think *they* are in charge! In order for both the prompt and the GUI windows @@ -148,7 +147,7 @@ able to have a usable prompt **and** interactive figure windows. We can do this using the 'input hook' feature of the interactive prompt. This hook is called by the prompt as it waits for the user -type (even for a fast typist the prompt is mostly waiting for the +to type (even for a fast typist the prompt is mostly waiting for the human to think and move their fingers). Although the details vary between prompts the logic is roughly @@ -157,7 +156,7 @@ between prompts the logic is roughly 3. as soon as the user hits a key, exit the GUI event loop and handle the key 4. repeat -This gives us the illusion of simultaneously having an interactive GUI +This gives us the illusion of simultaneously having interactive GUI windows and an interactive prompt. Most of the time the GUI event loop is running, but as soon as the user starts typing the prompt takes over again. @@ -167,7 +166,7 @@ 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 `. In this case it is your code, not the REPL, which -is blocking process so you need to handle the "time-share" manually. +is blocking the process so you need to handle the "time-share" manually. Conversely, a very slow figure draw will block the prompt until it finishes drawing. @@ -220,7 +219,7 @@ event loop using the methods described :ref:`above `. You can also use the methods described in :ref:`cp_block_the_prompt` to suspend run the GUI event loop. Once the loop exits your code will -resume. In general, anyplace you would use `time.sleep` you can use +resume. In general, any place you would use `time.sleep` you can use `.pyplot.pause` instead with the added benefit of interactive figures. For example, if you want to poll for data you could use something like :: @@ -280,7 +279,7 @@ For example :: slow_loop(100, ln) While this will feel a bit laggy (as we are only processing user input -every 100ms where as 20-30ms is what feels "responsive") it will +every 100ms whereas 20-30ms is what feels "responsive") it will respond. If you make changes to the plot and want it re-rendered you will need @@ -304,7 +303,7 @@ We can add this our example above as :: The more frequently you call `.FigureCanvasBase.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. +resources on the visualization and less on your computation. .. _stale_artists: @@ -315,10 +314,10 @@ Stale Artists Artists (as of Matplotlib 1.5) have a **stale** attribute which is `True` if the internal state of the artist has changed since the last time it was rendered. By default the stale state is propagated up to -the Artists parents in the draw tree, thus if the color of a `.Line2D` -instance is changed, the `.axes.Axes` and `.figure.Figure` it is -contained in will also be marked as "stale". Thus, ``fig.stale`` will -report of any artist in the figure has been modified and out of sync +the Artists parents in the draw tree, e.g., if the color of a `.Line2D` +instance is changed, the `.axes.Axes` and `.figure.Figure` that +contain it will also be marked as "stale". Thus, ``fig.stale`` will +report if any artist in the figure has been modified and is out of sync with what is displayed on the screen. This is intended to be used to determine if ``draw_idle`` should be called to schedule a re-rendering of the figure. @@ -330,17 +329,17 @@ with the signature :: ... which by default is set to a function that forwards the stale state to -the artists parent. If you wish to suppress a given artist from propagating +the artist's parent. If you wish to suppress a given artist from propagating set this attribute to None. `.figure.Figure` instances do not have a containing artist and their default callback is `None`. If you call `.pyplot.ion` and are not in -``IPython`` we will install callback to invoke -`~.backend_bases.FigureCanvasBase.draw_idle` when ever the +``IPython`` we will install a callback to invoke +`~.backend_bases.FigureCanvasBase.draw_idle` whenever the `.figure.Figure` becomes stale. In ``IPython`` we use the ``'post_execute'`` hook to invoke `~.backend_bases.FigureCanvasBase.draw_idle` on any stale figures -after having executed the users input, but before returning the prompt +after having executed the user's input, but before returning the prompt to the user. If you are not using `.pyplot` you can use the callback `Figure.stale_callback` attribute to be notified when a figure has become stale. @@ -363,7 +362,7 @@ Draw Idle In almost all cases, we recommend using `backend_bases.FigureCanvasBase.draw_idle` over `backend_bases.FigureCanvasBase.draw`. ``draw`` forces a rendering of -the figure where as ``draw_idle`` schedules a rendering the next time +the figure whereas ``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 :: @@ -448,7 +447,7 @@ method. The source for the prompt_toolkit input hooks lives at .. [#f2] Or you can `write your own `__ if you must. -.. [#f3] These examples are agressively dropping many of the +.. [#f3] These examples are aggressively dropping many of the complexities that must be dealt with in the real world such as - keyboard interupts, timeouts, bad input, resource + keyboard interrupts, timeouts, bad input, resource allocation and cleanup, etc. diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 38b5b2d6b45d..0597e0c8908d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -310,11 +310,11 @@ def show(*args, **kwargs): Display all open figures. In non-interactive mode, **block** defaults to True. All figures - will display and show will not return until all widows are closed. + will display and show will not return until all windows are closed. If there are no figures, return immediately. In interactive mode **block** defaults to False. This will ensure - that all of the figures are shown and immediately return. + that all of the figures are shown and this function immediately returns. Parameters ---------- From b0690ff0597bd9169c7279aebdfdc108a2772f70 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 17:01:39 -0400 Subject: [PATCH 23/36] DOC: remove badly named and superfluous heading --- doc/users/interactive_guide.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index cb524b13428c..ed6352c32663 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -387,15 +387,12 @@ In general Matplotlib is not thread safe. If you are going to update sure that you are locking in the critical sections. -The Weeds -========= - Eventloop integration mechanism -------------------------------- +=============================== CPython / readline -~~~~~~~~~~~~~~~~~~ +------------------ The python capi provides a hook, :c:var:`PyOS_InputHook`, to register a function to be run "The function will be called when Python's @@ -422,7 +419,7 @@ 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`` From cc3ac5ba55640c8d6ef40b23770336bd841e9c9c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 17:34:59 -0400 Subject: [PATCH 24/36] DOC: address review comments --- doc/users/interactive.rst | 79 +++++++++++++++++---------------- doc/users/interactive_guide.rst | 1 - lib/matplotlib/pyplot.py | 4 +- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 88f0860ca0d1..50e83bfe4d59 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -22,9 +22,9 @@ packages provide bindings to `kivy "live" the GUI event loop will need to be integrated with your prompt. The simplest way is to use IPython (see :ref:`below `). -The `.pyplot` module provides two functions for creating Figures that, -when an interactive backend is used, that are managed by Matplotlib, -embedded in GUI windows, and ready for interactive use out of the box +The `.pyplot` module provides functions for explicitly creating +Figures that include a interactive tools, a toolbar, a tool-tip, and +:ref:`key bindings ` ready to go: `.pyplot.figure` Creates a new empty `.figure.Figure` or selects an existing figure @@ -32,11 +32,17 @@ embedded in GUI windows, and ready for interactive use out of the box `.pyplot.subplots` Creates a new `.figure.Figure` and fills it with a grid of `.axes.Axes` -Matplotlib keeps a reference to all of the open figures created this -way so they will not be garbage collected. You can close all of your -open figures via ``plt.close('all')``. +`.pyplot` has a notion of "The Current Figure" which can be accessed +through `.pyplot.gcf` and a notion of "The Current Axes" accessed +through `.pyplot.gca`. Almost all of the function is `.pyplot` as pass +through to the current `.Figure` / `.axes.Axes` (or create one) as +appropriate. Matplotlib keeps a reference to all of the open figures +created this way so they will not be garbage collected. You can close +and deregister `.Figure`\s from from `.pyplot` individually via +`.pyplot.close` or close all open figures via ``plt.close('all')``. -For discussion of how this works under the hood see: +For discussion of how the integration of the event loops and Matplotlib's event +system work under the hood see: .. toctree:: :maxdepth: 1 @@ -172,7 +178,7 @@ figures will only be "live" when the GUI event loop is running (via loop). -.. warning +.. warning:: Using `.figure.Figure.show` it is possible to display a figure on the screen without starting the event loop and without being in @@ -196,28 +202,29 @@ Navigation Keyboard Shortcuts ----------------------------- The following table holds all the default keys, which can be -overwritten by use of your matplotlibrc (#keymap.\*). - -================================== ================================================= -Command Keyboard Shortcut(s) -================================== ================================================= -Home/Reset **h** or **r** or **home** -Back **c** or **left arrow** or **backspace** -Forward **v** or **right arrow** -Pan/Zoom **p** -Zoom-to-rect **o** -Save **ctrl** + **s** -Toggle fullscreen **f** or **ctrl** + **f** -Close plot **ctrl** + **w** -Close all plots **shift** + **w** +overwritten by use of your :ref:`matplotlibrc +`. + +================================== =============================== +Command Default key binding and rcParam +================================== =============================== +Home/Reset :rc:`keymap.home` +Back :rc:`keymap.back` +Forward :rc:`keymap.forward` +Pan/Zoom :rc:`keymap.pan` +Zoom-to-rect :rc:`keymap.zoom` +Save :rc:`keymap.save` +Toggle fullscreen :rc:`keymap.fullscreen` +Toggle major grids :rc:`keymap.grid` +Toggle minor grids :rc:`keymap.grid_minor` +Toggle x axis scale (log/linear) :rc:`keymap.xscale` +Toggle y axis scale (log/linear) :rc:`keymap.yscale` +Toggle y axis scale (log/linear) :rc:`keymap.yscale` +Close Figure :rc:`keymap.quit` Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse Preserve aspect ratio hold **CONTROL** when panning/zooming with mouse -Toggle major grids **g** when mouse is over an axes -Toggle minor grids **G** when mouse is over an axes -Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes -Toggle y axis scale (log/linear) **l** when mouse is over an axes -================================== ================================================= +================================== =============================== .. _other-shells: @@ -278,17 +285,13 @@ end. GUIs + jupyter ~~~~~~~~~~~~~~ -If you are running your jupyter server locally you can use one of the -GUI backends. However if you ever move that notebook to a remote -server it will cease to work correctly because the GUI windows will be -created on the server, not your machine. When you create a figure the -process running your kernel creates and shows a GUI window. If that -process is on the same computer as your client, then you will be able -to see and interact with the window. However if it is running on a -remote computer it will try to open the GUI window on _that_ computer. -This will either fail by raising an exception (as many servers -do not have an XServer running) or run, but leave you with no -way to access your figure. +If you are running your jupyter kernel locally you can use one of the +GUI backends. Process running your kernel will and shows a GUI window +on your desktop adjacent to your web browser. However if you move +that notebook to a remote server the kernel will try to open the GUI +window on _that_ computer. Unless you have arrange to forward the +xserver back to your desktop, you not be able to see or interact with +the figure (if it does not raise an exception outright). diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index ed6352c32663..d23a25f41621 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -230,7 +230,6 @@ For example, if you want to poll for data you could use something like :: while True: x, y = get_new_data() ln.set_data(x, y) - fig.canvas.draw_idle() plt.pause(1) which would poll for new data and update the figure at 1Hz. diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 0597e0c8908d..569d78613f8e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -390,7 +390,7 @@ def ion(): def pause(interval): """ - Run the GUI event loop or sleep for *interval* seconds and . + Run the GUI event loop for *interval* seconds. If there is an active figure, it will be updated and displayed before the pause, and the GUI event loop (if any) will run during the pause. @@ -398,6 +398,8 @@ def pause(interval): This can be used for crude animation. For more complex animation use :mod:`matplotlib.animation`. + If there is no active figure, sleep for *interval* seconds instead. + See Also -------- matplotlib.animation : Complex animation From afe9edd4e942fe79c24052e28393bcb22157b8d6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 18:08:20 -0400 Subject: [PATCH 25/36] DOC: edits from review Co-authored-by: Elliott Sales de Andrade --- doc/users/interactive.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 50e83bfe4d59..00bd055abac1 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -286,10 +286,10 @@ GUIs + jupyter ~~~~~~~~~~~~~~ If you are running your jupyter kernel locally you can use one of the -GUI backends. Process running your kernel will and shows a GUI window +GUI backends. The process running your kernel will show a GUI window on your desktop adjacent to your web browser. However if you move that notebook to a remote server the kernel will try to open the GUI -window on _that_ computer. Unless you have arrange to forward the +window on _that_ computer. Unless you have arranged to forward the xserver back to your desktop, you not be able to see or interact with the figure (if it does not raise an exception outright). From e09cab91461a9002214d698e07789ff5c71b8e5b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 18:10:04 -0400 Subject: [PATCH 26/36] DOC: more small edits --- doc/users/interactive.rst | 1 - lib/matplotlib/figure.py | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 00bd055abac1..bb76e7862fb5 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -219,7 +219,6 @@ Toggle major grids :rc:`keymap.grid` Toggle minor grids :rc:`keymap.grid_minor` Toggle x axis scale (log/linear) :rc:`keymap.xscale` Toggle y axis scale (log/linear) :rc:`keymap.yscale` -Toggle y axis scale (log/linear) :rc:`keymap.yscale` Close Figure :rc:`keymap.quit` Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index a84ff1215b7f..cdc9944d7dac 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -384,6 +384,7 @@ def show(self, warn=True): AttributeError. .. warning:: + This does not manage an GUI event loop. Consequently, the figure may only be shown briefly or not shown at all if you or your environment are not managing an event loop. @@ -395,11 +396,6 @@ def show(self, warn=True): python script, you should use `matplotlib.pyplot.show` instead, which takes care of managing the event loop for you. - .. warning - - If the GUI event loop is not running the figure will be shown, - but will not be responsive and may not draw. - Parameters ---------- warn : bool, default: True From 04f2fc6f8726ed8c713a925fce4cb6397326dcf8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 18:11:13 -0400 Subject: [PATCH 27/36] DOC: fix spyder spelling --- doc/users/interactive.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index bb76e7862fb5..f81defdf3e69 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -294,8 +294,8 @@ the figure (if it does not raise an exception outright). -PyCharm, Spydter, and VSCode ----------------------------- +PyCharm, Spyder, and VSCode +--------------------------- Many IDEs have built-in integration with Matplotlib, please consult its documentation for configuration details. From 9d842bfeb136a307681a07f089efe619b75f2f6c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 18:29:35 -0400 Subject: [PATCH 28/36] DOC: edits from review Co-authored-by: Elliott Sales de Andrade --- doc/users/interactive.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index f81defdf3e69..48a7d76d2065 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -23,7 +23,7 @@ packages provide bindings to `kivy simplest way is to use IPython (see :ref:`below `). The `.pyplot` module provides functions for explicitly creating -Figures that include a interactive tools, a toolbar, a tool-tip, and +Figures that include interactive tools, a toolbar, a tool-tip, and :ref:`key bindings ` ready to go: `.pyplot.figure` @@ -34,11 +34,11 @@ Figures that include a interactive tools, a toolbar, a tool-tip, and `.pyplot` has a notion of "The Current Figure" which can be accessed through `.pyplot.gcf` and a notion of "The Current Axes" accessed -through `.pyplot.gca`. Almost all of the function is `.pyplot` as pass -through to the current `.Figure` / `.axes.Axes` (or create one) as +through `.pyplot.gca`. Almost all of the functions in `.pyplot` pass +through the current `.Figure` / `.axes.Axes` (or create one) as appropriate. Matplotlib keeps a reference to all of the open figures created this way so they will not be garbage collected. You can close -and deregister `.Figure`\s from from `.pyplot` individually via +and deregister `.Figure`\s from `.pyplot` individually via `.pyplot.close` or close all open figures via ``plt.close('all')``. For discussion of how the integration of the event loops and Matplotlib's event From 41e19a8fbcd38e775213ae61d04081a858c08988 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 7 May 2020 18:36:22 -0400 Subject: [PATCH 29/36] MNT: remove unused import --- lib/matplotlib/textpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index f47f1314cb10..ad90fae8125c 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -5,7 +5,7 @@ import numpy as np -from matplotlib import _text_layout, cbook, dviread, font_manager, rcParams +from matplotlib import _text_layout, dviread, font_manager, rcParams from matplotlib.font_manager import FontProperties, get_font from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_TARGET_LIGHT from matplotlib.mathtext import MathTextParser From eda2ed3f539415eec040996474681b21443de13f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 8 May 2020 20:09:13 -0400 Subject: [PATCH 30/36] DOC: edits from review Co-authored-by: Elliott Sales de Andrade --- doc/users/interactive.rst | 4 ++-- doc/users/interactive_guide.rst | 8 ++++---- lib/matplotlib/pyplot.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 48a7d76d2065..b2ed5bea51df 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -15,7 +15,7 @@ tools are sufficient, but you can also use the Matplotlib event system to build customized data exploration tools. Matplotlib ships with :ref:`backends ` binding to -several GUI toolkits (Qt, Tk, Wx, Gtk, OSX, js) and third party +several GUI toolkits (Qt, Tk, Wx, GTK, macOS, JavaScript) and third party packages provide bindings to `kivy `__ and `Jupyter Lab `__. For the figures to be @@ -288,7 +288,7 @@ If you are running your jupyter kernel locally you can use one of the GUI backends. The process running your kernel will show a GUI window on your desktop adjacent to your web browser. However if you move that notebook to a remote server the kernel will try to open the GUI -window on _that_ computer. Unless you have arranged to forward the +window on *that* computer. Unless you have arranged to forward the xserver back to your desktop, you not be able to see or interact with the figure (if it does not raise an exception outright). diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index d23a25f41621..a8c4a500e994 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -393,7 +393,7 @@ Eventloop integration mechanism CPython / readline ------------------ -The python capi provides a hook, :c:var:`PyOS_InputHook`, to register a +The Python C API provides a hook, :c:var:`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 @@ -404,7 +404,7 @@ run the event loop until a key is pressed on stdin. Matplotlib does not currently do any management of -:c:var:`PyOS_InputHook` due to the wide range of ways that matplotlib +:c:var:`PyOS_InputHook` due to the wide range of ways that Matplotlib is used. This management is left to downstream libraries -- either user code or the shell. Interactive figures, even with matplotlib in 'interactive mode', may not work in the vanilla python repl if an @@ -422,9 +422,9 @@ 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 prompt_toolkit via the +has the same conceptual input hook, which is fed into ``prompt_toolkit`` via the :meth:`IPython.terminal.interactiveshell.TerminalInteractiveShell.inputhook` -method. The source for the prompt_toolkit input hooks lives at +method. The source for the ``prompt_toolkit`` input hooks lives at :mod:`IPython.terminal.pt_inputhooks` diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 569d78613f8e..803b83b86fc7 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -309,11 +309,11 @@ def show(*args, **kwargs): """ Display all open figures. - In non-interactive mode, **block** defaults to True. All figures + In non-interactive mode, *block* defaults to True. All figures will display and show will not return until all windows are closed. If there are no figures, return immediately. - In interactive mode **block** defaults to False. This will ensure + In interactive mode *block* defaults to False. This will ensure that all of the figures are shown and this function immediately returns. Parameters From 7cf68e8c0c393b7739ae7ee6364d1a5196543e86 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 8 May 2020 20:10:21 -0400 Subject: [PATCH 31/36] DOC: remove non-existent function call --- doc/users/interactive_guide.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index a8c4a500e994..4b76c13b7296 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -264,7 +264,6 @@ For example :: plt.ion() fig, ax = plt.subplots() - fig.canvas.show() th = np.linspace(0, 2*np.pi, 512) ax.set_ylim(-1.5, 1.5) From bfa35892db58f74a6d0b2d8b405d4995bf488265 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 8 May 2020 22:16:30 -0400 Subject: [PATCH 32/36] DOC: edits and duplicate removal --- doc/api/blocking_input_api.rst | 11 ----------- doc/users/interactive.rst | 2 -- lib/matplotlib/pyplot.py | 12 ++++++++++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/doc/api/blocking_input_api.rst b/doc/api/blocking_input_api.rst index 67f1ba6d8032..4a23efbe2f7d 100644 --- a/doc/api/blocking_input_api.rst +++ b/doc/api/blocking_input_api.rst @@ -6,14 +6,3 @@ :no-members: :no-undoc-members: :show-inheritance: - - -.. autosummary:: - :toctree: _as_gen - :template: autosummary.rst - :nosignatures: - - BlockingInput - BlockingMouseInput - BlockingContourLabeler - BlockingKeyMouseInput diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index b2ed5bea51df..907339304098 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -78,8 +78,6 @@ loop is properly integrated with the command line (see In [2]: import matplotlib.pyplot as plt - In [3]: - Calling :: diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 803b83b86fc7..0d6ffc06392e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -345,18 +345,21 @@ def isinteractive(): - newly created figures will be shown immediately - figures will automatically redraw on change - - pyplot.show will not block by default + - `.pyplot.show` will not block by default If not in interactive mode then: - newly created figures and changes to figures will not be reflected until explicitly asked to be - - pyplot.show will block by default + - `.pyplot.show` will block by default See Also -------- ion : enable interactive mode ioff : disable interactive mode + + show : show windows (and maybe block) + pause : show widows, run GUI event loop, and block for a time """ return matplotlib.is_interactive() @@ -370,6 +373,8 @@ def ioff(): ion : enable interactive mode isinteractive : query current state + show : show windows (and maybe block) + pause : show widows, run GUI event loop, and block for a time """ matplotlib.interactive(False) uninstall_repl_displayhook() @@ -383,6 +388,9 @@ def ion(): -------- ioff : disable interactive mode isinteractive : query current state + + show : show windows (and maybe block) + pause : show widows, run GUI event loop, and block for a time """ matplotlib.interactive(True) install_repl_displayhook() From 6edcb47e6ac368d7e68df91c1414ffb25312facc Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 9 May 2020 18:18:40 -0400 Subject: [PATCH 33/36] DOC: windows not widows Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/pyplot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 0d6ffc06392e..c96f99c0ff0b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -359,7 +359,7 @@ def isinteractive(): ioff : disable interactive mode show : show windows (and maybe block) - pause : show widows, run GUI event loop, and block for a time + pause : show windows, run GUI event loop, and block for a time """ return matplotlib.is_interactive() @@ -374,7 +374,7 @@ def ioff(): isinteractive : query current state show : show windows (and maybe block) - pause : show widows, run GUI event loop, and block for a time + pause : show windows, run GUI event loop, and block for a time """ matplotlib.interactive(False) uninstall_repl_displayhook() @@ -390,7 +390,7 @@ def ion(): isinteractive : query current state show : show windows (and maybe block) - pause : show widows, run GUI event loop, and block for a time + pause : show windows, run GUI event loop, and block for a time """ matplotlib.interactive(True) install_repl_displayhook() From 9b492bddee131e42d9a4d53029cee5b78c4f26af Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 9 May 2020 18:20:59 -0400 Subject: [PATCH 34/36] DOC: Revert all changes to blocking_input_api.rst --- doc/api/blocking_input_api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/blocking_input_api.rst b/doc/api/blocking_input_api.rst index 4a23efbe2f7d..6ba612682ac4 100644 --- a/doc/api/blocking_input_api.rst +++ b/doc/api/blocking_input_api.rst @@ -3,6 +3,6 @@ ***************************** .. automodule:: matplotlib.blocking_input - :no-members: - :no-undoc-members: + :members: + :undoc-members: :show-inheritance: From 6d4747f9b6aa9c7c12c83cc447595ee2c2c43db1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 20 May 2020 00:30:33 -0400 Subject: [PATCH 35/36] DOC: reduce jargon and correct grammar --- doc/users/interactive.rst | 38 +++++++++++++++++++++------------ doc/users/interactive_guide.rst | 14 ++++++------ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 907339304098..8c611f83f39e 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -146,34 +146,45 @@ Interactive mode controls: - whether created figures are automatically shown - whether changes to artists automatically trigger re-drawing existing figures -- whether `.pyplot.show` blocks or not +- whether `.pyplot.show` returns immediately or after all of the + figures have been closed when given no arguments If in interactive mode, then: - newly created figures will be shown immediately - figures will automatically redraw on change -- pyplot.show will not block by default +- pyplot.show will return immediately by default If not in interactive mode then: - newly created figures and changes to figures will not be reflected until explicitly asked to be -- pyplot.show will block by default +- pyplot.show runs the GUI event loop and does not return until all of + the plot windows are closed If you are in non-interactive mode (or created figures while in non-interactive mode) you may need to explicitly call `.pyplot.show` to bring the windows onto your screen. If you only want to run the GUI event loop for a fixed amount of time you can use `.pyplot.pause`. - -Being in interactive mode is orthogonal to the GUI event loop being -integrated with your command prompt. If you have the GUI event loop -integrated with your prompt, then shown figures will be "live" while -the prompt is waiting for input, if it is not integrated then your -figures will only be "live" when the GUI event loop is running (via -`.pyplot.show`, `.pyplot.pause`, or explicitly starting the GUI main -loop). +This will both block the progress of your code (as if you had called +`time.sleep`), raise ensure the current window is shown and if needed +re-drawn, and run the GUI event loop (so the windows are "live" for +intrection) for the specified period of time. + +Being in "interactive mode" is orthogonal to the GUI event loop being +integrated with your command prompt. If you use `pyplot.ion`, but +have not arrange for the event loop integration your figures will +appear but will not be "live" while the prompt is waiting for input. +You will not be able to pan/zoom and the figure may not even render +(the window might appear black, transparent, or as a snapshot the +desktop under it). Conversely, if you configure the event loop +integration displayed figures will be "live" while waiting for input +at the prompt independent of pyplot's "interactive mode". In either +case, the figures will be "live" if you use +``pyplot.show(block=True)``, `.pyplot.pause`, or run the the GUI main +loop in some other way. .. warning:: @@ -276,8 +287,7 @@ If you only need to use the classic notebook you can use %matplotlib notebook which uses the `.backend_nbagg` backend which ships with Matplotlib. -However nbagg does not work in Jupyter Lab due to changes in the front -end. +However nbagg does not work in Jupyter Lab. GUIs + jupyter ~~~~~~~~~~~~~~ @@ -295,5 +305,5 @@ the figure (if it does not raise an exception outright). PyCharm, Spyder, and VSCode --------------------------- -Many IDEs have built-in integration with Matplotlib, please consult its +Many IDEs have built-in integration with Matplotlib, please consult their documentation for configuration details. diff --git a/doc/users/interactive_guide.rst b/doc/users/interactive_guide.rst index 4b76c13b7296..15c4854c81f2 100644 --- a/doc/users/interactive_guide.rst +++ b/doc/users/interactive_guide.rst @@ -75,13 +75,13 @@ Command Prompt Integration ========================== So far, so good. We have the REPL (like the IPython terminal) that -lets us interactively send things code to the interpreter and get -results back. We also have the GUI toolkit that runs an event loop -waiting for user input and lets us register functions to be run when -that happens. However, if we want to do both we have a problem: the -prompt and the GUI event loop are both infinite loops that each think -*they* are in charge! In order for both the prompt and the GUI windows -to be responsive we need a method to allow the loops to 'timeshare' : +lets us interactively send code to the interpreter and get results +back. We also have the GUI toolkit that runs an event loop waiting +for user input and lets us register functions to be run when that +happens. However, if we want to do both we have a problem: the prompt +and the GUI event loop are both infinite loops that each think *they* +are in charge! In order for both the prompt and the GUI windows to be +responsive we need a method to allow the loops to 'timeshare' : 1. let the GUI main loop block the python process when you want interactive windows From f05ea3d4704b87317544309f25d9a27ff2af0e9c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 20 May 2020 10:02:28 -0400 Subject: [PATCH 36/36] DOC: grammar / wording corrections from review Co-authored-by: Elliott Sales de Andrade --- doc/users/interactive.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index 8c611f83f39e..8c10d1250f3a 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -169,19 +169,19 @@ non-interactive mode) you may need to explicitly call `.pyplot.show` to bring the windows onto your screen. If you only want to run the GUI event loop for a fixed amount of time you can use `.pyplot.pause`. This will both block the progress of your code (as if you had called -`time.sleep`), raise ensure the current window is shown and if needed +`time.sleep`), ensure the current window is shown and if needed re-drawn, and run the GUI event loop (so the windows are "live" for -intrection) for the specified period of time. +interaction) for the specified period of time. Being in "interactive mode" is orthogonal to the GUI event loop being integrated with your command prompt. If you use `pyplot.ion`, but -have not arrange for the event loop integration your figures will +have not arranged for the event loop integration, your figures will appear but will not be "live" while the prompt is waiting for input. You will not be able to pan/zoom and the figure may not even render -(the window might appear black, transparent, or as a snapshot the +(the window might appear black, transparent, or as a snapshot of the desktop under it). Conversely, if you configure the event loop -integration displayed figures will be "live" while waiting for input -at the prompt independent of pyplot's "interactive mode". In either +integration, displayed figures will be "live" while waiting for input +at the prompt, regardless of pyplot's "interactive mode". In either case, the figures will be "live" if you use ``pyplot.show(block=True)``, `.pyplot.pause`, or run the the GUI main loop in some other way.