2

我正在使用 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()
4

2 回答 2

2

您将获得无限递归,因为当您调用svlsr.set_valupdate()函数时,它会通知任何注册在svlsr. 对于任何对执行此操作的代码感兴趣的人,请点击此处

观察者是您在调用中指定的函数,svlsr.on_changed并且是 ...update()再次。所以,update()将再次被调用,它将set_val()再次调用,然后update()再次等等......

简单案例的解决方案

根据您拥有的代码,顶部滑块 ( sreds) 会更改底部滑块 ( ) 的值,svlsr反之则不会。如果是这种情况,那么解决方案相对容易。您可以有一个功能来处理sreds(例如updatesreds()),它可能与您当前的功能完全相同,update()而另一个功能(例如updatesvlsr())在底部滑块更新时执行您想要的任何操作。除非您想更改svlsrcall set_val()on ,否则这将起作用sreds,在这种情况下,您又回到了相同的情况。

代码看起来像这样(替换示例中的第 33 行):

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)
于 2015-07-28T22:17:47.077 回答
0

我找到了一个黑客来做到这一点。定义一个单独的函数说“不做任何事情”函数,它什么都不做。然后,在更新函数中设置 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:迭代期间字典更改大小”错误。

于 2019-09-05T14:26:08.233 回答