Skip to content

Commit ea04978

Browse files
authored
Merge pull request #21342 from ericpre/fix_drawing_animated_artists_callback
Fix drawing animated artists changed in selector callback
2 parents db96106 + 7603999 commit ea04978

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

lib/matplotlib/tests/test_widgets.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,63 @@ def test_span_selector_bound(direction):
916916
assert tool._edge_handles.positions == handle_positions
917917

918918

919+
@pytest.mark.backend('QtAgg', skip_on_importerror=True)
920+
def test_span_selector_animated_artists_callback():
921+
"""Check that the animated artists changed in callbacks are updated."""
922+
x = np.linspace(0, 2 * np.pi, 100)
923+
values = np.sin(x)
924+
925+
fig, ax = plt.subplots()
926+
(ln,) = ax.plot(x, values, animated=True)
927+
(ln2, ) = ax.plot([], animated=True)
928+
929+
# spin the event loop to let the backend process any pending operations
930+
# before drawing artists
931+
# See blitting tutorial
932+
plt.pause(0.1)
933+
ax.draw_artist(ln)
934+
fig.canvas.blit(fig.bbox)
935+
936+
def mean(vmin, vmax):
937+
# Return mean of values in x between *vmin* and *vmax*
938+
indmin, indmax = np.searchsorted(x, (vmin, vmax))
939+
v = values[indmin:indmax].mean()
940+
ln2.set_data(x, v)
941+
942+
span = widgets.SpanSelector(ax, mean, direction='horizontal',
943+
onmove_callback=mean,
944+
interactive=True,
945+
drag_from_anywhere=True,
946+
useblit=True)
947+
948+
# Add span selector and check that the line is draw after it was updated
949+
# by the callback
950+
press_data = [1, 2]
951+
move_data = [2, 2]
952+
do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1)
953+
do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1)
954+
assert span._get_animated_artists() == (ln, ln2)
955+
assert ln.stale is False
956+
assert ln2.stale
957+
assert ln2.get_ydata() == 0.9547335049088455
958+
span.update()
959+
assert ln2.stale is False
960+
961+
# Change span selector and check that the line is drawn/updated after its
962+
# value was updated by the callback
963+
press_data = [4, 2]
964+
move_data = [5, 2]
965+
release_data = [5, 2]
966+
do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1)
967+
do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1)
968+
assert ln.stale is False
969+
assert ln2.stale
970+
assert ln2.get_ydata() == -0.9424150707548072
971+
do_event(span, 'release', xdata=release_data[0],
972+
ydata=release_data[1], button=1)
973+
assert ln2.stale is False
974+
975+
919976
def check_lasso_selector(**kwargs):
920977
ax = get_ax()
921978

lib/matplotlib/widgets.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,18 @@ def set_active(self, active):
18391839
if active:
18401840
self.update_background(None)
18411841

1842+
def _get_animated_artists(self):
1843+
"""
1844+
Convenience method to get all animated artists of the figure containing
1845+
this widget, excluding those already present in self.artists.
1846+
The returned tuple is not sorted by 'z_order': z_order sorting is
1847+
valid only when considering all artists and not only a subset of all
1848+
artists.
1849+
"""
1850+
return tuple(a for ax_ in self.ax.get_figure().get_axes()
1851+
for a in ax_.get_children()
1852+
if a.get_animated() and a not in self.artists)
1853+
18421854
def update_background(self, event):
18431855
"""Force an update of the background."""
18441856
# If you add a call to `ignore` here, you'll want to check edge case:
@@ -1848,15 +1860,21 @@ def update_background(self, event):
18481860
# Make sure that widget artists don't get accidentally included in the
18491861
# background, by re-rendering the background if needed (and then
18501862
# re-re-rendering the canvas with the visible widget artists).
1851-
needs_redraw = any(artist.get_visible() for artist in self.artists)
1863+
# We need to remove all artists which will be drawn when updating
1864+
# the selector: if we have animated artists in the figure, it is safer
1865+
# to redrawn by default, in case they have updated by the callback
1866+
# zorder needs to be respected when redrawing
1867+
artists = sorted(self.artists + self._get_animated_artists(),
1868+
key=lambda a: a.get_zorder())
1869+
needs_redraw = any(artist.get_visible() for artist in artists)
18521870
with ExitStack() as stack:
18531871
if needs_redraw:
1854-
for artist in self.artists:
1872+
for artist in artists:
18551873
stack.enter_context(artist._cm_set(visible=False))
18561874
self.canvas.draw()
18571875
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
18581876
if needs_redraw:
1859-
for artist in self.artists:
1877+
for artist in artists:
18601878
self.ax.draw_artist(artist)
18611879

18621880
def connect_default_events(self):
@@ -1903,7 +1921,12 @@ def update(self):
19031921
self.canvas.restore_region(self.background)
19041922
else:
19051923
self.update_background(None)
1906-
for artist in self.artists:
1924+
# We need to draw all artists, which are not included in the
1925+
# background, therefore we also draw self._get_animated_artists()
1926+
# and we make sure that we respect z_order
1927+
artists = sorted(self.artists + self._get_animated_artists(),
1928+
key=lambda a: a.get_zorder())
1929+
for artist in artists:
19071930
self.ax.draw_artist(artist)
19081931
self.canvas.blit(self.ax.bbox)
19091932
else:

0 commit comments

Comments
 (0)