From ac43f88f31304f04fe5d732a4ff16c130289d754 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 22 May 2022 22:21:38 +0200 Subject: [PATCH] Document what pyplot expects from a backend. --- doc/users/explain/backends.rst | 3 + doc/users/explain/index.rst | 1 + .../writing_a_backend_pyplot_interface.rst | 82 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 doc/users/explain/writing_a_backend_pyplot_interface.rst diff --git a/doc/users/explain/backends.rst b/doc/users/explain/backends.rst index e66016e76e23..9b188048de50 100644 --- a/doc/users/explain/backends.rst +++ b/doc/users/explain/backends.rst @@ -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`. diff --git a/doc/users/explain/index.rst b/doc/users/explain/index.rst index c2121667da4b..4aaf21494f1a 100644 --- a/doc/users/explain/index.rst +++ b/doc/users/explain/index.rst @@ -10,6 +10,7 @@ Explanations api_interfaces.rst backends.rst + writing_a_backend_pyplot_interface.rst interactive.rst fonts.rst event_handling.rst diff --git a/doc/users/explain/writing_a_backend_pyplot_interface.rst b/doc/users/explain/writing_a_backend_pyplot_interface.rst new file mode 100644 index 000000000000..e193135f970d --- /dev/null +++ b/doc/users/explain/writing_a_backend_pyplot_interface.rst @@ -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.