From 4dcb403cc7ad33c8e3fa6e2407390ed9f8abf592 Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Mon, 29 Apr 2019 13:54:53 +0100 Subject: [PATCH 1/2] Resize slider depending on text size --- animatplot/animation.py | 58 +++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/animatplot/animation.py b/animatplot/animation.py index ce19522..fccc79e 100644 --- a/animatplot/animation.py +++ b/animatplot/animation.py @@ -1,6 +1,8 @@ from matplotlib.animation import FuncAnimation, PillowWriter from matplotlib.widgets import Button, Slider +from matplotlib.text import Text import matplotlib.pyplot as plt + import numpy as np from animatplot import Timeline @@ -68,7 +70,8 @@ def toggle(self, ax=None): """ if ax is None: 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) @@ -77,7 +80,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 @@ -112,14 +115,6 @@ def timeline_slider(self, text='Time', ax=None, valfmt=None, color=None): color : The color of the slider. """ - if ax is None: - adjust_plot = {'bottom': .2} - rect = [.18, .05, .5, .03] - - plt.subplots_adjust(**adjust_plot) - self.slider_ax = plt.axes(rect) - else: - self.slider_ax = ax if valfmt is None: if (np.issubdtype(self.timeline.t.dtype, np.datetime64) @@ -127,26 +122,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={}): @@ -167,8 +193,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 ---------- From 3b44bbe8a133b83be895ab0b8790304b1168af7e Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Mon, 29 Apr 2019 13:55:36 +0100 Subject: [PATCH 2/2] Added test to check text does not overlap button --- tests/test_animation.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_animation.py b/tests/test_animation.py index 203bf74..b37cb2c 100644 --- a/tests/test_animation.py +++ b/tests/test_animation.py @@ -41,3 +41,42 @@ def test_save(): anim.save_gif(base+'save') plt.close('all') assert os.path.exists(base+'save.gif') + + +@pytest.fixture() +def line_anim(): + 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) + y_data = np.sin(2 * np.pi * (x_grid + t_grid)) + + block = amp.blocks.Line(x, y_data) + + if timeline: + anim = amp.Animation([block], timeline=amp.Timeline(t)) + else: + 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