|
"""A matplotlib backend for publishing figures via display_data""" |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not hasattr(figure, 'show'): |
|
|
|
figure.show = lambda *a: display( |
|
figure, metadata=_fetch_figure_metadata(figure)) |
|
|
|
|
|
|
|
|
|
if not matplotlib.is_interactive(): |
|
return manager |
|
|
|
|
|
|
|
|
|
try: |
|
show._to_draw.remove(figure) |
|
except ValueError: |
|
|
|
pass |
|
|
|
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 = [] |
|
|
|
|
|
if close and Gcf.get_all_fig_managers(): |
|
matplotlib.pyplot.close('all') |
|
|
|
|
|
|
|
show._draw_called = False |
|
|
|
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: |
|
|
|
try: |
|
return show(True) |
|
except Exception as e: |
|
|
|
ip = get_ipython() |
|
if ip is None: |
|
raise e |
|
else: |
|
ip.showtraceback() |
|
return |
|
|
|
|
|
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: |
|
|
|
ip = get_ipython() |
|
if ip is None: |
|
raise e |
|
else: |
|
ip.showtraceback() |
|
return |
|
finally: |
|
|
|
show._to_draw = [] |
|
show._draw_called = False |
|
|
|
|
|
|
|
|
|
|
|
FigureCanvas = FigureCanvasAgg |
|
|
|
|
|
def configure_inline_support(shell, backend): |
|
"""Configure an IPython shell object for matplotlib use. |
|
|
|
Parameters |
|
---------- |
|
shell : InteractiveShell instance |
|
|
|
backend : matplotlib backend |
|
""" |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
shell._saved_rcParams = {} |
|
for k in cfg.rc: |
|
shell._saved_rcParams[k] = matplotlib.rcParams[k] |
|
|
|
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" |
|
|
|
|
|
|
|
|
|
cur_backend = getattr(configure_inline_support, "current_backend", "unset") |
|
if new_backend_name != cur_backend: |
|
|
|
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): |
|
|
|
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.""" |
|
|
|
if _is_transparent(fig.get_facecolor()): |
|
|
|
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(): |
|
|
|
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``. |
|
""" |
|
|
|
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 |
|
|