在 class 中放置具有可控参数的动画图形

Placing animated graph with controlable parameters in a class

import matplotlib.gridspec as gridspec
import numpy as np

from matplotlib import animation
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider, CheckButtons

PI = np.pi

sliderDataList = [{'name': 'Left amplitude', 'min': 0.1, 'max': 8.0, 'init': 2, 'step': 0.01}]
checkboxDataList = [{'name': 'Left wave', 'init': True}]


class CollidingWaves:
    def __init__(self, timeFactor=5, x_range=4 * PI, x_offset=0, y_range=4, y_offset=0, sliderData=[],
                 checkboxData=[], tension=1, massDensity=1):
        self.x_range = x_range
        self.x_offset = x_offset
        self.y_range = y_range
        self.y_offset = y_offset
        self.sliderData = sliderData
        self.checkboxData = checkboxData
        self.tension = tension
        self.massDensity = massDensity
        self.timeFactor = timeFactor
        self.showWave = []
        self.amplitude = 0

        self.fig = plt.figure()

        self.mainGrid = gridspec.GridSpec(2, 1)
        self.graphCell = plt.subplot(self.mainGrid[0, :])
        self.graphCell.set(xlim=(-self.x_range - self.x_offset, self.x_range - self.x_offset),
                           ylim=(-self.y_range - self.y_offset, self.y_range - self.y_offset))

        self.x_data = np.linspace(-self.x_range - self.x_offset, self.x_range - self.x_offset, 512)
        self.y_data = []

        self.lines = [plt.plot([], [])[0] for _ in range(2)]
        self.patches = self.lines

        self.controlCell = self.mainGrid[1, :]
        self.controlGrid = gridspec.GridSpecFromSubplotSpec(1, 7, self.controlCell)

        self.checkboxCell = self.controlGrid[0, 0]
        self.checkboxGrid = gridspec.GridSpecFromSubplotSpec(1, 1, self.checkboxCell)
        self.checkboxes = []
        self.checkboxAx = plt.subplot(self.checkboxGrid[0, 0:1])
        self.checkbox = CheckButtons(self.checkboxAx, tuple(x["name"] for x in self.checkboxData),
                                     tuple(x["init"] for x in self.checkboxData))
        self.checkboxes.append(self.checkbox)

        self.sliderCell = self.controlGrid[0, 2:6]
        self.sliderGrid = gridspec.GridSpecFromSubplotSpec(len(self.sliderData), 1, self.sliderCell)
        self.sliders = []
        for i in range(0, len(self.sliderData)):
            self.sliderAx = plt.subplot(self.sliderGrid[i, 0])
            self.slider = Slider(self.sliderAx, self.sliderData[i]["name"], self.sliderData[i]["min"],
                                 self.sliderData[i]["max"], valinit=self.sliderData[i]["init"],
                                 valstep=self.sliderData[i]["step"])
            self.sliders.append(self.slider)

        for slider in self.sliders:
            slider.on_changed(self.update)
        for checkbox in self.checkboxes:
            checkbox.on_clicked(self.update)

    def update(self):
        self.amplitude = self.sliders[0].val
        self.showWave = self.checkboxes[0].val

    def init(self):
        for line in self.lines:
            line.set_data([], [])
        return self.patches

    def animate(self, i):
        self.y_data[0] = [1] * 512
        self.y_data[1] = [2] * 512
        self.lines[0].set_data(self.x_data, self.y_data[0])
        self.lines[1].set_data(self.x_data, self.y_data[1])

        return self.patches

    def start(self):
        animation.FuncAnimation(self.fig, self.animate, init_func=self.init, frames=600, repeat=True, interval=20, blit=True)
        plt.show()


graph = CollidingWaves(sliderData=sliderDataList, checkboxData=checkboxDataList)
graph.start()

上面截图的想法是有一个动画图形和一组控制它的参数的小部件。更改参数应该会更改正在显示的图表。

话虽如此,上面的代码确实 none 了。它是一个不会改变的动画图形和两个改变对象内变量的小部件。但是,该程序没有按预期运行。

首先,图表根本没有显示。我不明白为什么。 其次,改变两个小部件中的任何一个的状态都会抛出 TypeError:

Traceback (most recent call last):
  File "C:\Programs\Python37\lib\site-packages\matplotlib\cbook\__init__.py", line 215, in process
    func(*args, **kwargs)
  File "C:\Programs\Python37\lib\site-packages\matplotlib\widgets.py", line 417, in _update
    self.set_val(val)
  File "C:\Programs\Python37\lib\site-packages\matplotlib\widgets.py", line 438, in set_val
    func(val)
TypeError: update() takes 1 positional argument but 2 were given

我做错了什么?

这里似乎只有四个问题:

  • update 以事件作为参数调用。您需要确保它确实接受了这个参数,即使您不使用它也是如此。
  • 复选框没有 val 属性。您可以通过 .get_status.
  • 获取复选框的状态
  • y_data 分配了两个元素。因此它需要从一开始就有两个元素。
  • 动画需要保存在内存中。因此,您会将其分配给一个变量。

总的来说,这会起作用:

import matplotlib.gridspec as gridspec
import numpy as np

from matplotlib import animation
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider, CheckButtons

PI = np.pi

sliderDataList = [{'name': 'Left amplitude', 'min': 0.1, 'max': 8.0, 'init': 2, 'step': 0.01}]
checkboxDataList = [{'name': 'Left wave', 'init': True}]


class CollidingWaves:
    def __init__(self, timeFactor=5, x_range=4 * PI, x_offset=0, y_range=4, y_offset=0, sliderData=[],
                 checkboxData=[], tension=1, massDensity=1):
        self.x_range = x_range
        self.x_offset = x_offset
        self.y_range = y_range
        self.y_offset = y_offset
        self.sliderData = sliderData
        self.checkboxData = checkboxData
        self.tension = tension
        self.massDensity = massDensity
        self.timeFactor = timeFactor
        self.showWave = []
        self.amplitude = 0

        self.fig = plt.figure()

        self.mainGrid = gridspec.GridSpec(2, 1)
        self.ax = plt.subplot(self.mainGrid[0, :])
        self.ax.set(xlim=(-self.x_range - self.x_offset, self.x_range - self.x_offset),
                           ylim=(-self.y_range - self.y_offset, self.y_range - self.y_offset))

        self.x_data = np.linspace(-self.x_range - self.x_offset, self.x_range - self.x_offset, 512)
        self.y_data = [[],[]]

        self.lines = [self.ax.plot([], [])[0] for _ in range(2)]
        self.patches = self.lines

        self.controlCell = self.mainGrid[1, :]
        self.controlGrid = gridspec.GridSpecFromSubplotSpec(1, 7, self.controlCell)

        self.checkboxCell = self.controlGrid[0, 0]
        self.checkboxGrid = gridspec.GridSpecFromSubplotSpec(1, 1, self.checkboxCell)
        self.checkboxes = []
        self.checkboxAx = plt.subplot(self.checkboxGrid[0, 0:1])
        self.checkbox = CheckButtons(self.checkboxAx, tuple(x["name"] for x in self.checkboxData),
                                     tuple(x["init"] for x in self.checkboxData))
        self.checkboxes.append(self.checkbox)

        self.sliderCell = self.controlGrid[0, 2:6]
        self.sliderGrid = gridspec.GridSpecFromSubplotSpec(len(self.sliderData), 1, self.sliderCell)
        self.sliders = []
        for i in range(0, len(self.sliderData)):
            self.sliderAx = plt.subplot(self.sliderGrid[i, 0])
            self.slider = Slider(self.sliderAx, self.sliderData[i]["name"], self.sliderData[i]["min"],
                                 self.sliderData[i]["max"], valinit=self.sliderData[i]["init"],
                                 valstep=self.sliderData[i]["step"])
            self.sliders.append(self.slider)

        for slider in self.sliders:
            slider.on_changed(self.update)
        for checkbox in self.checkboxes:
            checkbox.on_clicked(self.update)

    def update(self, event=None):
        self.amplitude = self.sliders[0].val
        self.showWave = self.checkboxes[0].get_status()

    def init(self):
        for line in self.lines:
            line.set_data([], [])
        return self.patches

    def animate(self, i):
        self.y_data[0] = [1] * 512
        self.y_data[1] = [2] * 512
        self.lines[0].set_data(self.x_data, self.y_data[0])
        self.lines[1].set_data(self.x_data, self.y_data[1])

        return self.patches

    def start(self):
        self.ani = animation.FuncAnimation(self.fig, self.animate, init_func=self.init, frames=600, repeat=True, interval=20, blit=True)
        plt.show()


graph = CollidingWaves(sliderData=sliderDataList, checkboxData=checkboxDataList)
graph.start()