Link 两个 matplotlib 滑块在一起
Link two matplotlib sliders together
我正在使用 matplotlib 编写脚本,其中有两个滑块可以左右移动绘图。我想这样做,如果我移动一个滑块,另一个滑块也会更新。我以为我可以为此使用 Slider.set_val(val) 方法,但是因为这让我陷入了无限循环。滑块的功能是沿 x 轴拉伸或压缩图形线。如果您 运行 代码,您会看到一个滑块比另一个滑块 'rough' 大,它拉伸图形更多,而另一个允许用户微调。我最终需要人们能够轻松读出绝对拉伸量,这就是为什么我希望将这些值联系起来。我目前有以下代码:
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()
你得到了无限递归,因为当你在 update()
函数中调用 svlsr.set_val
时,这会通知在 svlsr
上注册的任何观察者。对于任何感兴趣的人,执行此操作的代码是 here.
观察者是您在对 svlsr.on_changed
的调用中指定的函数,并且是... update()
。因此,update()
将再次调用,这将再次调用 set_val()
,然后再次调用 update()
,依此类推...
简单案例的解决方案
根据您的代码,顶部滑块 (sreds
) 会更改底部滑块 (svlsr
) 的值,反之亦然。如果是这种情况,那么解决方案就相对容易了。您可以使用一个函数来处理 sreds
(例如 updatesreds()
),这可能与您当前的 update()
完全相同,而另一个函数(例如 updatesvlsr()
)可以做任何您想要的事情想要底部滑块更新时。这将有效 除非 您希望更改 svlsr
以在 sreds
上调用 set_val()
,其中如果你回到同样的情况。
代码看起来像这样(在您的示例中从第 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)
我找到了一个黑客来做到这一点。定义一个单独的函数,比如 'donothing' 函数,它什么都不做。然后,在更新函数内设置 svlsr 的值之前,将 svlsr 绑定从更新更改为 donothing,以便调用 donothing 函数而不是 'update'。 set_value后,重新绑定svlsr slider更新函数。这是完整的代码:
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: dictionary changed size during iteration' 错误。
我正在使用 matplotlib 编写脚本,其中有两个滑块可以左右移动绘图。我想这样做,如果我移动一个滑块,另一个滑块也会更新。我以为我可以为此使用 Slider.set_val(val) 方法,但是因为这让我陷入了无限循环。滑块的功能是沿 x 轴拉伸或压缩图形线。如果您 运行 代码,您会看到一个滑块比另一个滑块 'rough' 大,它拉伸图形更多,而另一个允许用户微调。我最终需要人们能够轻松读出绝对拉伸量,这就是为什么我希望将这些值联系起来。我目前有以下代码:
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()
你得到了无限递归,因为当你在 update()
函数中调用 svlsr.set_val
时,这会通知在 svlsr
上注册的任何观察者。对于任何感兴趣的人,执行此操作的代码是 here.
观察者是您在对 svlsr.on_changed
的调用中指定的函数,并且是... update()
。因此,update()
将再次调用,这将再次调用 set_val()
,然后再次调用 update()
,依此类推...
简单案例的解决方案
根据您的代码,顶部滑块 (sreds
) 会更改底部滑块 (svlsr
) 的值,反之亦然。如果是这种情况,那么解决方案就相对容易了。您可以使用一个函数来处理 sreds
(例如 updatesreds()
),这可能与您当前的 update()
完全相同,而另一个函数(例如 updatesvlsr()
)可以做任何您想要的事情想要底部滑块更新时。这将有效 除非 您希望更改 svlsr
以在 sreds
上调用 set_val()
,其中如果你回到同样的情况。
代码看起来像这样(在您的示例中从第 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)
我找到了一个黑客来做到这一点。定义一个单独的函数,比如 'donothing' 函数,它什么都不做。然后,在更新函数内设置 svlsr 的值之前,将 svlsr 绑定从更新更改为 donothing,以便调用 donothing 函数而不是 'update'。 set_value后,重新绑定svlsr slider更新函数。这是完整的代码:
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: dictionary changed size during iteration' 错误。