File: writing_a_backend_pyplot_interface.rst

package info (click to toggle)
matplotlib 3.10.1%2Bdfsg1-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 78,352 kB
  • sloc: python: 147,118; cpp: 62,988; objc: 1,679; ansic: 1,426; javascript: 786; makefile: 104; sh: 53
file content (130 lines) | stat: -rw-r--r-- 5,909 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
.. redirect-from:: /users/explain/writing_a_backend_pyplot_interface

.. _writing_backend_interface:

=========================================
Writing a backend -- the pyplot interface
=========================================

This page assumes general understanding of the information in the
:ref:`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.

Registering a backend
---------------------

For a new backend to be usable via ``matplotlib.use()`` or IPython
``%matplotlib`` magic command, it must be compatible with one of the three ways
supported by the :class:`~matplotlib.backends.registry.BackendRegistry`:

Built-in
^^^^^^^^

A backend built into Matplotlib must have its name and
``FigureCanvas.required_interactive_framework`` hard-coded in the
:class:`~matplotlib.backends.registry.BackendRegistry`.  If the backend module
is not ``f"matplotlib.backends.backend_{backend_name.lower()}"`` then there
must also be an entry in the ``BackendRegistry._name_to_module``.

module:// syntax
^^^^^^^^^^^^^^^^

Any backend in a separate module (not built into Matplotlib) can be used by
specifying the path to the module in the form ``module://some.backend.module``.
An example is ``module://mplcairo.qt`` for
`mplcairo <https://github.com/matplotlib/mplcairo>`_.  The backend's
interactive framework will be taken from its
``FigureCanvas.required_interactive_framework``.

Entry point
^^^^^^^^^^^

An external backend module can self-register as a backend using an
``entry point`` in its ``pyproject.toml`` such as the one used by
``matplotlib-inline``:

.. code-block:: toml

    [project.entry-points."matplotlib.backend"]
    inline = "matplotlib_inline.backend_inline"

The backend's interactive framework will be taken from its
``FigureCanvas.required_interactive_framework``.  All entry points are loaded
together but only when first needed, such as when a backend name is not
recognised as a built-in backend, or when
:meth:`~matplotlib.backends.registry.BackendRegistry.list_all` is first called.