diff --git a/src/plopp/backends/matplotlib/figure.py b/src/plopp/backends/matplotlib/figure.py index 50aa5e9d..660787bf 100644 --- a/src/plopp/backends/matplotlib/figure.py +++ b/src/plopp/backends/matplotlib/figure.py @@ -7,7 +7,7 @@ from ...graphics import BaseFig from .canvas import Canvas -from .utils import fig_to_bytes, is_interactive_backend +from .utils import fig_to_bytes, is_widget_backend try: from ipywidgets import VBox @@ -218,7 +218,7 @@ def to_widget(self): def Figure(*args, **kwargs): - if is_interactive_backend(): + if is_widget_backend(): return InteractiveFigure(*args, **kwargs) else: return StaticFigure(*args, **kwargs) diff --git a/src/plopp/backends/matplotlib/utils.py b/src/plopp/backends/matplotlib/utils.py index 6dbd6760..1980771c 100644 --- a/src/plopp/backends/matplotlib/utils.py +++ b/src/plopp/backends/matplotlib/utils.py @@ -51,6 +51,17 @@ def is_interactive_backend() -> bool: ) +def is_widget_backend() -> bool: + """ + Return ``True`` if the current backend used by Matplotlib renders figures as + Jupyter widgets (e.g. ipympl). Note that this is different from + :func:`is_interactive_backend`: GUI backends such as Qt or Tk are interactive + but do not render figures as Jupyter widgets. + """ + backend = mpl.get_backend().lower() + return any(b in backend for b in ('ipympl', 'nbagg', 'notebook', 'widget')) + + def make_figure(*args, **kwargs) -> plt.Figure: """ Create a new figure. diff --git a/tests/backends/matplotlib/mpl_utils_test.py b/tests/backends/matplotlib/mpl_utils_test.py new file mode 100644 index 00000000..06d4122d --- /dev/null +++ b/tests/backends/matplotlib/mpl_utils_test.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) + +import matplotlib +import pytest + +from plopp.backends.matplotlib.figure import StaticFigure +from plopp.backends.matplotlib.utils import is_interactive_backend, is_widget_backend +from plopp.data.testing import data_array + + +@pytest.mark.parametrize( + 'backend', ['qtagg', 'tkagg', 'macosx', 'gtk4agg', 'wxagg', 'webagg'] +) +def test_gui_backends_are_interactive_but_not_widget(monkeypatch, backend): + monkeypatch.setattr(matplotlib, 'get_backend', lambda: backend) + assert is_interactive_backend() + assert not is_widget_backend() + + +@pytest.mark.parametrize('backend', ['module://ipympl.backend_nbagg', 'nbagg']) +def test_widget_backends_are_interactive_and_widget(monkeypatch, backend): + monkeypatch.setattr(matplotlib, 'get_backend', lambda: backend) + assert is_interactive_backend() + assert is_widget_backend() + + +@pytest.mark.parametrize( + 'backend', ['agg', 'module://matplotlib_inline.backend_inline', 'pdf', 'svg'] +) +def test_static_backends_are_neither_interactive_nor_widget(monkeypatch, backend): + monkeypatch.setattr(matplotlib, 'get_backend', lambda: backend) + assert not is_interactive_backend() + assert not is_widget_backend() + + +@pytest.mark.parametrize('backend', ['qtagg', 'tkagg', 'macosx']) +def test_gui_backend_makes_static_figure(monkeypatch, backend): + # GUI backends are used when plotting from a terminal, where ipywidgets-based + # interactive figures cannot be displayed. See issue #589. + monkeypatch.setattr(matplotlib, 'get_backend', lambda: backend) + fig = data_array(ndim=1).plot() + assert isinstance(fig, StaticFigure) + assert not fig.interactive