将两个matplotlib滑块链接在一起

时间:2015-07-27 17:48:16

标签: python matplotlib matplotlib-widget

我正在使用matplotlib编写一个脚本,其中我有两个可以左右移动绘图的滑块。我想这样做,如果我移动一个滑块,另一个滑块也会更新。我以为我可以使用Slider.set_val(val)方法,但这会让我陷入无限循环。滑块的功能是沿x轴拉伸或压缩图形线。如果运行代码,您将看到一个滑块比另一个更“粗糙”,它会更多地拉伸图形,另一个允许用户进行微调。我最终需要人们能够轻松读取绝对拉伸量,这就是为什么我希望将值链接起来。我目前有以下代码:

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()

2 个答案:

答案 0 :(得分: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)

答案 1 :(得分:0)

我发现有一个破解方法。定义一个单独的功能,例如“ donothing”功能,该功能什么也不做。然后,在设置update函数中的svlsr值之前,将svlsr绑定从update更改为donothing,以便调用donothing函数而不是'update'。在set_value之后,重新绑定svlsr滑块以更新功能。这是完整的代码:

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()

仅删除带有断开连接的绑定将不起作用,因为它将产生“ RuntimeError:字典在迭代期间更改大小”错误。