将两个 matplotlib 滑块链接在一起

Posted

技术标签:

【中文标题】将两个 matplotlib 滑块链接在一起【英文标题】:Link two matplotlib sliders together 【发布时间】:2015-10-18 00:55:04 【问题描述】:

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

【问题讨论】:

欢迎来到 Stack Overflow!您的问题可以使用一些编辑来获得最佳帮助。您在此处给出的代码是来自Minimal, complete and verifiable example 的公平方式。如果您可以创建一个可以简单地粘贴到编辑器中并运行的示例,它将使您获得更好的帮助,并且更多的人将能够快速帮助您。查看How to Ask 了解更多提示,理想情况下,编辑您的问题以包含 MCVE。 顺便说一句,您可能会遇到无限循环,因为当您使用 set_val() 更改值时,会调用观察者(传递给 on_changed 的函数),然后会调用 set_val() on另一个滑块再次等等。要解决这个问题是一件相当棘手的事情。这是可能的,如果您在 MCVE 中进行编辑,我可以提供帮助,这样我就知道自己在做什么。另一方面 - 我还建议您考虑在您的用例中是否真的需要两个滑块,如果一个的值只是反映另一个。 @JRichardSnape 嘿,理查德。谢谢您的回答!我已经编辑了这个问题以包含一个 MCVE,并更好地阐明我想要从我的代码中得到什么以及为什么。如果您还需要更多信息,请告诉我。 嗨@maaike - 这很好,足以回答无限循环的问题。但是 - 我不确定您的逻辑是否完全符合您的要求。我将添加一个解释和解决无限循环问题的答案。如果您需要逻辑方面的帮助(即如何编写粗调和微调),我认为这可能是一个新问题。 【参考方案1】:

您将获得无限递归,因为当您在 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)

【讨论】:

【参考方案2】:

我找到了一个 hack 来做到这一点。定义一个单独的函数说“不做任何事情”函数,它什么都不做。然后,在更新函数中设置 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()

仅使用 disconnect 删除绑定是行不通的,因为它会给出“RuntimeError: dictionary changed size during iteration”错误。

【讨论】:

以上是关于将两个 matplotlib 滑块链接在一起的主要内容,如果未能解决你的问题,请参考以下文章

Python Matplotlib饼图将两个标题相同的切片合并在一起

PyQt4 - 将滑块的范围链接到绘图轴的范围

论述在Python程序中如何导入OpenCV以及matplotlib库中的pyplot

Python使用matplotlib可视化树状图层次聚类系统树图树状图根据给定的距离度量将相似点分组在一起并根据点的相似性将它们组织成树状图链接起来(Dendrogram)

在同一进程中将 matplotlib 与 Qt5 后端一起使用并运行现有 QApplication

如何在图下方居中 matplotlib 滑块并重新标记滑块标签?