8000 Added PlayerAnimation with example · matplotlib/matplotlib@642167d · GitHub
[go: up one dir, main page]

Skip to content

Commit 642167d

Browse files
Added PlayerAnimation with example
1 parent eaa72c8 commit 642167d

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
=====
3+
Decay
4+
=====
5+
6+
This example showcases:
7+
- using PlayerAnimation
8+
- using blitting
9+
- changing axes limits during an animation.
10+
"""
11+
12+
import itertools
13+
14+
import numpy as np
15+
import matplotlib.pyplot as plt
16+
17+
from matplotlib.animation import PlayerAnimation, FuncAnimation
18+
import math
19+
20+
def data_gen():
21+
for cnt in itertools.count():
22+
yield cnt
23+
24+
def init(val):
25+
global line
26+
print(f"init with val: {val}")
27+
if not "line" in globals():
28+
line, = ax.plot([], [], lw=2)
29+
ax.grid()
30+
ax.set_ylim(-1.1, 1.1)
31+
ax.set_xlim(0, 1 + math.floor(val / 10))
32+
line.set_data([], [])
33+
return [line]
34+
35+
fig, ax = plt.subplots()
36+
37+
def update_plot(i):
38+
# update the data
39+
xdata = np.linspace(-10, 10, 1000)
40+
ydata = np.sin(xdata + i*0.1)
41+
_, xmax = ax.get_xlim()
42+
new_xmax = 1 + math.floor(i / 10)
43+
if xmax != new_xmax:
44+
ax.set_xlim(0, new_xmax)
45+
ax.figure.canvas.draw_idle()
46+
line.set_data(xdata, ydata)
47+
48+
return [line]
49+
50+
animation = PlayerAnimation(fig=fig, func=update_plot, init_func=init, interval=100, blit=True, valstep=0.5)
51+
plt.show()

lib/matplotlib/animation.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
DISPLAY_TEMPLATE, INCLUDED_FRAMES, JS_INCLUDE, STYLE_INCLUDE)
4141
from matplotlib import _api, cbook
4242

43+
from matplotlib.widgets import Button,Slider
44+
import mpl_toolkits.axes_grid1
4345

4446
_log = logging.getLogger(__name__)
4547

@@ -1210,7 +1212,7 @@ def _on_resize(self, event):
12101212
def _end_redraw(self, event):
12111213
# Now that the redraw has happened, do the post draw flushing and
12121214
# blit handling. Then re-enable all of the original events.
1213-
self._post_draw(None, False)
1215+
self._post_draw(None, self._blit)
12141216
if not self._was_stopped:
12151217
self.event_source.start()
12161218
self._fig.canvas.mpl_disconnect(self._resize_id)
@@ -1764,3 +1766,106 @@ def _draw_frame(self, framedata):
17641766

17651767
for a in self._drawn_artists:
17661768
a.set_animated(self._blit)
1769+
1770+
class PlayerAnimation(FuncAnimation):
1771+
# inspired from https://stackoverflow.com/a/46327978/3949028
1772+
PLAY_SYMBOL = "$\u25B6$"
1773+
STOP_SYMBOL = "$\u25A0$"
1774+
PAUSE_SYMBOL = "$\u23F8$" #TODO use instead of STOP_SYMBOL, but doesn't work in Button.label
1775+
ONE_BACK_SYMBOL = "$\u29CF$"
1776+
ONE_FORWARD_SYMBOL = "$\u29D0$"
1777+
1778+
def __init__(self, func, init_func, min_value=0, max_value=100,
1779+
pos=(0.125, 0.92), valstep=1, **kwargs):
1780+
self.val = min_value
1781+
self.min = min_value
1782+
self.max = max_value
1783+
self.direction = 1
1784+
self.caller_func = func
1785+
self.valstep = valstep
1786+
self._player_initiated = False
1787+
#https://github.com/matplotlib/matplotlib/issues/17685
1788+
1789+
def init_func_wrapper():
1790+
return init_func(self.val)
1791+
1792+
super().__init__(func=self._func_wrapper, frames=self._frame_generator,
1793+
init_func=init_func_wrapper, **kwargs)
1794+
1795+
self._setup_player(pos)
1796+
1797+
def _setup_player(self, pos):
1798+
if not self._player_initiated:
1799+
self._player_initiated = True
1800+
playerax = self._fig.add_axes([pos[0], pos[1], 0.64, 0.04])
1801+
divider = mpl_toolkits.axes_grid1.make_axes_locatable(playerax)
1802+
sax = divider.append_axes("right", size="80%", pad=0.05)
1803+
ofax = divider.append_axes("right", size="100%", pad=0.05)
1804+
sliderax = divider.append_axes("right", size="500%", pad=0.07)
1805+
self.button_oneback = Button(playerax, label=self.ONE_BACK_SYMBOL, useblit=self._blit)
1806+
self.play_pause_button = Button(sax, label=self.STOP_SYMBOL, useblit=self._blit)
1807+
self.button_oneforward = Button(ofax, label=self.ONE_FORWARD_SYMBOL, useblit=self._blit)
1808+
self.button_oneback.on_clicked(self.onebackward)
1809+
self.play_pause_button.on_clicked(self.play_pause)
1810+
self.button_oneforward.on_clicked(self.oneforward)
1811+
self.slider = Slider(sliderax, '', self.min, self.max, valinit=self.min, valstep=self.valstep, useblit=self._blit)
1812+
self.slider.on_changed(self.set_pos)
1813+
1814+
def _frame_generator(self):
1815+
while True:
1816+
next = self.val + self.direction*self.valstep
1817+
if next >= self.min and next <= self.max:
1818+
self.val = next
1819+
print(f"yield: {self.val}")
1820+
yield self.val
1821+
else:
1822+
self.pause()
1823+
print(f"pause, yield: {self.val}")
1824+
yield self.val
1825+
1826+
def pause(self, event=None):
1827+
super().pause()
1828+
self.direction = 0
1829+
self.play_pause_button.label.set_text(self.PLAY_SYMBOL)
1830+
self.play_pause_button._draw()
1831+
1832+
def resume(self, event=None):
1833+
self.direction = 1
1834+
self.play_pause_button.label.set_text(self.STOP_SYMBOL)
1835+
self.play_pause_button._draw()
1836+
super().resume()
1837+
1838+
def play_pause(self, event=None):
1839+
if self.direction == 0:
1840+
self.resume()
1841+
else:
1842+
self.pause()
1843+
1844+
def oneforward(self, event=None):
1845+
self.direction = 1
1846+
self.trigger_step()
1847+
1848+
def onebackward(self, event=None):
1849+
self.direction = -1
1850+
self.trigger_step()
1851+
1852+
def set_pos(self, val):
1853+
if isinstance(self.valstep, int):
1854+
val = int(val) # slider gives float event if valstep is int
1855+
if self.val != val:
1856+
print(f"slider set_pos: {val}")
1857+
self.val = val
1858+
self.direction = 0
1859+
self.trigger_step()
1860+
1861+
def trigger_step(self):
1862+
for a in self._drawn_artists:
1863+
a.set_animated(True)
1864+
self._step()
1865+
self.pause()
1866+
1867+
def _func_wrapper(self, val):
1868+
print(f"player _func_wrapper: {val}")
1869+
self.slider.set_val(val)
1870+
return self.caller_func(self.val)
1871+

0 commit comments

Comments
 (0)
0