Maaike
Maaike

Reputation: 122

Link two matplotlib sliders together

I'm writing a script using matplotlib in which I have two sliders that can move the plot left and right. I want to make it so that if I move one slider, the other slider is also updated. I thought I could use the Slider.set_val(val) method for this, but as this gets me stuck in an endless loop. The function of the sliders is to stretch or compress the graphed line along the x-axis. If you run the code you will see that the one slider is more 'rough' than the other, it stretches the graph more, and the other allows the user to fine-tune. I will eventually need for people to be able to easily read off the absolute amount of stretching, which is why I want the values to be linked. I currently have the following code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import sys

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t,s, lw=2, color='red')
plt.axis([0, 1, -10, 10])

axcolor = 'lightgoldenrodyellow'

d0      = 0.0
c       = 300000                
z0      = d0/c

vmin    = -300.0
vmax    = 3000.0

zmin    = -0.01
zmax    = 2

axfreq  = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
axz     = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)

svlsr   = Slider(axfreq, 'VLSR', vmin, vmax, valinit=d0, valfmt=u'%1.1f')
sreds   = Slider(axz, 'z', zmin, zmax, valinit=z0, valfmt=u'%1.4f')

def update(val):
    global d0, z0
    delt = svlsr.val/c
    z = sreds.val

    if z!=0.0:
        if z != z0:
            delt = z
            svlsr.set_val(z*c) #set_val causes infinite loop??

    d0 = delt
    z0    = z
    fac = 1.0 + delt
    l.set_xdata(t*fac)
    fig.canvas.draw_idle()

svlsr.on_changed(update)
sreds.on_changed(update)

plt.show()

Upvotes: 2

Views: 1023

Answers (2)

Anal Kumar
Anal Kumar

Reputation: 1

I found a hack to do this. Define a separate function say 'donothing' function, which does nothing. Then, before setting the value of svlsr inside the update function, change the svlsr binding from update to donothing so that donothing function is called instead of 'update'. After set_value, re-bind svlsr slider to update function. Here's the full code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import sys

fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t,s, lw=2, color='red')
plt.axis([0, 1, -10, 10])

axcolor = 'lightgoldenrodyellow'

d0      = 0.0
c       = 300000
z0      = d0/c

vmin    = -300.0
vmax    = 3000.0

zmin    = -0.01
zmax    = 2

axfreq  = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axz     = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)

svlsr   = Slider(axfreq, 'VLSR', vmin, vmax, valinit=d0, valfmt=u'%1.1f')
sreds   = Slider(axz, 'z', zmin, zmax, valinit=z0, valfmt=u'%1.4f')

def donothing(val): #The dummy function
    pass
def update(val):
    global d0, z0
    delt = svlsr.val/c
    z = sreds.val

    svlsr.observers[svlsrcid] = donothing #Binding removed from update
    if z!=0.0:
        if z != z0:
            delt = z
            svlsr.set_val(z*c) #set_val causes infinite loop?? Now it doesn't.
    svlsr.observers[svlsrcid] = update #Binded again with update

    d0 = delt
    z0    = z
    fac = 1.0 + delt
    l.set_xdata(t*fac)
    fig.canvas.draw_idle()

svlsrcid = svlsr.on_changed(update) #Getting the id of the binding
sredscid = sreds.on_changed(update)

plt.show()

Just deleting the binding with disconnect won't work as it will give 'RuntimeError: dictionary changed size during iteration' error.

Upvotes: 0

J Richard Snape
J Richard Snape

Reputation: 20344

You get the infinite recursion because when you call svlsr.set_val in the update() function this notifies any observer registered on svlsr. For anyone interested the code that does that is here.

The observer is the function you specified in the call to svlsr.on_changed and is ... update() again. So, update() will be called again, which will call set_val() again, then update() again and so on...

Solution for simple case

Based on the code you have, the top slider (sreds) changes the value on the bottom one (svlsr) but not vice versa. If this is the case, then the solution is relatively easy. You can have one function to deal with sreds (e.g. updatesreds()) which could be exactly the same as your current update() and a different one (e.g. updatesvlsr()) doing whatever you want when the bottom slider updates. This will work unless you want a change in svlsr to call set_val() on sreds, in which case you are back in the same situation.

The code would look something like this (replacing line 33 onwards in your example):

def updatereds(val):
    global d0, z0
    delt = svlsr.val/c
    z = sreds.val

    if z!=0.0:
        if z != z0:
            delt = z
            svlsr.set_val(z*c) #set_val causes infinite loop??

    d0 = delt
    z0    = z
    fac = 1.0 + delt
    l.set_xdata(t*fac)
    fig.canvas.draw_idle()
    
def updatesvlsr(val):
   # put any code you need to execute on direct update to svlsr here
   # the only thing you can't do is set_val on sreds, otherwise again
   # you will infinitely recurse
   pass

svlsr.on_changed(updatesvlsr)
sreds.on_changed(updatereds)

Upvotes: 2

Related Questions