diff --git a/LICENSE b/LICENSE index 1619c6dc0345162308e083596dc9341282001afb..b8350378e810121d96bc065a8ddaab7d2ec38308 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2019, +Copyright (c) 2019-2022, IPython Development Team. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 9ef91180b175712b690469c14a44199148129392..21c64188ce2e8d30179dc10242f133b0d65b3964 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,26 @@ # Matplotlib Inline Back-end for IPython and Jupyter +This package provides support for matplotlib to display figures directly inline in the Jupyter notebook and related clients, as shown below. + ## Installation With conda: ```bash -conda install -c conda-forge notebook matplotlib +conda install -c conda-forge matplotlib-inline ``` With pip: ```bash -pip install notebook matplotlib +pip install matplotlib-inline ``` ## Usage -This package is included in IPython and can be used in a Jupyter Notebook: +Note that in current versions of JupyterLab and Jupyter Notebook, the explicit use of the `%matplotlib inline` directive is not needed anymore, though other third-party clients may still require it. + +This will produce a figure immediately below: ```python %matplotlib inline @@ -28,3 +32,7 @@ x = np.linspace(0, 3*np.pi, 500) plt.plot(x, np.sin(x**2)) plt.title('A simple chirp'); ``` + +## License + +Licensed under the terms of the BSD 3-Clause License, by the IPython Development Team (see `LICENSE` file). diff --git a/matplotlib_inline/__init__.py b/matplotlib_inline/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f7ef1dd1a5f7e2f34add0f321caaf7742841d899 100644 --- a/matplotlib_inline/__init__.py +++ b/matplotlib_inline/__init__.py @@ -0,0 +1,2 @@ +from . import backend_inline, config # noqa +__version__ = "0.1.6" # noqa diff --git a/matplotlib_inline/backend_inline.py b/matplotlib_inline/backend_inline.py index ca2e003186d899b4ccc8d960081d44c90ed63841..6bcc1d4a013957a4015ef9d66e5fc4e29d80f201 100644 --- a/matplotlib_inline/backend_inline.py +++ b/matplotlib_inline/backend_inline.py @@ -4,13 +4,11 @@ # Distributed under the terms of the BSD 3-Clause License. import matplotlib -from matplotlib.backends.backend_agg import ( # noqa - new_figure_manager, - FigureCanvasAgg, - new_figure_manager_given_figure, -) 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 @@ -20,6 +18,57 @@ 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. @@ -56,51 +105,6 @@ show._draw_called = False 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 @@ -115,19 +119,20 @@ def flush_figures(): 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: + 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]: diff --git a/matplotlib_inline/config.py b/matplotlib_inline/config.py index c0f398069e089118c8bcb4ed5378e4a1eb991c2d..8718babad92c77a141535a5f675316d5c3a5e7ff 100644 --- a/matplotlib_inline/config.py +++ b/matplotlib_inline/config.py @@ -32,26 +32,19 @@ class InlineBackendConfig(SingletonConfigurable): class InlineBackend(InlineBackendConfig): """An object to store configuration of the inline backend.""" - # The typical default figure size is too large for inline use, - # so we shrink the figure size to 6x4, and tweak fonts to - # make that fit. + # While we are deprecating overriding matplotlib defaults out of the + # box, this structure should remain here (empty) for API compatibility + # and the use of other tools that may need it. Specifically Spyder takes + # advantage of it. + # See https://github.com/ipython/ipython/issues/10383 for details. rc = Dict( - { - 'figure.figsize': (6.0, 4.0), - # play nicely with white background in the Qt and notebook frontend - 'figure.facecolor': (1, 1, 1, 0), - 'figure.edgecolor': (1, 1, 1, 0), - # 12pt labels get cutoff on 6x4 logplots, so use 10pt. - 'font.size': 10, - # 72 dpi matches SVG/qtconsole - # this only affects PNG export, as SVG has no dpi setting - 'figure.dpi': 72, - # 10pt still needs a little more room on the xlabel: - 'figure.subplot.bottom': .125 - }, - help="""Subset of matplotlib rcParams that should be different for the - inline backend.""" - ).tag(config=True) + {}, + help="""Dict to manage matplotlib configuration defaults in the inline + backend. As of v0.1.4 IPython/Jupyter do not override defaults out of + the box, but third-party tools may use it to manage rc data. To change + personal defaults for matplotlib, use matplotlib's configuration + tools, or customize this class in your `ipython_config.py` file for + IPython/Jupyter-specific usage.""").tag(config=True) figure_formats = Set( {'png'}, diff --git a/setup.cfg b/setup.cfg index 97bee13299b62f5259b380a4e03301c11df2ea77..e999fd19540ebbcb6bfdc2425068c2364fa9e876 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,9 @@ [metadata] name = matplotlib-inline -version = 0.1.3 +version = attr: matplotlib_inline.__version__ description = Inline Matplotlib backend for Jupyter +long_description = file: README.md, LICENSE +long_description_content_type = text/markdown author = IPython Development Team author_email = ipython-dev@scipy.org url = https://github.com/ipython/matplotlib-inline @@ -27,3 +29,4 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10