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

Matplotlib 3D动画中坐标轴范围更新异常问题求助

Matplotlib 3D动画坐标轴范围不更新,线条无法居中显示问题

我在调整Matplotlib 3D动画的坐标轴范围时遇到问题:控制台输出的范围值正常,但画面显示的坐标轴范围不符合预期,绘制线条无法在轴内正常显示。手动移动相机后坐标轴范围显示正常,但程序自动运行时无法实现。希望无需手动调整相机,就能让绘制线条处于Axes3D的中心位置。

原代码

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button, TextBox
from matplotlib import animation
x = np.array([0.0])
y = np.array([1.0])
z = np.array([0.0])
v = np.array([1.0, 0.0, 1.0])
tmax = 1
tmin = 0
fig2 = plt.figure()
# fig2.set_visible(False)
ax = fig2.add_axes([0.075, 0.1, 0.6, 0.8],projection='3d')


def set_axes_equal(ax: plt.Axes):

    limits = np.array([ax.get_xlim3d(),ax.get_ylim3d(),ax.get_zlim3d(),])
    origin = np.mean(limits, axis=1)
    radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0]))
    _set_axes_radius(ax, origin, radius)

def _set_axes_radius(ax, origin, radius):
    x, y, z = origin
    ax.set_xlim3d([x - radius, x + radius])
    ax.set_ylim3d([y - radius, y + radius])
    ax.set_zlim3d([z - radius, z + radius])

ax.set_box_aspect((1, 1, 1))

def set_lim():
    global tmax, tmin

    tmax = max(np.max(x), np.max(y), np.max(z))

    tmin = min(np.min(x), np.min(y), np.min(z))
    print(ax.get_xlim3d())

    if tmax != tmin:
        ax.set_xlim(tmin, tmax)
        ax.set_ylim(tmin, tmax)
        ax.set_zlim(tmin, tmax)
    else: 
        ax.set_xlim(-10, 10)
        ax.set_ylim(-10, 10)
        ax.set_zlim(-10, 10)

k = 1.0
dt = 0.1

p, = ax.plot(x, y, z)

def fv(v:np.ndarray):
    return np.cross(v, np.array([0., 0., 1.]))

def runge_kutta(y:np.ndarray, t:float, f):
    k1 = t * f(y)
    k2 = t * f(y + 0.5 * k1)
    k3 = t * f(y + 0.5 * k2)
    k4 = t * f(y + k3)
    return y + (k1 + 2 * k2 + 2 * k3 + k4) / 6.

def update(i):
    global k, dt, p, x, y, z, v
    v = runge_kutta(v, dt, fv)
    dx = x[-1] + v[0] * dt
    dy = y[-1] + v[1] * dt
    dz = z[-1] + v[2] * dt
    x = np.append(x, [dx])
    y = np.append(y, [dy])
    z = np.append(z, [dz])
    set_lim()
    set_axes_equal(ax)
    if i >= 50:x, y, z = x[1:], y[1:], z[1:]
    p.set_data_3d(x, y, z)
    return (p,)
   
ani = animation.FuncAnimation(fig2, update, interval=100, frames=None, blit=True)

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

问题现象

  • 程序自动运行时:
    可见绘制线条超出坐标轴范围
  • 手动移动相机后:
    此时坐标轴范围显示正常

问题原因

  1. Blit模式限制:使用blit=True时,FuncAnimation只会更新update函数返回的艺术家对象(这里是线条p),坐标轴的范围变更不会被自动重绘,导致画面显示和实际范围不一致。
  2. 坐标轴设置不规范:set_lim函数中使用set_xlim而非3D轴专用的set_xlim3d,可能导致范围设置不生效。
  3. set_axes_equal逻辑依赖初始轴范围:该函数基于当前轴的范围计算等比例,但初始轴范围可能和真实数据范围不符,导致居中失效。

修改方案

  1. 关闭blit=True(改为blit=False),让动画每次更新时重绘整个画布,确保坐标轴变化被显示。
  2. 修正set_lim中的范围设置方法,使用set_xlim3d/set_ylim3d/set_zlim3d。
  3. 调整set_axes_equal的逻辑,直接基于数据的最大最小范围计算,避免依赖初始轴范围。

修改后的代码

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

x = np.array([0.0])
y = np.array([1.0])
z = np.array([0.0])
v = np.array([1.0, 0.0, 1.0])
tmax = 1
tmin = 0

fig2 = plt.figure()
ax = fig2.add_axes([0.075, 0.1, 0.6, 0.8], projection='3d')

def set_axes_equal(ax: plt.Axes, data_limits):
    # 基于数据的真实范围计算等比例坐标轴
    x_min, x_max = data_limits[0]
    y_min, y_max = data_limits[1]
    z_min, z_max = data_limits[2]
    
    origin = [(x_min + x_max)/2, (y_min + y_max)/2, (z_min + z_max)/2]
    radius = 0.5 * max(x_max - x_min, y_max - y_min, z_max - z_min)
    
    ax.set_xlim3d([origin[0] - radius, origin[0] + radius])
    ax.set_ylim3d([origin[1] - radius, origin[1] + radius])
    ax.set_zlim3d([origin[2] - radius, origin[2] + radius])

ax.set_box_aspect((1, 1, 1))

def set_lim():
    global tmax, tmin
    tmax = max(np.max(x), np.max(y), np.max(z))
    tmin = min(np.min(x), np.min(y), np.min(z))
    
    if tmax != tmin:
        ax.set_xlim3d(tmin, tmax)
        ax.set_ylim3d(tmin, tmax)
        ax.set_zlim3d(tmin, tmax)
    else: 
        ax.set_xlim3d(-10, 10)
        ax.set_ylim3d(-10, 10)
        ax.set_zlim3d(-10, 10)
    # 返回当前数据的范围,用于set_axes_equal
    return [(tmin, tmax), (tmin, tmax), (tmin, tmax)]

k = 1.0
dt = 0.1

p, = ax.plot(x, y, z)

def fv(v:np.ndarray):
    return np.cross(v, np.array([0., 0., 1.]))

def runge_kutta(y:np.ndarray, t:float, f):
    k1 = t * f(y)
    k2 = t * f(y + 0.5 * k1)
    k3 = t * f(y + 0.5 * k2)
    k4 = t * f(y + k3)
    return y + (k1 + 2 * k2 + 2 * k3 + k4) / 6.

def update(i):
    global k, dt, p, x, y, z, v
    v = runge_kutta(v, dt, fv)
    dx = x[-1] + v[0] * dt
    dy = y[-1] + v[1] * dt
    dz = z[-1] + v[2] * dt
    x = np.append(x, [dx])
    y = np.append(y, [dy])
    z = np.append(z, [dz])
    
    data_limits = set_lim()
    set_axes_equal(ax, data_limits)
    
    if i >= 50:
        x, y, z = x[1:], y[1:], z[1:]
    
    p.set_data_3d(x, y, z)
    return (p,)

# 关闭blit,确保坐标轴更新被重绘
ani = animation.FuncAnimation(fig2, update, interval=100, frames=None, blit=False)

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

关键修改点说明

  • 移除了未使用的Button和TextBox导入。
  • 修改set_axes_equal,直接接收数据范围作为参数,基于数据计算等比例坐标轴,避免依赖轴的初始状态。
  • set_lim中改用set_xlim3d等3D专用方法,并返回数据范围给set_axes_equal。
  • 将FuncAnimation的blit参数设为False,确保每次更新时重绘整个画布,坐标轴的变化能实时显示。

内容的提问来源于stack exchange,提问作者beginner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 08:15:04