pylabtools.py 14 KB

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