pylabtools.py 13 KB

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