Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/IPython/core/pylabtools.py: 22%

160 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:05 +0000

1# -*- coding: utf-8 -*- 

2"""Pylab (matplotlib) support utilities.""" 

3 

4# Copyright (c) IPython Development Team. 

5# Distributed under the terms of the Modified BSD License. 

6 

7from io import BytesIO 

8from binascii import b2a_base64 

9from functools import partial 

10import warnings 

11 

12from IPython.core.display import _pngxy 

13from IPython.utils.decorators import flag_calls 

14 

15# If user specifies a GUI, that dictates the backend, otherwise we read the 

16# user's mpl default from the mpl rc structure 

17backends = { 

18 "tk": "TkAgg", 

19 "gtk": "GTKAgg", 

20 "gtk3": "GTK3Agg", 

21 "gtk4": "GTK4Agg", 

22 "wx": "WXAgg", 

23 "qt4": "Qt4Agg", 

24 "qt5": "Qt5Agg", 

25 "qt6": "QtAgg", 

26 "qt": "QtAgg", 

27 "osx": "MacOSX", 

28 "nbagg": "nbAgg", 

29 "webagg": "WebAgg", 

30 "notebook": "nbAgg", 

31 "agg": "agg", 

32 "svg": "svg", 

33 "pdf": "pdf", 

34 "ps": "ps", 

35 "inline": "module://matplotlib_inline.backend_inline", 

36 "ipympl": "module://ipympl.backend_nbagg", 

37 "widget": "module://ipympl.backend_nbagg", 

38} 

39 

40# We also need a reverse backends2guis mapping that will properly choose which 

41# GUI support to activate based on the desired matplotlib backend. For the 

42# most part it's just a reverse of the above dict, but we also need to add a 

43# few others that map to the same GUI manually: 

44backend2gui = dict(zip(backends.values(), backends.keys())) 

45# In the reverse mapping, there are a few extra valid matplotlib backends that 

46# map to the same GUI support 

47backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk" 

48backend2gui["GTK3Cairo"] = "gtk3" 

49backend2gui["GTK4Cairo"] = "gtk4" 

50backend2gui["WX"] = "wx" 

51backend2gui["CocoaAgg"] = "osx" 

52# There needs to be a hysteresis here as the new QtAgg Matplotlib backend 

53# supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5, 

54# and Qt6. 

55backend2gui["QtAgg"] = "qt" 

56backend2gui["Qt4Agg"] = "qt4" 

57backend2gui["Qt5Agg"] = "qt5" 

58 

59# And some backends that don't need GUI integration 

60del backend2gui["nbAgg"] 

61del backend2gui["agg"] 

62del backend2gui["svg"] 

63del backend2gui["pdf"] 

64del backend2gui["ps"] 

65del backend2gui["module://matplotlib_inline.backend_inline"] 

66del backend2gui["module://ipympl.backend_nbagg"] 

67 

68#----------------------------------------------------------------------------- 

69# Matplotlib utilities 

70#----------------------------------------------------------------------------- 

71 

72 

73def getfigs(*fig_nums): 

74 """Get a list of matplotlib figures by figure numbers. 

75 

76 If no arguments are given, all available figures are returned. If the 

77 argument list contains references to invalid figures, a warning is printed 

78 but the function continues pasting further figures. 

79 

80 Parameters 

81 ---------- 

82 figs : tuple 

83 A tuple of ints giving the figure numbers of the figures to return. 

84 """ 

85 from matplotlib._pylab_helpers import Gcf 

86 if not fig_nums: 

87 fig_managers = Gcf.get_all_fig_managers() 

88 return [fm.canvas.figure for fm in fig_managers] 

89 else: 

90 figs = [] 

91 for num in fig_nums: 

92 f = Gcf.figs.get(num) 

93 if f is None: 

94 print('Warning: figure %s not available.' % num) 

95 else: 

96 figs.append(f.canvas.figure) 

97 return figs 

98 

99 

100def figsize(sizex, sizey): 

101 """Set the default figure size to be [sizex, sizey]. 

102 

103 This is just an easy to remember, convenience wrapper that sets:: 

104 

105 matplotlib.rcParams['figure.figsize'] = [sizex, sizey] 

106 """ 

107 import matplotlib 

108 matplotlib.rcParams['figure.figsize'] = [sizex, sizey] 

109 

110 

111def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs): 

112 """Print a figure to an image, and return the resulting file data 

113 

114 Returned data will be bytes unless ``fmt='svg'``, 

115 in which case it will be unicode. 

116 

117 Any keyword args are passed to fig.canvas.print_figure, 

118 such as ``quality`` or ``bbox_inches``. 

119 

120 If `base64` is True, return base64-encoded str instead of raw bytes 

121 for binary-encoded image formats 

122 

123 .. versionadded:: 7.29 

124 base64 argument 

125 """ 

126 # When there's an empty figure, we shouldn't return anything, otherwise we 

127 # get big blank areas in the qt console. 

128 if not fig.axes and not fig.lines: 

129 return 

130 

131 dpi = fig.dpi 

132 if fmt == 'retina': 

133 dpi = dpi * 2 

134 fmt = 'png' 

135 

136 # build keyword args 

137 kw = { 

138 "format":fmt, 

139 "facecolor":fig.get_facecolor(), 

140 "edgecolor":fig.get_edgecolor(), 

141 "dpi":dpi, 

142 "bbox_inches":bbox_inches, 

143 } 

144 # **kwargs get higher priority 

145 kw.update(kwargs) 

146 

147 bytes_io = BytesIO() 

148 if fig.canvas is None: 

149 from matplotlib.backend_bases import FigureCanvasBase 

150 FigureCanvasBase(fig) 

151 

152 fig.canvas.print_figure(bytes_io, **kw) 

153 data = bytes_io.getvalue() 

154 if fmt == 'svg': 

155 data = data.decode('utf-8') 

156 elif base64: 

157 data = b2a_base64(data, newline=False).decode("ascii") 

158 return data 

159 

160def retina_figure(fig, base64=False, **kwargs): 

161 """format a figure as a pixel-doubled (retina) PNG 

162 

163 If `base64` is True, return base64-encoded str instead of raw bytes 

164 for binary-encoded image formats 

165 

166 .. versionadded:: 7.29 

167 base64 argument 

168 """ 

169 pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs) 

170 # Make sure that retina_figure acts just like print_figure and returns 

171 # None when the figure is empty. 

172 if pngdata is None: 

173 return 

174 w, h = _pngxy(pngdata) 

175 metadata = {"width": w//2, "height":h//2} 

176 if base64: 

177 pngdata = b2a_base64(pngdata, newline=False).decode("ascii") 

178 return pngdata, metadata 

179 

180 

181# We need a little factory function here to create the closure where 

182# safe_execfile can live. 

183def mpl_runner(safe_execfile): 

184 """Factory to return a matplotlib-enabled runner for %run. 

185 

186 Parameters 

187 ---------- 

188 safe_execfile : function 

189 This must be a function with the same interface as the 

190 :meth:`safe_execfile` method of IPython. 

191 

192 Returns 

193 ------- 

194 A function suitable for use as the ``runner`` argument of the %run magic 

195 function. 

196 """ 

197 

198 def mpl_execfile(fname,*where,**kw): 

199 """matplotlib-aware wrapper around safe_execfile. 

200 

201 Its interface is identical to that of the :func:`execfile` builtin. 

202 

203 This is ultimately a call to execfile(), but wrapped in safeties to 

204 properly handle interactive rendering.""" 

205 

206 import matplotlib 

207 import matplotlib.pyplot as plt 

208 

209 #print '*** Matplotlib runner ***' # dbg 

210 # turn off rendering until end of script 

211 with matplotlib.rc_context({"interactive": False}): 

212 safe_execfile(fname, *where, **kw) 

213 

214 if matplotlib.is_interactive(): 

215 plt.show() 

216 

217 # make rendering call now, if the user tried to do it 

218 if plt.draw_if_interactive.called: 

219 plt.draw() 

220 plt.draw_if_interactive.called = False 

221 

222 # re-draw everything that is stale 

223 try: 

224 da = plt.draw_all 

225 except AttributeError: 

226 pass 

227 else: 

228 da() 

229 

230 return mpl_execfile 

231 

232 

233def _reshow_nbagg_figure(fig): 

234 """reshow an nbagg figure""" 

235 try: 

236 reshow = fig.canvas.manager.reshow 

237 except AttributeError as e: 

238 raise NotImplementedError() from e 

239 else: 

240 reshow() 

241 

242 

243def select_figure_formats(shell, formats, **kwargs): 

244 """Select figure formats for the inline backend. 

245 

246 Parameters 

247 ---------- 

248 shell : InteractiveShell 

249 The main IPython instance. 

250 formats : str or set 

251 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'. 

252 **kwargs : any 

253 Extra keyword arguments to be passed to fig.canvas.print_figure. 

254 """ 

255 import matplotlib 

256 from matplotlib.figure import Figure 

257 

258 svg_formatter = shell.display_formatter.formatters['image/svg+xml'] 

259 png_formatter = shell.display_formatter.formatters['image/png'] 

260 jpg_formatter = shell.display_formatter.formatters['image/jpeg'] 

261 pdf_formatter = shell.display_formatter.formatters['application/pdf'] 

262 

263 if isinstance(formats, str): 

264 formats = {formats} 

265 # cast in case of list / tuple 

266 formats = set(formats) 

267 

268 [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ] 

269 mplbackend = matplotlib.get_backend().lower() 

270 if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg': 

271 formatter = shell.display_formatter.ipython_display_formatter 

272 formatter.for_type(Figure, _reshow_nbagg_figure) 

273 

274 supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'} 

275 bad = formats.difference(supported) 

276 if bad: 

277 bs = "%s" % ','.join([repr(f) for f in bad]) 

278 gs = "%s" % ','.join([repr(f) for f in supported]) 

279 raise ValueError("supported formats are: %s not %s" % (gs, bs)) 

280 

281 if "png" in formats: 

282 png_formatter.for_type( 

283 Figure, partial(print_figure, fmt="png", base64=True, **kwargs) 

284 ) 

285 if "retina" in formats or "png2x" in formats: 

286 png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs)) 

287 if "jpg" in formats or "jpeg" in formats: 

288 jpg_formatter.for_type( 

289 Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs) 

290 ) 

291 if "svg" in formats: 

292 svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs)) 

293 if "pdf" in formats: 

294 pdf_formatter.for_type( 

295 Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs) 

296 ) 

297 

298#----------------------------------------------------------------------------- 

299# Code for initializing matplotlib and importing pylab 

300#----------------------------------------------------------------------------- 

301 

302 

303def find_gui_and_backend(gui=None, gui_select=None): 

304 """Given a gui string return the gui and mpl backend. 

305 

306 Parameters 

307 ---------- 

308 gui : str 

309 Can be one of ('tk','gtk','wx','qt','qt4','inline','agg'). 

310 gui_select : str 

311 Can be one of ('tk','gtk','wx','qt','qt4','inline'). 

312 This is any gui already selected by the shell. 

313 

314 Returns 

315 ------- 

316 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg', 

317 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg'). 

318 """ 

319 

320 import matplotlib 

321 

322 has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5) 

323 

324 backends_ = dict(backends) 

325 if not has_unified_qt_backend: 

326 backends_["qt"] = "qt5agg" 

327 

328 if gui and gui != 'auto': 

329 # select backend based on requested gui 

330 backend = backends_[gui] 

331 if gui == 'agg': 

332 gui = None 

333 else: 

334 # We need to read the backend from the original data structure, *not* 

335 # from mpl.rcParams, since a prior invocation of %matplotlib may have 

336 # overwritten that. 

337 # WARNING: this assumes matplotlib 1.1 or newer!! 

338 backend = matplotlib.rcParamsOrig['backend'] 

339 # In this case, we need to find what the appropriate gui selection call 

340 # should be for IPython, so we can activate inputhook accordingly 

341 gui = backend2gui.get(backend, None) 

342 

343 # If we have already had a gui active, we need it and inline are the 

344 # ones allowed. 

345 if gui_select and gui != gui_select: 

346 gui = gui_select 

347 backend = backends_[gui] 

348 

349 return gui, backend 

350 

351 

352def activate_matplotlib(backend): 

353 """Activate the given backend and set interactive to True.""" 

354 

355 import matplotlib 

356 matplotlib.interactive(True) 

357 

358 # Matplotlib had a bug where even switch_backend could not force 

359 # the rcParam to update. This needs to be set *before* the module 

360 # magic of switch_backend(). 

361 matplotlib.rcParams['backend'] = backend 

362 

363 # Due to circular imports, pyplot may be only partially initialised 

364 # when this function runs. 

365 # So avoid needing matplotlib attribute-lookup to access pyplot. 

366 from matplotlib import pyplot as plt 

367 

368 plt.switch_backend(backend) 

369 

370 plt.show._needmain = False 

371 # We need to detect at runtime whether show() is called by the user. 

372 # For this, we wrap it into a decorator which adds a 'called' flag. 

373 plt.draw_if_interactive = flag_calls(plt.draw_if_interactive) 

374 

375 

376def import_pylab(user_ns, import_all=True): 

377 """Populate the namespace with pylab-related values. 

378 

379 Imports matplotlib, pylab, numpy, and everything from pylab and numpy. 

380 

381 Also imports a few names from IPython (figsize, display, getfigs) 

382 

383 """ 

384 

385 # Import numpy as np/pyplot as plt are conventions we're trying to 

386 # somewhat standardize on. Making them available to users by default 

387 # will greatly help this. 

388 s = ("import numpy\n" 

389 "import matplotlib\n" 

390 "from matplotlib import pylab, mlab, pyplot\n" 

391 "np = numpy\n" 

392 "plt = pyplot\n" 

393 ) 

394 exec(s, user_ns) 

395 

396 if import_all: 

397 s = ("from matplotlib.pylab import *\n" 

398 "from numpy import *\n") 

399 exec(s, user_ns) 

400 

401 # IPython symbols to add 

402 user_ns['figsize'] = figsize 

403 from IPython.display import display 

404 # Add display and getfigs to the user's namespace 

405 user_ns['display'] = display 

406 user_ns['getfigs'] = getfigs 

407 

408 

409def configure_inline_support(shell, backend): 

410 """ 

411 .. deprecated:: 7.23 

412 

413 use `matplotlib_inline.backend_inline.configure_inline_support()` 

414 

415 Configure an IPython shell object for matplotlib use. 

416 

417 Parameters 

418 ---------- 

419 shell : InteractiveShell instance 

420 backend : matplotlib backend 

421 """ 

422 warnings.warn( 

423 "`configure_inline_support` is deprecated since IPython 7.23, directly " 

424 "use `matplotlib_inline.backend_inline.configure_inline_support()`", 

425 DeprecationWarning, 

426 stacklevel=2, 

427 ) 

428 

429 from matplotlib_inline.backend_inline import ( 

430 configure_inline_support as configure_inline_support_orig, 

431 ) 

432 

433 configure_inline_support_orig(shell, backend)