Skip to content

Commit 6650f21

Browse files
committed
Add a backend kwarg to savefig.
This makes it easier e.g. to test the pgf backend without globally switching the backend.
1 parent 1c6ee08 commit 6650f21

File tree

6 files changed

+57
-15
lines changed

6 files changed

+57
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
``savefig()`` gained a ``backend`` keyword argument
2+
---------------------------------------------------
3+
4+
The ``backend`` keyword argument to ``savefig`` can now be used to pick the
5+
rendering backend without having to globally set the backend; e.g. one can save
6+
pdfs using the pgf backend with ``savefig("file.pdf", backend="pgf")``.

lib/matplotlib/backend_bases.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,18 +1951,32 @@ def get_supported_filetypes_grouped(cls):
19511951
groupings[name].sort()
19521952
return groupings
19531953

1954-
def _get_output_canvas(self, fmt):
1954+
def _get_output_canvas(self, backend, fmt):
19551955
"""
1956-
Return a canvas suitable for saving figures to a specified file format.
1956+
Set the canvas in preparation for saving the figure.
19571957
1958-
If necessary, this function will switch to a registered backend that
1959-
supports the format.
1960-
"""
1961-
# Return the current canvas if it supports the requested format.
1962-
if hasattr(self, 'print_{}'.format(fmt)):
1958+
Parameters
1959+
----------
1960+
backend : str or None
1961+
If not None, switch the figure canvas to the ``FigureCanvas`` class
1962+
of the given backend.
1963+
fmt : str
1964+
If *backend* is None, then determine a suitable canvas class for
1965+
saving to format *fmt* -- either the current canvas class, if it
1966+
supports *fmt*, or whatever `get_registered_canvas_class` returns;
1967+
switch the figure canvas to that canvas class.
1968+
"""
1969+
if backend is not None:
1970+
# Return a specific canvas class, if requested.
1971+
canvas_class = (
1972+
importlib.import_module(cbook._backend_module_name(backend))
1973+
.FigureCanvas)
1974+
elif hasattr(self, 'print_{}'.format(fmt)):
1975+
# Return the current canvas if it supports the requested format.
19631976
return self
1964-
# Return a default canvas for the requested format, if it exists.
1965-
canvas_class = get_registered_canvas_class(fmt)
1977+
else:
1978+
# Return a default canvas for the requested format, if it exists.
1979+
canvas_class = get_registered_canvas_class(fmt)
19661980
if canvas_class:
19671981
return self.switch_backends(canvas_class)
19681982
# Else report error for unsupported format.
@@ -1972,7 +1986,7 @@ def _get_output_canvas(self, fmt):
19721986

19731987
def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
19741988
orientation='portrait', format=None,
1975-
*, bbox_inches=None, **kwargs):
1989+
*, bbox_inches=None, backend=None, **kwargs):
19761990
"""
19771991
Render the figure to hardcopy. Set the figure patch face and edge
19781992
colors. This is useful because some of the GUIs have a gray figure
@@ -2012,6 +2026,10 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
20122026
A list of extra artists that will be considered when the
20132027
tight bbox is calculated.
20142028
2029+
backend : str, optional
2030+
When set, forcibly use this backend for rendering. This can be
2031+
either a standard backend name ("agg", ...) or a custom backend
2032+
name ("module://...").
20152033
"""
20162034
if format is None:
20172035
# get format from filename, or from backend's default filetype
@@ -2026,7 +2044,7 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
20262044
format = format.lower()
20272045

20282046
# get canvas object and print method for format
2029-
canvas = self._get_output_canvas(format)
2047+
canvas = self._get_output_canvas(backend, format)
20302048
print_method = getattr(canvas, 'print_%s' % format)
20312049

20322050
if dpi is None:

lib/matplotlib/cbook/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,3 +2212,12 @@ def __init__(self, fget):
22122212

22132213
def __get__(self, instance, owner):
22142214
return self._fget(owner)
2215+
2216+
2217+
def _backend_module_name(name):
2218+
"""
2219+
Convert a backend name (either a standard backend -- "Agg", "TkAgg", ... --
2220+
or a custom backend -- "module://...") to the corresponding module name).
2221+
"""
2222+
return (name[9:] if name.startswith("module://")
2223+
else "matplotlib.backends.backend_{}".format(name.lower()))

lib/matplotlib/figure.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2131,6 +2131,11 @@ def savefig(self, fname, *, transparent=None, **kwargs):
21312131
A list of extra artists that will be considered when the
21322132
tight bbox is calculated.
21332133
2134+
backend : str, optional
2135+
When set, forcibly use this backend for rendering. This can be
2136+
either a standard backend name ("agg", ...) or a custom backend
2137+
name ("module://...").
2138+
21342139
metadata : dict, optional
21352140
Key/value pairs to store in the image metadata. The supported keys
21362141
and defaults depend on the image format and backend:

lib/matplotlib/pyplot.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,7 @@ def switch_backend(newbackend):
212212
rcParamsOrig["backend"] = "agg"
213213
return
214214

215-
backend_name = (
216-
newbackend[9:] if newbackend.startswith("module://")
217-
else "matplotlib.backends.backend_{}".format(newbackend.lower()))
218-
215+
backend_name = cbook._backend_module_name(newbackend)
219216
backend_mod = importlib.import_module(backend_name)
220217
Backend = type(
221218
"Backend", (matplotlib.backend_bases._Backend,), vars(backend_mod))

lib/matplotlib/tests/test_figure.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,13 @@ def test_savefig():
391391
fig.savefig("fname1.png", "fname2.png")
392392

393393

394+
def test_savefig_backend():
395+
fig = plt.figure()
396+
# Intentionally use an invalid module name.
397+
with pytest.raises(ModuleNotFoundError, match="No module named '@absent'"):
398+
fig.savefig("test", backend="module://@absent")
399+
400+
394401
def test_figure_repr():
395402
fig = plt.figure(figsize=(10, 20), dpi=10)
396403
assert repr(fig) == "<Figure size 100x200 with 0 Axes>"

0 commit comments

Comments
 (0)