diff --git a/animatplot/animation.py b/animatplot/animation.py index 853a66d..c200217 100644 --- a/animatplot/animation.py +++ b/animatplot/animation.py @@ -1,7 +1,9 @@ from matplotlib.animation import FuncAnimation, PillowWriter from matplotlib.gridspec import GridSpec from matplotlib.widgets import Button, Slider +from matplotlib.text import Text import matplotlib.pyplot as plt + import numpy as np from animatplot import Timeline @@ -120,7 +122,8 @@ def toggle(self, ax=None): 'adding in independent axes. tight_layout() will ignore ' 'the button.') adjust_plot = {'bottom': .2} - rect = [.78, .03, .1, .07] + left, bottom, width, height = (.78, .03, .1, .07) + rect = (left, bottom, width, height) plt.subplots_adjust(**adjust_plot) self.button_ax = plt.axes(rect) @@ -129,7 +132,7 @@ def toggle(self, ax=None): self.button = Button(self.button_ax, "Pause") self.button.label2 = self.button_ax.text( - 0.5, 0.5, 'Play', + x=0.5, y=0.5, s='Play', verticalalignment='center', horizontalalignment='center', transform=self.button_ax.transAxes @@ -190,26 +193,57 @@ def timeline_slider(self, text='Time', ax=None, valfmt=None, color=None): valfmt = '%s' else: valfmt = '%1.2f' - if self.timeline.log: valfmt = '$10^{%s}$' % valfmt + if ax is None: + # Try to intelligently decide slider width to avoid overlap + + renderer = self.fig.canvas.get_renderer() + + # Calculate width of widest time value on plot + def text_width(txt): + t_val_text = Text(text=txt, figure=self.fig) + bbox = t_val_text.get_window_extent(renderer=renderer) + extents = self.fig.transFigure.inverted().transform(bbox) + return extents[1][0] - extents[0][0] + + text_val_width = max(text_width(valfmt % (self.timeline[i])) + for i in range(len(self.timeline))) + label_width = text_width(text) + + # Calculate width of slider + default_button_width = 0.1 + width = 0.73 - text_val_width - label_width - default_button_width + + adjust_plot = {'bottom': .2} + left, bottom, height = (.18, .05, .03) + rect = (left, bottom, width, height) + + plt.subplots_adjust(**adjust_plot) + self.slider_ax = plt.axes(rect) + else: + self.slider_ax = ax + self.slider = Slider( - self.slider_ax, text, 0, self.timeline._len-1, + self.slider_ax, label=text, valmin=0, valmax=self.timeline._len-1, valinit=0, valfmt=(valfmt+self.timeline.units), valstep=1, color=color ) self._has_slider = True - def set_time(t): - self.timeline.index = int(self.slider.val) + def set_time(new_slider_val): + # Update slider value and text on each step + self.timeline.index = int(new_slider_val) self.slider.valtext.set_text( self.slider.valfmt % (self.timeline[self.timeline.index])) + if self._pause: for block in self.blocks: block._update(self.timeline.index) self.fig.canvas.draw() + self.slider.on_changed(set_time) def controls(self, timeline_slider_args={}, toggle_args={}): @@ -230,8 +264,8 @@ def controls(self, timeline_slider_args={}, toggle_args={}): def save_gif(self, filename): """Saves the animation to a gif - A convience function. Provided to let the user avoid dealing - with writers. + A convenience function. Provided to let the user avoid dealing + with writers - uses PillowWriter. Parameters ---------- diff --git a/tests/test_animation.py b/tests/test_animation.py index da3475a..3f3747d 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -64,7 +64,7 @@ def make_line_block(t_length=5): @pytest.fixture() def line_anim(): - def make_line_anim(t_length=5, timeline=False): + def make_line_anim(t_length=5, timeline=False, controls=False): x = np.linspace(0, 1, 10) t = np.linspace(0, 1, t_length) x_grid, t_grid = np.meshgrid(x, t) @@ -73,13 +73,34 @@ def make_line_anim(t_length=5, timeline=False): block = amp.blocks.Line(x, y_data) if timeline: - return amp.Animation([block], timeline=amp.Timeline(t)) + anim = amp.Animation([block], timeline=amp.Timeline(t)) else: - return amp.Animation([block]) + anim = amp.Animation([block]) + if controls: + anim.controls() + + return anim return make_line_anim +class TestSlider: + def test_slider_size(self, line_anim): + """Test text not overlapping with button (GH issue #32)""" + anim = line_anim(timeline=True, controls=True) + + slider_rhs = anim.slider_ax.get_position().x1 + + valtext_bbox = anim.slider.valtext.get_window_extent() + valtext_extents = anim.fig.transFigure.inverted().transform(valtext_bbox) + valtext_rhs = valtext_extents[1][0] + + button_lhs = anim.button_ax.get_position().x0 + + assert slider_rhs < button_lhs + assert valtext_rhs < button_lhs + + class TestAddBlocks: def test_add_blocks(self, line_block): anim = amp.Animation([line_block()])