123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- """A matplotlib backend for publishing figures via display_data"""
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the BSD 3-Clause License.
- import matplotlib
- from matplotlib import colors
- from matplotlib.backends import backend_agg
- from matplotlib.backends.backend_agg import FigureCanvasAgg
- from matplotlib._pylab_helpers import Gcf
- from matplotlib.figure import Figure
- from IPython.core.interactiveshell import InteractiveShell
- from IPython.core.getipython import get_ipython
- from IPython.core.pylabtools import select_figure_formats
- from IPython.display import display
- from .config import InlineBackend
- def new_figure_manager(num, *args, FigureClass=Figure, **kwargs):
- """
- Return a new figure manager for a new figure instance.
- This function is part of the API expected by Matplotlib backends.
- """
- return new_figure_manager_given_figure(num, FigureClass(*args, **kwargs))
- def new_figure_manager_given_figure(num, figure):
- """
- Return a new figure manager for a given figure instance.
- This function is part of the API expected by Matplotlib backends.
- """
- manager = backend_agg.new_figure_manager_given_figure(num, 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(figure, 'show'):
- # Queue up `figure` for display
- figure.show = lambda *a: display(
- figure, metadata=_fetch_figure_metadata(figure))
- # 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 manager
- # 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(figure)
- 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(figure)
- show._draw_called = True
- return manager
- 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 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
- try:
- 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
- # 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 configure_inline_support(shell, backend):
- """Configure an IPython shell object for matplotlib use.
- Parameters
- ----------
- shell : InteractiveShell instance
- backend : matplotlib backend
- """
- # If using our svg payload backend, register the post-execution
- # function that will pick up the results for display. This can only be
- # done with access to the real shell object.
- cfg = InlineBackend.instance(parent=shell)
- cfg.shell = shell
- if cfg not in shell.configurables:
- shell.configurables.append(cfg)
- if backend == 'module://matplotlib_inline.backend_inline':
- shell.events.register('post_execute', flush_figures)
- # Save rcParams that will be overwrittern
- shell._saved_rcParams = {}
- for k in cfg.rc:
- shell._saved_rcParams[k] = matplotlib.rcParams[k]
- # load inline_rc
- matplotlib.rcParams.update(cfg.rc)
- new_backend_name = "inline"
- else:
- try:
- shell.events.unregister('post_execute', flush_figures)
- except ValueError:
- pass
- if hasattr(shell, '_saved_rcParams'):
- matplotlib.rcParams.update(shell._saved_rcParams)
- del shell._saved_rcParams
- new_backend_name = "other"
- # only enable the formats once -> don't change the enabled formats (which the user may
- # has changed) when getting another "%matplotlib inline" call.
- # See https://github.com/ipython/ipykernel/issues/29
- cur_backend = getattr(configure_inline_support, "current_backend", "unset")
- if new_backend_name != cur_backend:
- # Setup the default figure format
- select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs)
- configure_inline_support.current_backend = new_backend_name
- 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 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
- def set_matplotlib_formats(*formats, **kwargs):
- """Select figure formats for the inline backend. Optionally pass quality for JPEG.
- For example, this enables PNG and JPEG output with a JPEG quality of 90%::
- In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
- To set this in your config files use the following::
- c.InlineBackend.figure_formats = {'png', 'jpeg'}
- c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
- Parameters
- ----------
- *formats : strs
- One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
- **kwargs
- Keyword args will be relayed to ``figure.canvas.print_figure``.
- """
- # build kwargs, starting with InlineBackend config
- cfg = InlineBackend.instance()
- kw = {}
- kw.update(cfg.print_figure_kwargs)
- kw.update(**kwargs)
- shell = InteractiveShell.instance()
- select_figure_formats(shell, formats, **kw)
- def set_matplotlib_close(close=True):
- """Set whether the inline backend closes all figures automatically or not.
- By default, the inline backend used in the IPython Notebook will close all
- matplotlib figures automatically after each cell is run. This means that
- plots in different cells won't interfere. Sometimes, you may want to make
- a plot in one cell and then refine it in later cells. This can be accomplished
- by::
- In [1]: set_matplotlib_close(False)
- To set this in your config files use the following::
- c.InlineBackend.close_figures = False
- Parameters
- ----------
- close : bool
- Should all matplotlib figures be automatically closed after each cell is
- run?
- """
- cfg = InlineBackend.instance()
- cfg.close_figures = close
|