-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Move Matplotlib backend mapping to Matplotlib #14371
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
Changes from all commits
e0e1cad
38e6f14
f9ce8c2
6dcf7cf
e200a04
4a4fa93
e666bc9
b1d9d89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,9 +12,12 @@ | |
from IPython.core.display import _pngxy | ||
from IPython.utils.decorators import flag_calls | ||
|
||
# If user specifies a GUI, that dictates the backend, otherwise we read the | ||
# user's mpl default from the mpl rc structure | ||
backends = { | ||
|
||
# Matplotlib backend resolution functionality moved from IPython to Matplotlib | ||
# in IPython 8.24 and Matplotlib 3.9.1. Need to keep `backends` and `backend2gui` | ||
# here for earlier Matplotlib and for external backend libraries such as | ||
# mplcairo that might rely upon it. | ||
_deprecated_backends = { | ||
"tk": "TkAgg", | ||
"gtk": "GTKAgg", | ||
"gtk3": "GTK3Agg", | ||
|
@@ -41,29 +44,44 @@ | |
# GUI support to activate based on the desired matplotlib backend. For the | ||
# most part it's just a reverse of the above dict, but we also need to add a | ||
# few others that map to the same GUI manually: | ||
backend2gui = dict(zip(backends.values(), backends.keys())) | ||
_deprecated_backend2gui = dict( | ||
zip(_deprecated_backends.values(), _deprecated_backends.keys()) | ||
) | ||
# In the reverse mapping, there are a few extra valid matplotlib backends that | ||
# map to the same GUI support | ||
backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk" | ||
backend2gui["GTK3Cairo"] = "gtk3" | ||
backend2gui["GTK4Cairo"] = "gtk4" | ||
backend2gui["WX"] = "wx" | ||
backend2gui["CocoaAgg"] = "osx" | ||
_deprecated_backend2gui["GTK"] = _deprecated_backend2gui["GTKCairo"] = "gtk" | ||
_deprecated_backend2gui["GTK3Cairo"] = "gtk3" | ||
_deprecated_backend2gui["GTK4Cairo"] = "gtk4" | ||
_deprecated_backend2gui["WX"] = "wx" | ||
_deprecated_backend2gui["CocoaAgg"] = "osx" | ||
# There needs to be a hysteresis here as the new QtAgg Matplotlib backend | ||
# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5, | ||
# and Qt6. | ||
backend2gui["QtAgg"] = "qt" | ||
backend2gui["Qt4Agg"] = "qt4" | ||
backend2gui["Qt5Agg"] = "qt5" | ||
_deprecated_backend2gui["QtAgg"] = "qt" | ||
_deprecated_backend2gui["Qt4Agg"] = "qt4" | ||
_deprecated_backend2gui["Qt5Agg"] = "qt5" | ||
|
||
# And some backends that don't need GUI integration | ||
del backend2gui["nbAgg"] | ||
del backend2gui["agg"] | ||
del backend2gui["svg"] | ||
del backend2gui["pdf"] | ||
del backend2gui["ps"] | ||
del backend2gui["module://matplotlib_inline.backend_inline"] | ||
del backend2gui["module://ipympl.backend_nbagg"] | ||
del _deprecated_backend2gui["nbAgg"] | ||
del _deprecated_backend2gui["agg"] | ||
del _deprecated_backend2gui["svg"] | ||
del _deprecated_backend2gui["pdf"] | ||
del _deprecated_backend2gui["ps"] | ||
del _deprecated_backend2gui["module://matplotlib_inline.backend_inline"] | ||
del _deprecated_backend2gui["module://ipympl.backend_nbagg"] | ||
|
||
|
||
# Deprecated attributes backends and backend2gui mostly following PEP 562. | ||
def __getattr__(name): | ||
if name in ("backends", "backend2gui"): | ||
warnings.warn( | ||
f"{name} is deprecated since IPython 8.24, backends are managed " | ||
"in matplotlib and can be externally registered.", | ||
DeprecationWarning, | ||
) | ||
return globals()[f"_deprecated_{name}"] | ||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") | ||
|
||
|
||
#----------------------------------------------------------------------------- | ||
# Matplotlib utilities | ||
|
@@ -267,7 +285,7 @@ def select_figure_formats(shell, formats, **kwargs): | |
|
||
[ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ] | ||
mplbackend = matplotlib.get_backend().lower() | ||
if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg': | ||
if mplbackend in ("nbagg", "ipympl", "widget", "module://ipympl.backend_nbagg"): | ||
formatter = shell.display_formatter.ipython_display_formatter | ||
formatter.for_type(Figure, _reshow_nbagg_figure) | ||
|
||
|
@@ -319,7 +337,23 @@ def find_gui_and_backend(gui=None, gui_select=None): | |
|
||
import matplotlib | ||
|
||
has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5) | ||
if _matplotlib_manages_backends(): | ||
backend_registry = matplotlib.backends.registry.backend_registry | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that need explicit import of
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
# gui argument may be a gui event loop or may be a backend name. | ||
if gui in ("auto", None): | ||
backend = matplotlib.rcParamsOrig["backend"] | ||
backend, gui = backend_registry.resolve_backend(backend) | ||
else: | ||
backend, gui = backend_registry.resolve_gui_or_backend(gui) | ||
|
||
return gui, backend | ||
|
||
# Fallback to previous behaviour (Matplotlib < 3.9) | ||
mpl_version_info = getattr(matplotlib, "__version_info__", (0, 0)) | ||
has_unified_qt_backend = mpl_version_info >= (3, 5) | ||
|
||
from IPython.core.pylabtools import backends | ||
|
||
backends_ = dict(backends) | ||
if not has_unified_qt_backend: | ||
|
@@ -338,6 +372,7 @@ def find_gui_and_backend(gui=None, gui_select=None): | |
backend = matplotlib.rcParamsOrig['backend'] | ||
# In this case, we need to find what the appropriate gui selection call | ||
# should be for IPython, so we can activate inputhook accordingly | ||
from IPython.core.pylabtools import backend2gui | ||
gui = backend2gui.get(backend, None) | ||
|
||
# If we have already had a gui active, we need it and inline are the | ||
|
@@ -346,6 +381,11 @@ def find_gui_and_backend(gui=None, gui_select=None): | |
gui = gui_select | ||
backend = backends_[gui] | ||
|
||
# Matplotlib before _matplotlib_manages_backends() can return "inline" for | ||
# no gui event loop rather than the None that IPython >= 8.24.0 expects. | ||
if gui == "inline": | ||
gui = None | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we emit a warning here, to make sure that things like backend_to_gui never return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only get to this code if we are using "old" Matplotlib (i.e. before the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add an extra comment in the code here though. |
||
return gui, backend | ||
|
||
|
||
|
@@ -431,3 +471,48 @@ def configure_inline_support(shell, backend): | |
) | ||
|
||
configure_inline_support_orig(shell, backend) | ||
|
||
|
||
# Determine if Matplotlib manages backends only if needed, and cache result. | ||
# Do not read this directly, instead use _matplotlib_manages_backends(). | ||
_matplotlib_manages_backends_value: bool | None = None | ||
|
||
|
||
def _matplotlib_manages_backends() -> bool: | ||
"""Return True if Matplotlib manages backends, False otherwise. | ||
|
||
If it returns True, the caller can be sure that | ||
matplotlib.backends.registry.backend_registry is available along with | ||
member functions resolve_gui_or_backend, resolve_backend, list_all, and | ||
list_gui_frameworks. | ||
""" | ||
global _matplotlib_manages_backends_value | ||
if _matplotlib_manages_backends_value is None: | ||
try: | ||
from matplotlib.backends.registry import backend_registry | ||
|
||
_matplotlib_manages_backends_value = hasattr( | ||
backend_registry, "resolve_gui_or_backend" | ||
) | ||
except ImportError: | ||
_matplotlib_manages_backends_value = False | ||
|
||
return _matplotlib_manages_backends_value | ||
|
||
|
||
def _list_matplotlib_backends_and_gui_loops() -> list[str]: | ||
"""Return list of all Matplotlib backends and GUI event loops. | ||
|
||
This is the list returned by | ||
%matplotlib --list | ||
""" | ||
if _matplotlib_manages_backends(): | ||
from matplotlib.backends.registry import backend_registry | ||
|
||
ret = backend_registry.list_all() + backend_registry.list_gui_frameworks() | ||
else: | ||
from IPython.core import pylabtools | ||
|
||
ret = list(pylabtools.backends.keys()) | ||
|
||
return sorted(["auto"] + ret) |
Uh oh!
There was an error while loading. Please reload this page.