使用matplotlib的动画交互式绘图


问题内容

在寻找一种使用matplotlib制作动画交互式绘图的方法时,我在堆栈溢出文档中遇到了这段代码:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.widgets import Slider

TWOPI = 2*np.pi

fig, ax = plt.subplots()

t = np.arange(0.0, TWOPI, 0.001)
initial_amp = .5
s = initial_amp*np.sin(t)
l, = plt.plot(t, s, lw=2)

ax = plt.axis([0,TWOPI,-1,1])

axamp = plt.axes([0.25, .03, 0.50, 0.02])
# Slider
samp = Slider(axamp, 'Amp', 0, 1, valinit=initial_amp)

def update(val):
    # amp is the current value of the slider
    amp = samp.val
    # update curve
    l.set_ydata(amp*np.sin(t))
    # redraw canvas while idle
    fig.canvas.draw_idle()

# call update function on slider value change
samp.on_changed(update)

plt.show()

这段代码几乎可以完全满足我的需求,但是我希望对绘图进行动画处理,即使滑块自动从左向右移动,例如每秒移动0.01。有没有简单的方法可以做到这一点?知道我还想将手动控件保留在滑块上(使用click事件)。


问题答案:

这是对代码的简单修改,以添加动画:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.widgets import Slider

TWOPI = 2*np.pi

fig, ax = plt.subplots()

t = np.arange(0.0, TWOPI, 0.001)
initial_amp = .5
s = initial_amp*np.sin(t)
l, = plt.plot(t, s, lw=2)

ax = plt.axis([0,TWOPI,-1,1])

axamp = plt.axes([0.25, .03, 0.50, 0.02])
# Slider
samp = Slider(axamp, 'Amp', 0, 1, valinit=initial_amp)

# Animation controls
is_manual = False # True if user has taken control of the animation
interval = 100 # ms, time between animation frames
loop_len = 5.0 # seconds per loop
scale = interval / 1000 / loop_len

def update_slider(val):
    global is_manual
    is_manual=True
    update(val)

def update(val):
    # update curve
    l.set_ydata(val*np.sin(t))
    # redraw canvas while idle
    fig.canvas.draw_idle()

def update_plot(num):
    global is_manual
    if is_manual:
        return l, # don't change

    val = (samp.val + scale) % samp.valmax
    samp.set_val(val)
    is_manual = False # the above line called update_slider, so we need to reset this
    return l,

def on_click(event):
    # Check where the click happened
    (xm,ym),(xM,yM) = samp.label.clipbox.get_points()
    if xm < event.x < xM and ym < event.y < yM:
        # Event happened within the slider, ignore since it is handled in update_slider
        return
    else:
        # user clicked somewhere else on canvas = unpause
        global is_manual
        is_manual=False

# call update function on slider value change
samp.on_changed(update_slider)

fig.canvas.mpl_connect('button_press_event', on_click)

ani = animation.FuncAnimation(fig, update_plot, interval=interval)

plt.show()

主要变化是update_plot功能的添加,该功能用于FuncAnimation在倒数第二行中添加a。动画从设置的最后一个滑块值开始递增。

该变量is_manual跟踪用户何时单击滑块。用户单击它之后,变量将设置为True,并且动画将不再更新绘图。

为了恢复动画,我添加了一个on_click功能,该功能设置is_manual = False用户何时单击画布上滑块以外的其他位置。

由于这是一个快速处理的脚本,因此我将变量保留为全局变量,但是您可以轻松地将其编写在适当的类中。

请注意,samp.set_val隐式调用会调用该update_slider函数,当用户直接单击滑块时也会调用该函数,因此我们必须is_manual在该update_plot函数中进行重置。