diff --git a/doc/conf.py b/doc/conf.py index 76c941b7d7bc..dace4fc28982 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -114,13 +114,14 @@ 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), } diff --git a/doc/devel/documenting_mpl.rst b/doc/devel/documenting_mpl.rst index dcc3a2411c04..551b25912d8a 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 + .. 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/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" ], diff --git a/doc/users/interactive.rst b/doc/users/interactive.rst index e8af8df9409e..8c10d1250f3a 100644 --- a/doc/users/interactive.rst +++ b/doc/users/interactive.rst @@ -1,12 +1,309 @@ -.. _dump-index: +.. currentmodule:: matplotlib -=================== - Interactive plots -=================== +.. _mpl-shell: + +===================== + Interactive Figures +===================== + +.. 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 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, macOS, JavaScript) 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 `). + +The `.pyplot` module provides functions for explicitly creating +Figures that include 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 + +`.pyplot.subplots` + Creates a new `.figure.Figure` and fills it with a grid of `.axes.Axes` + +`.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 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 `.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 +system work under the hood see: .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - navigation_toolbar.rst - shell.rst + interactive_guide.rst event_handling.rst + + +.. _ipython-pylab: + +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 +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 + +.. highlight:: ipython + +:: + + user@machine:~ $ ipython + Python 3.8.2 (default, Apr 8 2020, 14:31:25) + Type 'copyright', 'credits' or 'license' for more information + IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help. + + In [1]: %matplotlib + Using matplotlib backend: Qt5Agg + + In [2]: import matplotlib.pyplot as plt + +Calling + +:: + + In [3]: fig, ax = plt.subplots() + +will pop open a window for you and + +:: + + In [4]: ln, = ax.plot(range(5)) + +will show your data in the window. If you change something about the +line, for example the color + +:: + + In [5]: ln.set_color('orange') + +it will be reflected immediately. If you wish to disable this behavior +use + +:: + + In [6]: plt.ioff() + +and + +:: + + In [7]: plt.ion() + +re-enable it. + +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. + + +.. highlight:: python + +.. _controlling-interactive: + +Interactive mode +================ + + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + pyplot.ion + pyplot.ioff + pyplot.isinteractive + + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + pyplot.show + pyplot.pause + + +Interactive mode controls: + +- whether created figures are automatically shown +- whether changes to artists automatically trigger re-drawing existing figures +- 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 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 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`. +This will both block the progress of your code (as if you had called +`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 +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 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 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, 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. + + +.. warning:: + + 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 +========== + + +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. + + +.. _key-event-handling: + +Navigation Keyboard Shortcuts +----------------------------- + +The following table holds all the default keys, which can be +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` +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 +================================== =============================== + + +.. _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:: pycon + + >>> 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. + +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. + +GUIs + jupyter +~~~~~~~~~~~~~~ + +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 +xserver back to your desktop, you not be able to see or interact with +the figure (if it does not raise an exception outright). + + + +PyCharm, Spyder, and VSCode +--------------------------- + +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 new file mode 100644 index 000000000000..15c4854c81f2 --- /dev/null +++ b/doc/users/interactive_guide.rst @@ -0,0 +1,448 @@ +.. _interactive_figures_and_eventloops: + +.. currentmodule:: matplotlib + + +================================================== + Interactive Figures and Asynchronous Programming +================================================== + +Matplotlib supports rich interactive figures by embedding figures into +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. + +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 +`__. + +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 +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 or the network -- +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 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 +users to write reactive, event-driven, programs without having to +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``. + + +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 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 +interfaces. + + +.. _cp_integration: + +Command Prompt Integration +========================== + +So far, so good. We have the REPL (like the IPython terminal) that +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 +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) + +.. _cp_block_the_prompt: + +Blocking the Prompt +------------------- + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + pyplot.show + pyplot.pause + + backend_bases.FigureCanvasBase.start_event_loop + backend_bases.FigureCanvasBase.stop_event_loop + + +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 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 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`. + +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, or displays a figure +between polling for additional data. See :ref:`interactive_scripts` +for more details. + + +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 +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 +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 + +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 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 +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 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. + +Full embedding +============== + +It is also possible to go the other direction and fully embed figures +(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 : + +Scripts and functions +===================== + + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + backend_bases.FigureCanvasBase.flush_events + backend_bases.FigureCanvasBase.draw_idle + + figure.Figure.ginput + pyplot.ginput + + pyplot.show + pyplot.pause + +There are several use-cases for using interactive figures in scripts: + +- capture user input to steer the script +- progress updates as a long running script progresses +- streaming updates from a data source + +Blocking functions +------------------ + +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 `. + +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, 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 :: + + fig, ax = plt.subplots() + ln, = ax.plot([], []) + + while True: + x, y = get_new_data() + ln.set_data(x, y) + 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 +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() + 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) + +While this will feel a bit laggy (as we are only processing user input +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 +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 +resources on the visualization and less on your computation. + + +.. _stale_artists: + +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, 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. + +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 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 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 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. + + +.. _draw_idle: + +Draw Idle +========= + +.. autosummary:: + :template: autosummary.rst + :nosignatures: + + backend_bases.FigureCanvasBase.draw + backend_bases.FigureCanvasBase.draw_idle + backend_bases.FigureCanvasBase.flush_events + + +In almost all cases, we recommend using +`backend_bases.FigureCanvasBase.draw_idle` over +`backend_bases.FigureCanvasBase.draw`. ``draw`` forces a rendering of +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 :: + + fig.canvas.draw_idle() + fig.canvas.flush_events() + + + +Threading +========= + +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. + + + +Eventloop integration mechanism +=============================== + +CPython / readline +------------------ + +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 +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 +: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. +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 fed 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` + + + +.. 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 + then the loop would look something like :: + + fds = [...] + while True: # Loop + inp = select(fds).read() # Read + eval(inp) # Evaluate / Print + +.. [#f2] Or you can `write your own + `__ if you must. + +.. [#f3] These examples are aggressively dropping many of the + complexities that must be dealt with in the real world such as + keyboard interrupts, timeouts, bad input, resource + allocation and cleanup, etc. 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) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f7889aa5344e..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. diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 6c99c51df4a5..c96f99c0ff0b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -307,58 +307,111 @@ 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 windows 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 this function immediately returns. 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) 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 + + show : show windows (and maybe block) + pause : show windows, run GUI event loop, and block for a time + """ 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 + + show : show windows (and maybe block) + pause : show windows, run GUI event loop, and block for a time + """ 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 + + show : show windows (and maybe block) + pause : show windows, run GUI event loop, and block for a time + """ matplotlib.interactive(True) install_repl_displayhook() def pause(interval): """ - Pause for *interval* seconds. + 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. - 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`. - Notes - ----- - This function is experimental; its behavior may be changed or extended in a - future release. + If there is no active figure, sleep for *interval* seconds instead. + + See Also + -------- + matplotlib.animation : Complex animation + show : show figures and optional block forever """ manager = _pylab_helpers.Gcf.get_active() if manager is not None: 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