backend_inline.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. """A matplotlib backend for publishing figures via display_data"""
  2. # Copyright (c) IPython Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. from __future__ import print_function
  5. import matplotlib
  6. from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg # analysis: ignore
  7. from matplotlib import colors
  8. from matplotlib._pylab_helpers import Gcf
  9. from IPython.core.getipython import get_ipython
  10. from IPython.core.display import display
  11. from .config import InlineBackend
  12. def show(close=None, block=None):
  13. """Show all figures as SVG/PNG payloads sent to the IPython clients.
  14. Parameters
  15. ----------
  16. close : bool, optional
  17. If true, a ``plt.close('all')`` call is automatically issued after
  18. sending all the figures. If this is set, the figures will entirely
  19. removed from the internal list of figures.
  20. block : Not used.
  21. The `block` parameter is a Matplotlib experimental parameter.
  22. We accept it in the function signature for compatibility with other
  23. backends.
  24. """
  25. if close is None:
  26. close = InlineBackend.instance().close_figures
  27. try:
  28. for figure_manager in Gcf.get_all_fig_managers():
  29. display(
  30. figure_manager.canvas.figure,
  31. metadata=_fetch_figure_metadata(figure_manager.canvas.figure)
  32. )
  33. finally:
  34. show._to_draw = []
  35. # only call close('all') if any to close
  36. # close triggers gc.collect, which can be slow
  37. if close and Gcf.get_all_fig_managers():
  38. matplotlib.pyplot.close('all')
  39. # This flag will be reset by draw_if_interactive when called
  40. show._draw_called = False
  41. # list of figures to draw when flush_figures is called
  42. show._to_draw = []
  43. def draw_if_interactive():
  44. """
  45. Is called after every pylab drawing command
  46. """
  47. # signal that the current active figure should be sent at the end of
  48. # execution. Also sets the _draw_called flag, signaling that there will be
  49. # something to send. At the end of the code execution, a separate call to
  50. # flush_figures() will act upon these values
  51. manager = Gcf.get_active()
  52. if manager is None:
  53. return
  54. fig = manager.canvas.figure
  55. # Hack: matplotlib FigureManager objects in interacive backends (at least
  56. # in some of them) monkeypatch the figure object and add a .show() method
  57. # to it. This applies the same monkeypatch in order to support user code
  58. # that might expect `.show()` to be part of the official API of figure
  59. # objects.
  60. # For further reference:
  61. # https://github.com/ipython/ipython/issues/1612
  62. # https://github.com/matplotlib/matplotlib/issues/835
  63. if not hasattr(fig, 'show'):
  64. # Queue up `fig` for display
  65. fig.show = lambda *a: display(fig, metadata=_fetch_figure_metadata(fig))
  66. # If matplotlib was manually set to non-interactive mode, this function
  67. # should be a no-op (otherwise we'll generate duplicate plots, since a user
  68. # who set ioff() manually expects to make separate draw/show calls).
  69. if not matplotlib.is_interactive():
  70. return
  71. # ensure current figure will be drawn, and each subsequent call
  72. # of draw_if_interactive() moves the active figure to ensure it is
  73. # drawn last
  74. try:
  75. show._to_draw.remove(fig)
  76. except ValueError:
  77. # ensure it only appears in the draw list once
  78. pass
  79. # Queue up the figure for drawing in next show() call
  80. show._to_draw.append(fig)
  81. show._draw_called = True
  82. def flush_figures():
  83. """Send all figures that changed
  84. This is meant to be called automatically and will call show() if, during
  85. prior code execution, there had been any calls to draw_if_interactive.
  86. This function is meant to be used as a post_execute callback in IPython,
  87. so user-caused errors are handled with showtraceback() instead of being
  88. allowed to raise. If this function is not called from within IPython,
  89. then these exceptions will raise.
  90. """
  91. if not show._draw_called:
  92. return
  93. if InlineBackend.instance().close_figures:
  94. # ignore the tracking, just draw and close all figures
  95. try:
  96. return show(True)
  97. except Exception as e:
  98. # safely show traceback if in IPython, else raise
  99. ip = get_ipython()
  100. if ip is None:
  101. raise e
  102. else:
  103. ip.showtraceback()
  104. return
  105. try:
  106. # exclude any figures that were closed:
  107. active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()])
  108. for fig in [ fig for fig in show._to_draw if fig in active ]:
  109. try:
  110. display(fig, metadata=_fetch_figure_metadata(fig))
  111. except Exception as e:
  112. # safely show traceback if in IPython, else raise
  113. ip = get_ipython()
  114. if ip is None:
  115. raise e
  116. else:
  117. ip.showtraceback()
  118. return
  119. finally:
  120. # clear flags for next round
  121. show._to_draw = []
  122. show._draw_called = False
  123. # Changes to matplotlib in version 1.2 requires a mpl backend to supply a default
  124. # figurecanvas. This is set here to a Agg canvas
  125. # See https://github.com/matplotlib/matplotlib/pull/1125
  126. FigureCanvas = FigureCanvasAgg
  127. def _enable_matplotlib_integration():
  128. """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend."""
  129. from matplotlib import get_backend
  130. ip = get_ipython()
  131. backend = get_backend()
  132. if ip and backend == 'module://%s' % __name__:
  133. from IPython.core.pylabtools import configure_inline_support, activate_matplotlib
  134. try:
  135. activate_matplotlib(backend)
  136. configure_inline_support(ip, backend)
  137. except (ImportError, AttributeError):
  138. # bugs may cause a circular import on Python 2
  139. def configure_once(*args):
  140. activate_matplotlib(backend)
  141. configure_inline_support(ip, backend)
  142. ip.events.unregister('post_run_cell', configure_once)
  143. ip.events.register('post_run_cell', configure_once)
  144. _enable_matplotlib_integration()
  145. def _fetch_figure_metadata(fig):
  146. """Get some metadata to help with displaying a figure."""
  147. # determine if a background is needed for legibility
  148. if _is_transparent(fig.get_facecolor()):
  149. # the background is transparent
  150. ticksLight = _is_light([label.get_color()
  151. for axes in fig.axes
  152. for axis in (axes.xaxis, axes.yaxis)
  153. for label in axis.get_ticklabels()])
  154. if ticksLight.size and (ticksLight == ticksLight[0]).all():
  155. # there are one or more tick labels, all with the same lightness
  156. return {'needs_background': 'dark' if ticksLight[0] else 'light'}
  157. return None
  158. def _is_light(color):
  159. """Determines if a color (or each of a sequence of colors) is light (as
  160. opposed to dark). Based on ITU BT.601 luminance formula (see
  161. https://stackoverflow.com/a/596241)."""
  162. rgbaArr = colors.to_rgba_array(color)
  163. return rgbaArr[:,:3].dot((.299, .587, .114)) > .5
  164. def _is_transparent(color):
  165. """Determine transparency from alpha."""
  166. rgba = colors.to_rgba(color)
  167. return rgba[3] < .5