2

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()
Maaike
  • 122
  • 9
  • Welcom to Stack Overflow! Your question could use a little editing to get the best help here. The code you have given here is a fair way from a [Minimal, complete and verifiable example](http://stackoverflow.com/help/mcve). If you can create an example that can simply be pasted into an editor and run, it will make the help you get much better and far more people will be able to help you quickly. Have a look at [How to Ask](http://stackoverflow.com/help/how-to-ask) for some more tips and, ideally, edit your question to include an MCVE. – J Richard Snape Jul 28 '15 at 09:23
  • By the way, you probably hit the infinite loop because when you change the value using `set_val()` the observers (functions you pass to `on_changed`) are called, which will then call `set_val()` on the other slider again and so on and so on. To get around this is a fairly tricky thing to do. It is possible and I could help if you edit in an MCVE so I know what I'm doing. On the other hand - I'd also recommend you consider whether you really do need both sliders in your use case, if the value of one just mirrors the other. – J Richard Snape Jul 28 '15 at 09:37
  • @JRichardSnape Hey Richard. Thank you for your answer! I have edited the question to include a MCVE and to better clarify what I want from my code and why. Please let me know if you need more information still. – Maaike Jul 28 '15 at 15:23
  • Hi @maaike - that's great and enough to answer the question on the infinite loop. However - I'm not sure that your logic is doing exactly what you would like. I'll add an answer that explains and solves the infinite loop problem. If you need help with the logic (i.e. how to code a coarse adjust and fine adjust), that might be a new question, I think. – J Richard Snape Jul 28 '15 at 22:17

2 Answers2

2

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)
Community
  • 1
  • 1
J Richard Snape
  • 20,116
  • 5
  • 51
  • 79
0

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.