Skip to content

Document what pyplot expects from a backend. #24218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/users/explain/backends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,6 @@ More generally, any importable backend can be selected by using any of the
methods above. If ``name.of.the.backend`` is the module containing the
backend, use ``module://name.of.the.backend`` as the backend name, e.g.
``matplotlib.use('module://name.of.the.backend')``.

Information for backend implementers is available at
:doc:`/users/explain/writing_a_backend_pyplot_interface`.
1 change: 1 addition & 0 deletions doc/users/explain/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Explanations

api_interfaces.rst
backends.rst
writing_a_backend_pyplot_interface.rst
interactive.rst
fonts.rst
event_handling.rst
Expand Down
82 changes: 82 additions & 0 deletions doc/users/explain/writing_a_backend_pyplot_interface.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
=========================================
Writing a backend -- the pyplot interface
=========================================

This page assumes general understanding of the information in the
:doc:`/users/explain/backends` page, and is instead intended as reference for
third-party backend implementers. It also only deals with the interaction
between backends and `.pyplot`, not with the rendering side, which is described
in `.backend_template`.

There are two APIs for defining backends: a new canvas-based API (introduced in
Matplotlib 3.6), and an older function-based API. The new API is simpler to
implement because many methods can be inherited from "parent backends". It is
recommended if back-compatibility for Matplotlib < 3.6 is not a concern.
However, the old API remains supported.

Fundamentally, a backend module needs to provide information to `.pyplot`, so
that

1. `.pyplot.figure()` can create a new `.Figure` instance and associate it with
an instance of a backend-provided canvas class, itself hosted in an instance
of a backend-provided manager class.
2. `.pyplot.show()` can show all figures and start the GUI event loop (if any).

To do so, the backend module must define a ``backend_module.FigureCanvas``
subclass of `.FigureCanvasBase`. In the canvas-based API, this is the only
strict requirement for backend modules. The function-based API additionally
requires many module-level functions to be defined.

Canvas-based API (Matplotlib >= 3.6)
------------------------------------

1. **Creating a figure**: `.pyplot.figure()` calls
``figure = Figure(); FigureCanvas.new_manager(figure, num)``
(``new_manager`` is a classmethod) to instantiate a canvas and a manager and
set up the ``figure.canvas`` and ``figure.canvas.manager`` attributes.
Figure unpickling uses the same approach, but replaces the newly
instantiated ``Figure()`` by the unpickled figure.

Interactive backends should customize the effect of ``new_manager`` by
setting the ``FigureCanvas.manager_class`` attribute to the desired manager
class, and additionally (if the canvas cannot be created before the manager,
as in the case of the wx backends) by overriding the
``FigureManager.create_with_canvas`` classmethod. (Non-interactive backends
can normally use a trivial ``FigureManagerBase`` and can therefore skip this
step.)

After a new figure is registered with `.pyplot` (either via
`.pyplot.figure()` or via unpickling), if in interactive mode, `.pyplot`
will call its canvas' ``draw_idle()`` method, which can be overridden as
desired.

2. **Showing figures**: `.pyplot.show()` calls
``FigureCanvas.manager_class.pyplot_show()`` (a classmethod), forwarding any
arguments, to start the main event loop.

By default, ``pyplot_show()`` checks whether there are any ``managers``
registered with `.pyplot` (exiting early if not), calls ``manager.show()``
on all such managers, and then, if called with ``block=True`` (or with
the default ``block=None`` and out of IPython's pylab mode and not in
interactive mode), calls ``FigureCanvas.manager_class.start_main_loop()``
(a classmethod) to start the main event loop. Interactive backends should
therefore override the ``FigureCanvas.manager_class.start_main_loop``
classmethod accordingly (or alternatively, they may also directly override
``FigureCanvas.manager_class.pyplot_show`` directly).

Function-based API
------------------

1. **Creating a figure**: `.pyplot.figure()` calls
``new_figure_manager(num, *args, **kwargs)`` (which also takes care of
creating the new figure as ``Figure(*args, **kwargs)``); unpickling calls
``new_figure_manager_given_figure(num, figure)``.

Furthermore, in interactive mode, the first draw of the newly registered
figure can be customized by providing a module-level
``draw_if_interactive()`` function. (In the new canvas-based API, this
function is not taken into account anymore.)

2. **Showing figures**: `.pyplot.show()` calls a module-level ``show()``
function, which is typically generated via the ``ShowBase`` class and its
``mainloop`` method.