You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			312 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			312 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
"""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 in ('inline', '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 in ('inline', 'module://matplotlib_inline.backend_inline'):
 | 
						|
        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
 |