Skip to content

Commit

Permalink
Move draw_if_interactive logic to new_figure_manager_given_figure.
Browse files Browse the repository at this point in the history
Currently, Matplotlib only ever calls draw_if_interactive at the end of
`plt.figure()` (and upon figure unpickling), i.e. this is a mechanism to
further customize handling of newly generated figures.  (Prior to
Matplotlib 1.5, draw_if_interactive was also called at the end of all
pyplot functions to trigger a figure redraw, but this is now handled by
the stale attribute.)

In order to simplify the backend API ("what is the API that a backend
module must/can provide"), I am planning to deprecate (on Matplotlib's
side) the ability for backends to provide a draw_if_interactive function
(forcing them to always do `if interactive(): draw_idle()`).  Instead,
any relevant new-figure-customization logic can instead go into
`new_figure_manager_given_figure`.  This PR implements this change for
matplotlib-inline, and should be fully back-compatible all the way back
to Matplotlib 1.5.  I would like to make these changes first on the side
of the clients (i.e., the third-party backends), to catch any possible
problems with the intended change on Matplotlib's side.
  • Loading branch information
anntzer committed Jun 10, 2022
1 parent 170a075 commit 6380cd9
Showing 1 changed file with 54 additions and 50 deletions.
104 changes: 54 additions & 50 deletions matplotlib_inline/backend_inline.py
Expand Up @@ -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
Expand All @@ -20,6 +18,57 @@
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

# 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.
Expand Down Expand Up @@ -56,51 +105,6 @@ def show(close=None, block=None):
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
Expand Down

0 comments on commit 6380cd9

Please sign in to comment.