You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单个PyQt5窗口中实现两种动画(旋转扇形圆与轨迹)

解决PyQt5中Matplotlib双动画(旋转扇形+轨迹)的合并问题

问题描述

需要在单个PyQt5窗口的Matplotlib极坐标图中同时实现两个动画:

  • 旋转的红色扇形动画
  • 从JSON文件读取的轨迹绘制动画
    现有代码单独运行两个动画正常,但合并时出现错误,核心是不理解FuncAnimation的使用和参数传递规则。

问题分析

原有代码的核心问题:

  • 使用两个独立的FuncAnimation实例,其中update1函数调用ax.clear()会清除整个坐标轴,包括轨迹线,导致两个动画互相干扰。
  • fargs传递了不必要的参数,增加了逻辑混乱。
  • 极坐标下错误使用了笛卡尔坐标系的set_xlim/set_ylim,应该用极坐标专属的半径范围设置。
  • 扇形每次重新创建而非更新已有对象,效率低且容易冲突。

修正后的完整代码

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWidgets import QWidget, QGridLayout, QGroupBox

# 假设make_points_between是你已实现的轨迹插值函数
def make_points_between(trajectory):
    # 示例实现:将轨迹点插值为更密集的点
    x = np.array([p[0] for p in trajectory])
    y = np.array([p[1] for p in trajectory])
    t = np.linspace(0, 1, len(x))
    t_new = np.linspace(0, 1, len(x)*10)
    x_new = np.interp(t_new, t, x)
    y_new = np.interp(t_new, t, y)
    return np.vstack((x_new, y_new))

class MyFigurePolar(FigureCanvas):
    def __init__(self, width=5, height=4, dpi=100, trajectory_data=None):
        self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = self.fig.add_subplot(111, projection='polar')
        super().__init__(self.fig)
        
        # 初始化极坐标参数
        self.r = 7  # 外圆半径
        self.period = np.pi / 64  # 扇形角度范围
        self.trajectory_points = make_points_between(trajectory_data).T
        
        # 初始化动画元素
        self._init_static_elements()
        self._init_animation_elements()
        self._start_animations()

    def _init_static_elements(self):
        # 绘制固定外圆
        theta = np.linspace(0, 2 * np.pi, 100)
        self.axes.plot(theta, np.ones(100) * self.r, linestyle='-', color='black')
        # 设置极坐标属性
        self.axes.set_theta_zero_location("N")
        self.axes.set_theta_direction(-1)
        self.axes.set_yticklabels([5000, 10000, 15000, 20000, 25000])
        self.axes.set_rmax(self.r * 1.1)  # 设置最大半径

    def _init_animation_elements(self):
        # 初始化轨迹线
        self.trajectory_ln, = self.axes.plot([], [], color='blue')
        # 初始化扇形(用fill_between创建,保存对象以便更新)
        self.sector = self.axes.fill_between([], [], [], alpha=0.5, facecolor='red')

    def _update_trajectory(self, num):
        # 更新轨迹数据:转换为极坐标
        x = self.trajectory_points[:num, 0]
        y = self.trajectory_points[:num, 1]
        theta = np.arctan2(y, x)  # 注意极坐标theta是从x轴正方向逆时针转,这里要对应
        r = np.sqrt(x**2 + y**2)
        self.trajectory_ln.set_data(theta, r)
        return self.trajectory_ln,

    def _update_sector(self, frame):
        # 更新扇形数据,不清除坐标轴
        start = frame * np.pi / 180
        end = start - self.period
        # 更新扇形的顶点数据
        theta_vals = np.linspace(end, start, 20)
        r_vals = np.zeros_like(theta_vals)
        r_vals[:] = self.r
        # 获取fill_between的路径集合并更新
        path = self.sector.get_paths()[0]
        vertices = np.vstack((np.append(theta_vals, [end, start]),
                              np.append(r_vals, [0, 0]))).T
        path.vertices = vertices
        return self.sector,

    def _start_animations(self):
        # 轨迹动画:帧数量等于轨迹点数量,速度根据总时长调整
        self.trajectory_anim = FuncAnimation(
            self.fig, self._update_trajectory, 
            frames=len(self.trajectory_points),
            interval=10000 / len(self.trajectory_points),
            blit=True
        )
        # 扇形旋转动画:360帧,循环播放
        self.sector_anim = FuncAnimation(
            self.fig, self._update_sector,
            frames=360, interval=15,
            blit=True, repeat=True
        )

class MainWindow(QWidget):
    def __init__(self, json_data):
        super().__init__()
        self.init_ui(json_data)

    def init_ui(self, json_data):
        self.groupBox = QGroupBox("双动画演示")
        self.gridlayout = QGridLayout(self.groupBox)
        
        # 创建带动画的极坐标画布
        self.figure = MyFigurePolar(trajectory_data=json_data['trajectory'])
        self.gridlayout.addWidget(self.figure, 0, 1)
        
        main_layout = QGridLayout()
        main_layout.addWidget(self.groupBox, 0, 0)
        self.setLayout(main_layout)
        self.setWindowTitle("PyQt5 + Matplotlib 双动画")

# 示例JSON数据(替换为你的实际数据)
sample_json_data = {
    'trajectory': [
        (1, 1), (2, 3), (3, 2), (4, 4),
        (3, 5), (2, 4), (1, 3), (0, 0)
    ]
}

if __name__ == "__main__":
    import sys
    from PyQt5.QtWidgets import QApplication
    app = QApplication(sys.argv)
    win = MainWindow(sample_json_data)
    win.show()
    sys.exit(app.exec_())

关键修改说明

  • 避免坐标轴清除:不再使用ax.clear(),直接更新已有动画元素的数据源,确保两个动画互不干扰。
  • 动画元素复用:提前创建轨迹线和扇形对象,每次更新仅修改它们的顶点/数据,而非重新绘制。
  • 统一极坐标处理:正确设置极坐标的零位置、方向和半径范围,替换错误的笛卡尔坐标系范围设置。
  • 分离动画逻辑:将轨迹和扇形的更新逻辑拆分为独立函数,各自维护自己的动画元素,通过两个FuncAnimation分别驱动,但确保它们操作同一坐标轴的不同元素。
  • 参数传递优化:移除不必要的fargs参数,直接通过类实例属性访问数据,简化逻辑。

内容的提问来源于stack exchange,提问作者Алина Люлюшкина

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 09:02:34