pylabtools.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. # -*- coding: utf-8 -*-
  2. """Pylab (matplotlib) support utilities."""
  3. # Copyright (c) IPython Development Team.
  4. # Distributed under the terms of the Modified BSD License.
  5. from io import BytesIO
  6. from binascii import b2a_base64
  7. from functools import partial
  8. import warnings
  9. from IPython.core.display import _pngxy
  10. from IPython.utils.decorators import flag_calls
  11. # If user specifies a GUI, that dictates the backend, otherwise we read the
  12. # user's mpl default from the mpl rc structure
  13. backends = {
  14. "tk": "TkAgg",
  15. "gtk": "GTKAgg",
  16. "gtk3": "GTK3Agg",
  17. "gtk4": "GTK4Agg",
  18. "wx": "WXAgg",
  19. "qt4": "Qt4Agg",
  20. "qt5": "Qt5Agg",
  21. "qt6": "QtAgg",
  22. "qt": "QtAgg",
  23. "osx": "MacOSX",
  24. "nbagg": "nbAgg",
  25. "webagg": "WebAgg",
  26. "notebook": "nbAgg",
  27. "agg": "agg",
  28. "svg": "svg",
  29. "pdf": "pdf",
  30. "ps": "ps",
  31. "inline": "module://matplotlib_inline.backend_inline",
  32. "ipympl": "module://ipympl.backend_nbagg",
  33. "widget": "module://ipympl.backend_nbagg",
  34. }
  35. # We also need a reverse backends2guis mapping that will properly choose which
  36. # GUI support to activate based on the desired matplotlib backend. For the
  37. # most part it's just a reverse of the above dict, but we also need to add a
  38. # few others that map to the same GUI manually:
  39. backend2gui = dict(zip(backends.values(), backends.keys()))
  40. # In the reverse mapping, there are a few extra valid matplotlib backends that
  41. # map to the same GUI support
  42. backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk"
  43. backend2gui["GTK3Cairo"] = "gtk3"
  44. backend2gui["GTK4Cairo"] = "gtk4"
  45. backend2gui["WX"] = "wx"
  46. backend2gui["CocoaAgg"] = "osx"
  47. # There needs to be a hysteresis here as the new QtAgg Matplotlib backend
  48. # supports either Qt5 or Qt6 and the IPython qt event loop support Qt4, Qt5,
  49. # and Qt6.
  50. backend2gui["QtAgg"] = "qt"
  51. backend2gui["Qt4Agg"] = "qt4"
  52. backend2gui["Qt5Agg"] = "qt5"
  53. # And some backends that don't need GUI integration
  54. del backend2gui["nbAgg"]
  55. del backend2gui["agg"]
  56. del backend2gui["svg"]
  57. del backend2gui["pdf"]
  58. del backend2gui["ps"]
  59. del backend2gui["module://matplotlib_inline.backend_inline"]
  60. del backend2gui["module://ipympl.backend_nbagg"]
  61. #-----------------------------------------------------------------------------
  62. # Matplotlib utilities
  63. #-----------------------------------------------------------------------------
  64. def getfigs(*fig_nums):
  65. """Get a list of matplotlib figures by figure numbers.
  66. If no arguments are given, all available figures are returned. If the
  67. argument list contains references to invalid figures, a warning is printed
  68. but the function continues pasting further figures.
  69. Parameters
  70. ----------
  71. figs : tuple
  72. A tuple of ints giving the figure numbers of the figures to return.
  73. """
  74. from matplotlib._pylab_helpers import Gcf
  75. if not fig_nums:
  76. fig_managers = Gcf.get_all_fig_managers()
  77. return [fm.canvas.figure for fm in fig_managers]
  78. else:
  79. figs = []
  80. for num in fig_nums:
  81. f = Gcf.figs.get(num)
  82. if f is None:
  83. print('Warning: figure %s not available.' % num)
  84. else:
  85. figs.append(f.canvas.figure)
  86. return figs
  87. def figsize(sizex, sizey):
  88. """Set the default figure size to be [sizex, sizey].
  89. This is just an easy to remember, convenience wrapper that sets::
  90. matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
  91. """
  92. import matplotlib
  93. matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
  94. def print_figure(fig, fmt="png", bbox_inches="tight", base64=False, **kwargs):
  95. """Print a figure to an image, and return the resulting file data
  96. Returned data will be bytes unless ``fmt='svg'``,
  97. in which case it will be unicode.
  98. Any keyword args are passed to fig.canvas.print_figure,
  99. such as ``quality`` or ``bbox_inches``.
  100. If `base64` is True, return base64-encoded str instead of raw bytes
  101. for binary-encoded image formats
  102. .. versionadded:: 7.29
  103. base64 argument
  104. """
  105. # When there's an empty figure, we shouldn't return anything, otherwise we
  106. # get big blank areas in the qt console.
  107. if not fig.axes and not fig.lines:
  108. return
  109. dpi = fig.dpi
  110. if fmt == 'retina':
  111. dpi = dpi * 2
  112. fmt = 'png'
  113. # build keyword args
  114. kw = {
  115. "format":fmt,
  116. "facecolor":fig.get_facecolor(),
  117. "edgecolor":fig.get_edgecolor(),
  118. "dpi":dpi,
  119. "bbox_inches":bbox_inches,
  120. }
  121. # **kwargs get higher priority
  122. kw.update(kwargs)
  123. bytes_io = BytesIO()
  124. if fig.canvas is None:
  125. from matplotlib.backend_bases import FigureCanvasBase
  126. FigureCanvasBase(fig)
  127. fig.canvas.print_figure(bytes_io, **kw)
  128. data = bytes_io.getvalue()
  129. if fmt == 'svg':
  130. data = data.decode('utf-8')
  131. elif base64:
  132. data = b2a_base64(data, newline=False).decode("ascii")
  133. return data
  134. def retina_figure(fig, base64=False, **kwargs):
  135. """format a figure as a pixel-doubled (retina) PNG
  136. If `base64` is True, return base64-encoded str instead of raw bytes
  137. for binary-encoded image formats
  138. .. versionadded:: 7.29
  139. base64 argument
  140. """
  141. pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
  142. # Make sure that retina_figure acts just like print_figure and returns
  143. # None when the figure is empty.
  144. if pngdata is None:
  145. return
  146. w, h = _pngxy(pngdata)
  147. metadata = {"width": w//2, "height":h//2}
  148. if base64:
  149. pngdata = b2a_base64(pngdata, newline=False).decode("ascii")
  150. return pngdata, metadata
  151. # We need a little factory function here to create the closure where
  152. # safe_execfile can live.
  153. def mpl_runner(safe_execfile):
  154. """Factory to return a matplotlib-enabled runner for %run.
  155. Parameters
  156. ----------
  157. safe_execfile : function
  158. This must be a function with the same interface as the
  159. :meth:`safe_execfile` method of IPython.
  160. Returns
  161. -------
  162. A function suitable for use as the ``runner`` argument of the %run magic
  163. function.
  164. """
  165. def mpl_execfile(fname,*where,**kw):
  166. """matplotlib-aware wrapper around safe_execfile.
  167. Its interface is identical to that of the :func:`execfile` builtin.
  168. This is ultimately a call to execfile(), but wrapped in safeties to
  169. properly handle interactive rendering."""
  170. import matplotlib
  171. import matplotlib.pyplot as plt
  172. #print '*** Matplotlib runner ***' # dbg
  173. # turn off rendering until end of script
  174. with matplotlib.rc_context({"interactive": False}):
  175. safe_execfile(fname, *where, **kw)
  176. if matplotlib.is_interactive():
  177. plt.show()
  178. # make rendering call now, if the user tried to do it
  179. if plt.draw_if_interactive.called:
  180. plt.draw()
  181. plt.draw_if_interactive.called = False
  182. # re-draw everything that is stale
  183. try:
  184. da = plt.draw_all
  185. except AttributeError:
  186. pass
  187. else:
  188. da()
  189. return mpl_execfile
  190. def _reshow_nbagg_figure(fig):
  191. """reshow an nbagg figure"""
  192. try:
  193. reshow = fig.canvas.manager.reshow
  194. except AttributeError as e:
  195. raise NotImplementedError() from e
  196. else:
  197. reshow()
  198. def select_figure_formats(shell, formats, **kwargs):
  199. """Select figure formats for the inline backend.
  200. Parameters
  201. ----------
  202. shell : InteractiveShell
  203. The main IPython instance.
  204. formats : str or set
  205. One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
  206. **kwargs : any
  207. Extra keyword arguments to be passed to fig.canvas.print_figure.
  208. """
  209. import matplotlib
  210. from matplotlib.figure import Figure
  211. svg_formatter = shell.display_formatter.formatters['image/svg+xml']
  212. png_formatter = shell.display_formatter.formatters['image/png']
  213. jpg_formatter = shell.display_formatter.formatters['image/jpeg']
  214. pdf_formatter = shell.display_formatter.formatters['application/pdf']
  215. if isinstance(formats, str):
  216. formats = {formats}
  217. # cast in case of list / tuple
  218. formats = set(formats)
  219. [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ]
  220. mplbackend = matplotlib.get_backend().lower()
  221. if mplbackend == 'nbagg' or mplbackend == 'module://ipympl.backend_nbagg':
  222. formatter = shell.display_formatter.ipython_display_formatter
  223. formatter.for_type(Figure, _reshow_nbagg_figure)
  224. supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'}
  225. bad = formats.difference(supported)
  226. if bad:
  227. bs = "%s" % ','.join([repr(f) for f in bad])
  228. gs = "%s" % ','.join([repr(f) for f in supported])
  229. raise ValueError("supported formats are: %s not %s" % (gs, bs))
  230. if "png" in formats:
  231. png_formatter.for_type(
  232. Figure, partial(print_figure, fmt="png", base64=True, **kwargs)
  233. )
  234. if "retina" in formats or "png2x" in formats:
  235. png_formatter.for_type(Figure, partial(retina_figure, base64=True, **kwargs))
  236. if "jpg" in formats or "jpeg" in formats:
  237. jpg_formatter.for_type(
  238. Figure, partial(print_figure, fmt="jpg", base64=True, **kwargs)
  239. )
  240. if "svg" in formats:
  241. svg_formatter.for_type(Figure, partial(print_figure, fmt="svg", **kwargs))
  242. if "pdf" in formats:
  243. pdf_formatter.for_type(
  244. Figure, partial(print_figure, fmt="pdf", base64=True, **kwargs)
  245. )
  246. #-----------------------------------------------------------------------------
  247. # Code for initializing matplotlib and importing pylab
  248. #-----------------------------------------------------------------------------
  249. def find_gui_and_backend(gui=None, gui_select=None):
  250. """Given a gui string return the gui and mpl backend.
  251. Parameters
  252. ----------
  253. gui : str
  254. Can be one of ('tk','gtk','wx','qt','qt4','inline','agg').
  255. gui_select : str
  256. Can be one of ('tk','gtk','wx','qt','qt4','inline').
  257. This is any gui already selected by the shell.
  258. Returns
  259. -------
  260. A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
  261. 'WXAgg','Qt4Agg','module://matplotlib_inline.backend_inline','agg').
  262. """
  263. import matplotlib
  264. has_unified_qt_backend = getattr(matplotlib, "__version_info__", (0, 0)) >= (3, 5)
  265. backends_ = dict(backends)
  266. if not has_unified_qt_backend:
  267. backends_["qt"] = "qt5agg"
  268. if gui and gui != 'auto':
  269. # select backend based on requested gui
  270. backend = backends_[gui]
  271. if gui == 'agg':
  272. gui = None
  273. else:
  274. # We need to read the backend from the original data structure, *not*
  275. # from mpl.rcParams, since a prior invocation of %matplotlib may have
  276. # overwritten that.
  277. # WARNING: this assumes matplotlib 1.1 or newer!!
  278. backend = matplotlib.rcParamsOrig['backend']
  279. # In this case, we need to find what the appropriate gui selection call
  280. # should be for IPython, so we can activate inputhook accordingly
  281. gui = backend2gui.get(backend, None)
  282. # If we have already had a gui active, we need it and inline are the
  283. # ones allowed.
  284. if gui_select and gui != gui_select:
  285. gui = gui_select
  286. backend = backends_[gui]
  287. return gui, backend
  288. def activate_matplotlib(backend):
  289. """Activate the given backend and set interactive to True."""
  290. import matplotlib
  291. matplotlib.interactive(True)
  292. # Matplotlib had a bug where even switch_backend could not force
  293. # the rcParam to update. This needs to be set *before* the module
  294. # magic of switch_backend().
  295. matplotlib.rcParams['backend'] = backend
  296. # Due to circular imports, pyplot may be only partially initialised
  297. # when this function runs.
  298. # So avoid needing matplotlib attribute-lookup to access pyplot.
  299. from matplotlib import pyplot as plt
  300. plt.switch_backend(backend)
  301. plt.show._needmain = False
  302. # We need to detect at runtime whether show() is called by the user.
  303. # For this, we wrap it into a decorator which adds a 'called' flag.
  304. plt.draw_if_interactive = flag_calls(plt.draw_if_interactive)
  305. def import_pylab(user_ns, import_all=True):
  306. """Populate the namespace with pylab-related values.
  307. Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
  308. Also imports a few names from IPython (figsize, display, getfigs)
  309. """
  310. # Import numpy as np/pyplot as plt are conventions we're trying to
  311. # somewhat standardize on. Making them available to users by default
  312. # will greatly help this.
  313. s = ("import numpy\n"
  314. "import matplotlib\n"
  315. "from matplotlib import pylab, mlab, pyplot\n"
  316. "np = numpy\n"
  317. "plt = pyplot\n"
  318. )
  319. exec(s, user_ns)
  320. if import_all:
  321. s = ("from matplotlib.pylab import *\n"
  322. "from numpy import *\n")
  323. exec(s, user_ns)
  324. # IPython symbols to add
  325. user_ns['figsize'] = figsize
  326. from IPython.display import display
  327. # Add display and getfigs to the user's namespace
  328. user_ns['display'] = display
  329. user_ns['getfigs'] = getfigs
  330. def configure_inline_support(shell, backend):
  331. """
  332. .. deprecated:: 7.23
  333. use `matplotlib_inline.backend_inline.configure_inline_support()`
  334. Configure an IPython shell object for matplotlib use.
  335. Parameters
  336. ----------
  337. shell : InteractiveShell instance
  338. backend : matplotlib backend
  339. """
  340. warnings.warn(
  341. "`configure_inline_support` is deprecated since IPython 7.23, directly "
  342. "use `matplotlib_inline.backend_inline.configure_inline_support()`",
  343. DeprecationWarning,
  344. stacklevel=2,
  345. )
  346. from matplotlib_inline.backend_inline import (
  347. configure_inline_support as configure_inline_support_orig,
  348. )
  349. configure_inline_support_orig(shell, backend)