0

I am a newbie to python and the various libraries. In my attempts to get up to speed I am have been attempting to learn to create, manage and visualize data sets. Part of this process has been attempting to set up a 2 x 2 animated plot of 4 distributions, and then using slider control to manipulate the various parameters of these distributions. Unfortunately I am now stuck....I have been able to get the animation to work with no sliders and also to draw the sliders. But am yet to get the sliders to work with the animation. So I have three questions if anyone is able to help.

  1. Any advice on where I am going wrong with my code and why the animation is not working?
  2. Once the animation has commenced, is there anyway to change the distribution parameters and therefore get the plots to change?
  3. Any advice on my coding would be much appreciated.

Code as follows (thanks in advance!):

import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.animation as animation
import mpl_toolkits.axes_grid1
import matplotlib.widgets 

%matplotlib notebook

n = 1000

gspec = gridspec.GridSpec(7, 2)
fig = plt.figure()

top_left = plt.subplot(gspec[0:2, 0])
top_right = plt.subplot(gspec[0:2, 1])
lower_left = plt.subplot(gspec[3:5, 0])
lower_right = plt.subplot(gspec[3:5, 1])
ax = [top_right, top_left, lower_right, lower_left]
ax_right = [top_right, lower_right]

axis1 = [-5, 0, 0, 0.6]
axis2 = [0, 10, 0, 0.6]
axis3 = [7, 15, 0, 0.6]
axis4 = [14, 20, 0, 0.6]
axis = [axis1, axis2, axis3, axis4]

# generate 4 random variables from the random, gamma, exponential, and uniform distributions
x1 = np.random.normal(-2.5, 1, n)
x2 = np.random.gamma(2, 1.5, n)
x3 = np.random.exponential(2, n)+7
x4 = np.random.uniform(14,20, n)
x = [x1, x2, x3, x4]
bar_color = ['lightgrey', 'lightgrey', 'grey', 'grey']

for a in ax:
    a.spines['left'].set_visible(False)
    a.spines['right'].set_visible(False)
    a.spines['top'].set_visible(False)
    a.tick_params(top=False, bottom=True, left=True, right=False, labelleft=True, labelbottom=True)

for a in ax_right:
    a.spines['left'].set_visible(False)
    a.tick_params(top=False, bottom=True, left=False, right=False, labelleft=False, labelbottom=True)


norm_slidecolor = 'lightgrey'
sd_slidecolor = 'grey'
button_color = 'grey'

normsliderpos_sd = fig.add_axes([0.65, 0.2, 0.25, 0.03], facecolor=norm_slidecolor)
normslider_sd = Slider(normsliderpos_sd, r'Norm $\sigma$', 0, 5, valinit = 1, valstep = 0.5)
normslider_sd.label.set_size(10)

normsliderpos_mean = fig.add_axes([0.65, 0.25, 0.25, 0.03], facecolor=norm_slidecolor)
normslider_mean = Slider(normsliderpos_mean, r'Norm $\mu$', -10, 10, valinit = -2.5, valstep = 0.5)
normslider_mean.label.set_size(10)

expsliderpos_decay = fig.add_axes([0.65, 0.1, 0.25, 0.03], facecolor=sd_slidecolor)
expslider_decay = Slider(expsliderpos_decay, r'Exp Decay', 0, 5, valinit = 2, valstep = 0.5)
expslider_decay.label.set_size(10)

class Player(animation.FuncAnimation):

    def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
                 save_count=None, mini=0, maxi=100, pos=(0.55, 0.02), **kwargs):

        self.i = 0
        self.min=mini
        self.max=maxi
        self.runs = True
        self.forwards = True
        self.fig = fig
        self.func = func
        self.setup(pos)
        animation.FuncAnimation.__init__(self,self.fig, self.func, frames=self.play(), 
                                           init_func=init_func, fargs=fargs,
                                           save_count=save_count, **kwargs )    

    def play(self):
        print(i)
        while self.runs:
            self.i = self.i+self.forwards-(not self.forwards)
            if self.i > self.min and self.i < self.max:
                yield self.i
            else:
                self.stop()
                yield self.i

    def start(self, event = None):
        self.runs=True
        self.event_source.start()

    def stop(self, event=None):
        self.runs = False
        self.event_source.stop()

    def forward(self, event=None):
        self.forwards = True
        self.start()

    def backward(self, event=None):
        self.forwards = False
        self.start()


    def setup(self, pos):
        playerax = self.fig.add_axes([pos[0],pos[1], 0.4, 0.04])
        divider = mpl_toolkits.axes_grid1.make_axes_locatable(playerax)
        sax = divider.append_axes("left", size="100%", pad=0.05)
        fax = divider.append_axes("left", size="100%", pad=0.05)
        stax = divider.append_axes("left", size="100%", pad=0.05)

        self.button_back = matplotlib.widgets.Button(playerax, label='Back')
        self.button_stop = matplotlib.widgets.Button(sax, label='Start')
        self.button_forward = matplotlib.widgets.Button(fax, label='Forward')
        self.button_start = matplotlib.widgets.Button(stax, label='Stop')

        self.button_back.on_clicked(self.backward)
        self.button_stop.on_clicked(self.stop)
        self.button_forward.on_clicked(self.forward)
        self.button_start.on_clicked(self.start)

def update(i):

    if curr >= n: 
        a.event_source.stop()

    for a in range(len(ax)):  
        ax[a].cla()
        ax[a].hist(x[a][:i], density = True, bins = 20, color = bar_color[a])
        ax[a].axis(axis[a])
    fig.suptitle('Normed Frequency. Sample Number: {}'.format(i))

ani = Player(fig, update)

plt.show()
MikeM
  • 157
  • 1
  • 1
  • 12
  • Unfortunately, I don't think you'll be able to achieve what you are trying to do with matplotlib alone. I don't think you can modify the parameters of the animation while it is running. Maybe someone more knowledgeable will prove me wrong? – Diziet Asahi Mar 19 '19 at 09:10
  • The reason for the animation not working is that "it is critical to keep a reference to the instance object" ([as per the documentation](https://matplotlib.org/api/animation_api.html)) so you need to return the `simulation` object somehow, but I don't think you can return it from a widget button click – Diziet Asahi Mar 19 '19 at 09:12
  • You can use a generator as `frames` argument that would yield the value you want and supply it to the updating function. (see e.g. [here](https://stackoverflow.com/questions/44985966/managing-dynamic-plotting-in-matplotlib-animation-module)) Putting the `FuncAnimation` into a class would allow to keep the reference. Make sure not to use more than one `FuncAnimation`, as this would slow down everything significantly. – ImportanceOfBeingErnest Mar 19 '19 at 11:46
  • Thanks you both! Appreciate you both taking the time to look at what I have done. @ImportanceOfBeingErnest, I loved the example that you highlighted and have attempted to incorporate this. Unfortunately, without much luck despite a fair amount of effort. My understanding is simply not up to scratch yet. In particular, how the update function and generator work together. I will update my code in edit shortly, and would certainly welcome any comments that you may have. Thanks again! – MikeM Mar 21 '19 at 07:30
  • Your case is way more complicated than the one I linked to - or at least somehow different; because you need to synchronize the sliders with the animation (I don't even know how that would look like, e.g. what happens if you slide while the animation is running?). That link was more meant for you to understand how to use a generator. – ImportanceOfBeingErnest Mar 22 '19 at 00:27
  • Thanks again @ImportanceOfBeingErnest. The link certainly did help with my understanding of the generator. And even good to know that what I was trying to do was not basic. Still early days for me and python. So just trying different things at the moment. Thanks! – MikeM Mar 24 '19 at 09:41

0 Answers0