123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- """A matplotlib backend for publishing figures via display_data"""
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the Modified BSD License.
- from __future__ import print_function
- import matplotlib
- from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg # analysis: ignore
- from matplotlib import colors
- from matplotlib._pylab_helpers import Gcf
- from IPython.core.getipython import get_ipython
- from IPython.core.display import display
- from .config import InlineBackend
- def show(close=None, block=None):
- """Show all figures as SVG/PNG payloads sent to the IPython clients.
- Parameters
- ----------
- close : bool, optional
- If true, a ``plt.close('all')`` call is automatically issued after
- sending all the figures. If this is set, the figures will entirely
- removed from the internal list of figures.
- block : Not used.
- The `block` parameter is a Matplotlib experimental parameter.
- We accept it in the function signature for compatibility with other
- backends.
- """
- if close is None:
- close = InlineBackend.instance().close_figures
- try:
- for figure_manager in Gcf.get_all_fig_managers():
- display(
- figure_manager.canvas.figure,
- metadata=_fetch_figure_metadata(figure_manager.canvas.figure)
- )
- finally:
- show._to_draw = []
- # only call close('all') if any to close
- # close triggers gc.collect, which can be slow
- if close and Gcf.get_all_fig_managers():
- matplotlib.pyplot.close('all')
- # This flag will be reset by draw_if_interactive when called
- show._draw_called = False
- # list of figures to draw when flush_figures is called
- show._to_draw = []
- def draw_if_interactive():
- """
- Is called after every pylab drawing command
- """
- # signal that the current active figure should be sent at the end of
- # execution. Also sets the _draw_called flag, signaling that there will be
- # something to send. At the end of the code execution, a separate call to
- # flush_figures() will act upon these values
- manager = Gcf.get_active()
- if manager is None:
- return
- fig = manager.canvas.figure
- # Hack: matplotlib FigureManager objects in interacive backends (at least
- # in some of them) monkeypatch the figure object and add a .show() method
- # to it. This applies the same monkeypatch in order to support user code
- # that might expect `.show()` to be part of the official API of figure
- # objects.
- # For further reference:
- # https://github.com/ipython/ipython/issues/1612
- # https://github.com/matplotlib/matplotlib/issues/835
- if not hasattr(fig, 'show'):
- # Queue up `fig` for display
- fig.show = lambda *a: display(fig, metadata=_fetch_figure_metadata(fig))
- # If matplotlib was manually set to non-interactive mode, this function
- # should be a no-op (otherwise we'll generate duplicate plots, since a user
- # who set ioff() manually expects to make separate draw/show calls).
- if not matplotlib.is_interactive():
- return
- # ensure current figure will be drawn, and each subsequent call
- # of draw_if_interactive() moves the active figure to ensure it is
- # drawn last
- try:
- show._to_draw.remove(fig)
- except ValueError:
- # ensure it only appears in the draw list once
- pass
- # Queue up the figure for drawing in next show() call
- show._to_draw.append(fig)
- show._draw_called = True
- def flush_figures():
- """Send all figures that changed
- This is meant to be called automatically and will call show() if, during
- prior code execution, there had been any calls to draw_if_interactive.
- This function is meant to be used as a post_execute callback in IPython,
- so user-caused errors are handled with showtraceback() instead of being
- allowed to raise. If this function is not called from within IPython,
- then these exceptions will raise.
- """
- if not show._draw_called:
- return
- if InlineBackend.instance().close_figures:
- # ignore the tracking, just draw and close all figures
- try:
- return show(True)
- except Exception as e:
- # safely show traceback if in IPython, else raise
- ip = get_ipython()
- if ip is None:
- raise e
- else:
- ip.showtraceback()
- return
- try:
- # exclude any figures that were closed:
- active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()])
- for fig in [ fig for fig in show._to_draw if fig in active ]:
- try:
- display(fig, metadata=_fetch_figure_metadata(fig))
- except Exception as e:
- # safely show traceback if in IPython, else raise
- ip = get_ipython()
- if ip is None:
- raise e
- else:
- ip.showtraceback()
- return
- finally:
- # clear flags for next round
- show._to_draw = []
- show._draw_called = False
- # Changes to matplotlib in version 1.2 requires a mpl backend to supply a default
- # figurecanvas. This is set here to a Agg canvas
- # See https://github.com/matplotlib/matplotlib/pull/1125
- FigureCanvas = FigureCanvasAgg
- def _enable_matplotlib_integration():
- """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend."""
- from matplotlib import get_backend
- ip = get_ipython()
- backend = get_backend()
- if ip and backend == 'module://%s' % __name__:
- from IPython.core.pylabtools import configure_inline_support, activate_matplotlib
- try:
- activate_matplotlib(backend)
- configure_inline_support(ip, backend)
- except (ImportError, AttributeError):
- # bugs may cause a circular import on Python 2
- def configure_once(*args):
- activate_matplotlib(backend)
- configure_inline_support(ip, backend)
- ip.events.unregister('post_run_cell', configure_once)
- ip.events.register('post_run_cell', configure_once)
- _enable_matplotlib_integration()
- def _fetch_figure_metadata(fig):
- """Get some metadata to help with displaying a figure."""
- # determine if a background is needed for legibility
- if _is_transparent(fig.get_facecolor()):
- # the background is transparent
- ticksLight = _is_light([label.get_color()
- for axes in fig.axes
- for axis in (axes.xaxis, axes.yaxis)
- for label in axis.get_ticklabels()])
- if ticksLight.size and (ticksLight == ticksLight[0]).all():
- # there are one or more tick labels, all with the same lightness
- return {'needs_background': 'dark' if ticksLight[0] else 'light'}
- return None
- def _is_light(color):
- """Determines if a color (or each of a sequence of colors) is light (as
- opposed to dark). Based on ITU BT.601 luminance formula (see
- https://stackoverflow.com/a/596241)."""
- rgbaArr = colors.to_rgba_array(color)
- return rgbaArr[:,:3].dot((.299, .587, .114)) > .5
- def _is_transparent(color):
- """Determine transparency from alpha."""
- rgba = colors.to_rgba(color)
- return rgba[3] < .5
|