-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy path_manage_backend.py
176 lines (144 loc) · 5.51 KB
/
_manage_backend.py
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import importlib
import sys
import logging
import types
from matplotlib import cbook, rcsetup
from matplotlib import rcParams, rcParamsDefault
import matplotlib.backend_bases
_backend_mod = None
_log = logging.getLogger(__name__)
def current_backend_module():
"""
Get the currently active backend module, selecting one if needed.
Returns
-------
matplotlib.backend_bases._Backend
"""
if _backend_mod is None:
select_gui_toolkit()
return _backend_mod
def select_gui_toolkit(newbackend=None):
"""
Select the GUI toolkit to use.
The argument is case-insensitive. Switching between GUI toolkits is
possible only if no event loop for another interactive backend has started.
Switching to and from non-interactive backends is always possible.
Parameters
----------
newbackend : Union[str, _Backend]
The name of the backend to use or a _Backend class to use.
Returns
-------
_Backend
The backend selected.
"""
global _backend_mod
# work-around the sentinel resolution in Matplotlib 😱
if newbackend is None:
newbackend = dict.__getitem__(rcParams, "backend")
if newbackend is rcsetup._auto_backend_sentinel:
current_framework = cbook._get_running_interactive_framework()
mapping = {
"qt": "qtagg",
"gtk3": "gtk3agg",
"gtk4": "gtk4agg",
"wx": "wxagg",
"tk": "tkagg",
"macosx": "macosx",
"headless": "agg",
}
best_guess = mapping.get(current_framework, None)
if best_guess is not None:
candidates = [best_guess]
else:
candidates = []
candidates += ["macosx", "qtagg", "gtk3agg", "tkagg", "wxagg"]
# Don't try to fallback on the cairo-based backends as they each have
# an additional dependency (pycairo) over the agg-based backend, and
# are of worse quality.
for candidate in candidates:
try:
return select_gui_toolkit(candidate)
except ImportError:
continue
else:
# Switching to Agg should always succeed; if it doesn't, let the
# exception propagate out.
return select_gui_toolkit("agg")
if isinstance(newbackend, str):
# Backends are implemented as modules, but "inherit" default method
# implementations from backend_bases._Backend. This is achieved by
# creating a "class" that inherits from backend_bases._Backend and whose
# body is filled with the module's globals.
if newbackend.lower() == "tkagg":
backend_name = f"mpl_gui._patched_backends.{newbackend.lower()}"
else:
backend_name = cbook._backend_module_name(newbackend)
mod = importlib.import_module(backend_name)
if hasattr(mod, "Backend"):
orig_class = mod.Backend
else:
class orig_class(matplotlib.backend_bases._Backend):
locals().update(vars(mod))
@classmethod
def mainloop(cls):
return mod.Show().mainloop()
class BackendClass(orig_class):
@classmethod
def show_managers(cls, *, managers, block):
if not managers:
return
for manager in managers:
manager.show() # Emits a warning for non-interactive backend
manager.canvas.draw_idle()
if cls.mainloop is None:
return
if block:
try:
cls.FigureManager._active_managers = managers
cls.mainloop()
finally:
cls.FigureManager._active_managers = None
if not hasattr(BackendClass.FigureManager, "_active_managers"):
BackendClass.FigureManager._active_managers = None
rc_params_string = newbackend
else:
BackendClass = newbackend
mod_name = f"_backend_mod_{id(BackendClass)}"
rc_params_string = f"module://{mod_name}"
mod = types.ModuleType(mod_name)
mod.Backend = BackendClass
sys.modules[mod_name] = mod
required_framework = getattr(
BackendClass.FigureCanvas, "required_interactive_framework", None
)
if required_framework is not None:
current_framework = cbook._get_running_interactive_framework()
if (
current_framework
and required_framework
and current_framework != required_framework
):
raise ImportError(
"Cannot load backend {!r} which requires the {!r} interactive "
"framework, as {!r} is currently running".format(
newbackend, required_framework, current_framework
)
)
_log.debug(
"Loaded backend %s version %s.", newbackend, BackendClass.backend_version
)
rcParams["backend"] = rcParamsDefault["backend"] = rc_params_string
# is IPython imported?
mod_ipython = sys.modules.get("IPython")
if mod_ipython:
# if so are we in an IPython session
ip = mod_ipython.get_ipython()
if ip:
# macosx -> osx mapping for the osx backend in ipython
if required_framework == "macosx":
required_framework = "osx"
ip.enable_gui(required_framework)
# remember to set the global variable
_backend_mod = BackendClass
return BackendClass