8000 working PlayerAnimation with example · matplotlib/matplotlib@9cc4e30 · GitHub
[go: up one dir, main page]

Skip to content
Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9cc4e30

Browse files
working PlayerAnimation with example
1 parent df926b7 commit 9cc4e30

File tree

3 files changed

+255
-15
lines changed

3 files changed

+255
-15
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
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
45+
from functools import partial
4346

4447
_log = logging.getLogger(__name__)
4548

@@ -1764,3 +1767,106 @@ def _draw_frame(self, framedata):
17641767

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

0 commit comments

Comments
 (0)
0