Thanks to visit codestin.com
Credit goes to github.com

Skip to content

MNT: overhaul stale handling #4738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Aug 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
02158b1
FIX: attempting to draw marks figure as not-stale
tacaswell Jul 18, 2015
b0c3741
PEP: remove extra lines
tacaswell Jul 18, 2015
7f3ef65
FIX: do not propogate stale on animated artists
tacaswell Jul 23, 2015
a677634
MNT: Limit what propagates stale to figure
tacaswell Jul 23, 2015
6d6e39a
API: forbid changing figure of Artist instance
tacaswell Jul 23, 2015
1aec54b
MNT: do not re-trigger draws during draw
tacaswell Jul 23, 2015
1a58f17
DOC: added explanatory comments to example
tacaswell Jul 23, 2015
72c5ce2
FIX: make sure that offset boxes propagate axes
tacaswell Jul 31, 2015
4cc9a74
TST: don't reuse artists in tests
tacaswell Jul 31, 2015
b39fd90
FIX: protect against None child
tacaswell Jul 31, 2015
0b49a55
MNT: use direct callback for stale, not pchanged
tacaswell Aug 1, 2015
59eaad6
MNT: still only propagate True up the chain
tacaswell Aug 1, 2015
69b1b36
MNT: fix vanilla python repl hook
tacaswell Aug 1, 2015
770b8d5
FIX: remove excessive stale cleaning
tacaswell Aug 1, 2015
aa810b6
MNT: don't need try/except anymore
tacaswell Aug 1, 2015
0af89d7
FIX: fix stupid bug in pyplot
tacaswell Aug 1, 2015
14b5a36
FIX: don't push race condition on un-pickling
tacaswell Aug 1, 2015
252d7a7
MNT: make base draw_idle re-entrant
tacaswell Aug 6, 2015
a44b74d
FIX: fix documentation typos
tacaswell Aug 7, 2015
53e2db2
MNT: use no-op callback instead of None
tacaswell Aug 7, 2015
54e04d3
FIX: remove stale callback on pickling
tacaswell Aug 9, 2015
5bd4711
MNT: make figure use base-class __getstate__
tacaswell Aug 10, 2015
a48be9f
MNT: defaulting `Artist.stale_callback` to None
tacaswell Aug 10, 2015
13e7c09
FIX: stale propagation in artist without fig/ax
Aug 11, 2015
83ea020
MNT: make sorting out what to re-draw faster
tacaswell Aug 12, 2015
a5f5ab5
MNT: make set_animated smarter
tacaswell Aug 12, 2015
c75f75d
MNT: when blitting in make artist animated
tacaswell Aug 12, 2015
51cc0ca
MNT: set animated to blit state
tacaswell Aug 12, 2015
0e201f9
MNT: setting cmap/norm marks as stale
tacaswell Aug 12, 2015
25079bf
MNT: set_data on FigureImage marks as stale
tacaswell Aug 12, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions examples/pylab_examples/system_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ def get_net():
def get_stats():
return get_memory(), get_cpu(), get_net()


# turn interactive mode on for dynamic updates. If you aren't in
# interactive mode, you'll need to use a GUI event handler/timer.
plt.ion()

fig, ax = plt.subplots()
ind = np.arange(1, 4)

# show the figure, but do not block
plt.show(block=False)


pm, pc, pn = plt.bar(ind, get_stats())
centers = ind + 0.5*pm.get_width()
pm.set_facecolor('r')
Expand All @@ -44,10 +44,21 @@ def get_stats():
for i in range(200): # run for a little while
m, c, n = get_stats()

# update the animated artists
pm.set_height(m)
pc.set_height(c)
pn.set_height(n)

# ask the canvas to re-draw itself the next time it
# has a chance.
# For most of the GUI backends this adds an event to the queue
# of the GUI frameworks event loop.
fig.canvas.draw_idle()
try:
# make sure that the GUI framework has a chance to run its event loop
# and clear any GUI events. This needs to be in a try/except block
# because the default implemenation of this method is to raise
# NotImplementedError
fig.canvas.flush_events()
except NotImplementedError:
pass
Expand Down
15 changes: 10 additions & 5 deletions lib/matplotlib/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -971,17 +971,18 @@ def __init__(self, fig, artists, *args, **kwargs):

def _init_draw(self):
# Make all the artists involved in *any* frame invisible
axes = []
figs = set()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait a minute... figures are mutable. How is it that we can have a set of them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on that and because i bet it is defaulting to using the object id

On Wed, Aug 12, 2015, 11:27 AM Benjamin Root [email protected]
wrote:

In lib/matplotlib/animation.py
#4738 (comment):

@@ -971,17 +971,18 @@ def init(self, fig, artists, _args, *_kwargs):

 def _init_draw(self):
     # Make all the artists involved in *any* frame invisible
  •    axes = []
    
  •    figs = set()
    

wait a minute... figures are mutable. How is it that we can have a set of
them?


Reply to this email directly or view it on GitHub
https://github.com/matplotlib/matplotlib/pull/4738/files#r36873399.

for f in self.new_frame_seq():
for artist in f:
artist.set_visible(False)
artist.set_animated(self._blit)
# Assemble a list of unique axes that need flushing
if artist.axes not in axes:
axes.append(artist.axes)
if artist.axes.figure not in figs:
figs.add(artist.axes.figure)

# Flush the needed axes
for ax in axes:
ax.figure.canvas.draw()
for fig in figs:
fig.canvas.draw()

def _pre_draw(self, framedata, blit):
'''
Expand Down Expand Up @@ -1093,6 +1094,8 @@ def _init_draw(self):
self._draw_frame(next(self.new_frame_seq()))
else:
self._drawn_artists = self._init_func()
for a in self._drawn_artists:
a.set_animated(self._blit)

def _draw_frame(self, framedata):
# Save the data for potential saving of movies.
Expand All @@ -1105,3 +1108,5 @@ def _draw_frame(self, framedata):
# Call the func with framedata and args. If blitting is desired,
# func needs to return a sequence of any artists that were modified.
self._drawn_artists = self._func(framedata, *self._args)
for a in self._drawn_artists:
a.set_animated(self._blit)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this going to wreak havoc on the stale-handling code? Don't we want to avoid setting artists as stale within drawing code paths?

50 changes: 30 additions & 20 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,9 @@ def draw_wrapper(artist, renderer, *args, **kwargs):
return draw_wrapper


def _stale_figure_callback(self):
self.figure.stale = True


def _stale_axes_callback(self):
self.axes.stale = True
def _stale_axes_callback(self, val):
if self.axes:
self.axes.stale = val


class Artist(object):
Expand All @@ -87,6 +84,7 @@ class Artist(object):

def __init__(self):
self._stale = True
self.stale_callback = None
self._axes = None
self.figure = None

Expand Down Expand Up @@ -124,6 +122,7 @@ def __getstate__(self):
# remove the unpicklable remove method, this will get re-added on load
# (by the axes) if the artist lives on an axes.
d['_remove_method'] = None
d['stale_callback'] = None
return d

def remove(self):
Expand Down Expand Up @@ -222,7 +221,7 @@ def axes(self, new_axes):

self._axes = new_axes
if new_axes is not None and new_axes is not self:
self.add_callback(_stale_axes_callback)
self.stale_callback = _stale_axes_callback

return new_axes

Expand All @@ -236,15 +235,16 @@ def stale(self):

@stale.setter
def stale(self, val):
# only trigger call-back stack on being marked as 'stale'
# when not already stale
# the draw process will take care of propagating the cleaning
# process
if not (self._stale == val):
self._stale = val
# only trigger propagation if marking as stale
if self._stale:
self.pchanged()
self._stale = val

# if the artist is animated it does not take normal part in the
# draw stack and is not expected to be drawn as part of the normal
# draw loop (when not saving) so do not propagate this change
if self.get_animated():
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still going to be possible to change the properties of an animated object? I typically do things like changing the alpha or color as part of an animation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, the animated bit on an Artist essentially means "I need to manage how this is redrawn, so don't do it for me." So everything should just work as normal, this just makes blitting work a bit better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stale handler does not propagate on animated artists, but you can still
set all of their properties. If you are using blitting you are still
responsible for doing the redraw. If you are not blitting then animated
needs to be false so they get drawn by the normal draw method which means
stale state does propagate, but draw_idle takes care of collecting those.

I am a bit worried about saving, i suspect that should also be checked in
the figure call back installed by pyplot.

On Wed, Aug 12, 2015, 11:33 AM Benjamin Root [email protected]
wrote:

In lib/matplotlib/artist.py
#4738 (comment):

  •    # only trigger call-back stack on being marked as 'stale'
    
  •    # when not already stale
    
  •    # the draw process will take care of propagating the cleaning
    
  •    # process
    
  •    if not (self._stale == val):
    
  •        self._stale = val
    
  •        # only trigger propagation if marking as stale
    
  •        if self._stale:
    
  •            self.pchanged()
    
  •    self._stale = val
    
  •    # if the artist is animated it does not take normal part in the
    
  •    # draw stack and is not expected to be drawn as part of the normal
    
  •    # draw loop (when not saving) so do not propagate this change
    
  •    if self.get_animated():
    
  •        return
    

Is it still going to be possible to change the properties of an animated
object? I typically do things like changing the alpha or color as part of
an animation.


Reply to this email directly or view it on GitHub
https://github.com/matplotlib/matplotlib/pull/4738/files#r36874315.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert the set change when i get back to my normal computer.

On Wed, Aug 12, 2015, 11:43 AM Thomas Caswell [email protected] wrote:

The stale handler does not propagate on animated artists, but you can
still set all of their properties. If you are using blitting you are still
responsible for doing the redraw. If you are not blitting then animated
needs to be false so they get drawn by the normal draw method which means
stale state does propagate, but draw_idle takes care of collecting those.

I am a bit worried about saving, i suspect that should also be checked in
the figure call back installed by pyplot.

On Wed, Aug 12, 2015, 11:33 AM Benjamin Root [email protected]
wrote:

In lib/matplotlib/artist.py
#4738 (comment)
:

  •    # only trigger call-back stack on being marked as 'stale'
    
  •    # when not already stale
    
  •    # the draw process will take care of propagating the cleaning
    
  •    # process
    
  •    if not (self._stale == val):
    
  •        self._stale = val
    
  •        # only trigger propagation if marking as stale
    
  •        if self._stale:
    
  •            self.pchanged()
    
  •    self._stale = val
    
  •    # if the artist is animated it does not take normal part in the
    
  •    # draw stack and is not expected to be drawn as part of the normal
    
  •    # draw loop (when not saving) so do not propagate this change
    
  •    if self.get_animated():
    
  •        return
    

Is it still going to be possible to change the properties of an animated
object? I typically do things like changing the alpha or color as part of
an animation.


Reply to this email directly or view it on GitHub
https://github.com/matplotlib/matplotlib/pull/4738/files#r36874315.


if val and self.stale_callback is not None:
self.stale_callback(self, val)

def get_window_extent(self, renderer):
"""
Expand Down Expand Up @@ -608,9 +608,19 @@ def set_figure(self, fig):

ACCEPTS: a :class:`matplotlib.figure.Figure` instance
"""
# if this is a no-op just return
if self.figure is fig:
return
# if we currently have a figure (the case of both `self.figure`
# and `fig` being none is taken care of above) we then user is
# trying to change the figure an artist is associated with which
# is not allowed for the same reason as adding the same instance
# to more than one Axes
if self.figure is not None:
raise RuntimeError("Can not put single artist in "
"more than one figure")
self.figure = fig
if self.figure and self.figure is not self:
self.add_callback(_stale_figure_callback)
self.pchanged()
self.stale = True

Expand Down Expand Up @@ -804,9 +814,9 @@ def set_animated(self, b):

ACCEPTS: [True | False]
"""
self._animated = b
self.pchanged()
self.stale = True
if self._animated != b:
self._animated = b
self.pchanged()

def update(self, props):
"""
Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def __setstate__(self, state):
container = getattr(self, container_name)
for artist in container:
artist._remove_method = container.remove
self.stale = True
self._stale = True

def get_window_extent(self, *args, **kwargs):
"""
Expand Down Expand Up @@ -2059,7 +2059,8 @@ def draw(self, renderer=None, inframe=False):
if not self.get_visible():
return
renderer.open_group('axes')

# prevent triggering call backs during the draw process
self._stale = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I understand why drawing would need to set stale flag to True.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be removed now that stale state always propagates (which I am not 100% happy with) and with the context manager in draw_idle. As I babbled in the comments, the stale code is very aggressive about setting artists to be stale (even if it hasn't changed). In the case of Agg this was causing redraws to be trigged from inside of a draw.

locator = self.get_axes_locator()
if locator:
pos = locator(self, renderer)
Expand Down
12 changes: 11 additions & 1 deletion lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

from __future__ import (absolute_import, division, print_function,
unicode_literals)
from contextlib import contextmanager

from matplotlib.externals import six
from matplotlib.externals.six.moves import xrange
Expand Down Expand Up @@ -1690,6 +1691,13 @@ def __init__(self, figure):
self.mouse_grabber = None # the axes currently grabbing mouse
self.toolbar = None # NavigationToolbar2 will set me
self._is_saving = False
self._is_idle_drawing = False

@contextmanager
def _idle_draw_cntx(self):
self._is_idle_drawing = True
yield
self._is_idle_drawing = False

def is_saving(self):
"""
Expand Down Expand Up @@ -2012,7 +2020,9 @@ def draw_idle(self, *args, **kwargs):
"""
:meth:`draw` only if idle; defaults to draw but backends can overrride
"""
self.draw(*args, **kwargs)
if not self._is_idle_drawing:
with self._idle_draw_cntx():
self.draw(*args, **kwargs)

def draw_cursor(self, event):
"""
Expand Down
21 changes: 17 additions & 4 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
docstring.interpd.update(projection_names=get_projection_names())


def _stale_figure_callback(self, val):
if self.figure:
self.figure.stale = val


class AxesStack(Stack):
"""
Specialization of the Stack to handle all tracking of Axes in a Figure.
Expand Down Expand Up @@ -330,6 +335,7 @@ def __init__(self,
xy=(0, 0), width=1, height=1,
facecolor=facecolor, edgecolor=edgecolor,
linewidth=linewidth)

self._set_artist_props(self.patch)
self.patch.set_aa(False)

Expand Down Expand Up @@ -543,6 +549,7 @@ def suptitle(self, t, **kwargs):
sup.remove()
else:
self._suptitle = sup

self.stale = True
return self._suptitle

Expand Down Expand Up @@ -650,6 +657,8 @@ def figimage(self, X,
self.set_size_inches(figsize, forward=True)

im = FigureImage(self, cmap, norm, xo, yo, origin, **kwargs)
im.stale_callback = _stale_figure_callback

im.set_array(X)
im.set_alpha(alpha)
if norm is None:
Expand Down Expand Up @@ -909,6 +918,7 @@ def add_axes(self, *args, **kwargs):
self._axstack.add(key, a)
self.sca(a)
self.stale = True
a.stale_callback = _stale_figure_callback
return a

@docstring.dedent_interpd
Expand Down Expand Up @@ -997,6 +1007,7 @@ def add_subplot(self, *args, **kwargs):
self._axstack.add(key, a)
self.sca(a)
self.stale = True
a.stale_callback = _stale_figure_callback
return a

def clf(self, keep_observers=False):
Expand Down Expand Up @@ -1044,8 +1055,10 @@ def draw(self, renderer):
# draw the figure bounding box, perhaps none for white figure
if not self.get_visible():
return
renderer.open_group('figure')

renderer.open_group('figure')
# prevent triggering call backs during the draw process
self._stale = True
if self.get_tight_layout() and self.axes:
try:
self.tight_layout(renderer, **self._tight_parameters)
Expand Down Expand Up @@ -1117,12 +1130,11 @@ def draw_composite():
dsu.sort(key=itemgetter(0))
for zorder, a, func, args in dsu:
func(*args)
a.stale = False

renderer.close_group('figure')
self.stale = False

self._cachedRenderer = renderer
self.stale = False
self.canvas.draw_event(renderer)

def draw_artist(self, a):
Expand Down Expand Up @@ -1272,6 +1284,7 @@ def text(self, x, y, s, *args, **kwargs):
def _set_artist_props(self, a):
if a != self:
a.set_figure(self)
a.stale_callback = _stale_figure_callback
a.set_transform(self.transFigure)

@docstring.dedent_interpd
Expand Down Expand Up @@ -1348,7 +1361,7 @@ def _gci(self):
return None

def __getstate__(self):
state = self.__dict__.copy()
state = super(Figure, self).__getstate__()
# the axobservers cannot currently be pickled.
# Additionally, the canvas cannot currently be pickled, but this has
# the benefit of meaning that a figure can be detached from one canvas,
Expand Down
13 changes: 11 additions & 2 deletions lib/matplotlib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ class _AxesImageBase(martist.Artist, cm.ScalarMappable):

interpnames = list(six.iterkeys(_interpd))

def set_cmap(self, cmap):
super(_AxesImageBase, self).set_cmap(cmap)
self.stale = True

def set_norm(self, norm):
super(_AxesImageBase, self).set_norm(norm)
self.stale = True

def __str__(self):
return "AxesImage(%g,%g;%gx%g)" % tuple(self.axes.bbox.bounds)

Expand Down Expand Up @@ -828,12 +836,12 @@ def set_filterrad(self, s):
def set_norm(self, norm):
if self._A is not None:
raise RuntimeError('Cannot change colors after loading data')
cm.ScalarMappable.set_norm(self, norm)
super(NonUniformImage, self).set_norm(self, norm)

def set_cmap(self, cmap):
if self._A is not None:
raise RuntimeError('Cannot change colors after loading data')
cm.ScalarMappable.set_cmap(self, cmap)
super(NonUniformImage, self).set_cmap(self, cmap)


class PcolorImage(martist.Artist, cm.ScalarMappable):
Expand Down Expand Up @@ -1026,6 +1034,7 @@ def get_extent(self):
def set_data(self, A):
"""Set the image array."""
cm.ScalarMappable.set_array(self, cbook.safe_masked_invalid(A))
self.stale = True

def set_array(self, A):
"""Deprecated; use set_data for consistency with other image types."""
Expand Down
Loading