hub/venv/lib/python3.7/site-packages/ipykernel/pylab/backend_inline.py

201 lines
7.3 KiB
Python
Raw Normal View History

"""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,
new_figure_manager_given_figure,
) # analysis: ignore
from matplotlib import colors
from matplotlib._pylab_helpers import Gcf
from IPython.core.getipython import get_ipython
from IPython.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