|
40 | 40 | DISPLAY_TEMPLATE, INCLUDED_FRAMES, JS_INCLUDE, STYLE_INCLUDE)
|
41 | 41 | from matplotlib import _api, cbook
|
42 | 42 |
|
| 43 | +from matplotlib.widgets import Button,Slider |
| 44 | +import mpl_toolkits.axes_grid1 |
| 45 | +from functools import partial |
43 | 46 |
|
44 | 47 | _log = logging.getLogger(__name__)
|
45 | 48 |
|
@@ -1764,3 +1767,106 @@ def _draw_frame(self, framedata):
|
1764 | 1767 |
|
1765 | 1768 | for a in self._drawn_artists:
|
1766 | 1769 | 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