如何利用FuncAnimation高效更新mpl_toolkits mplot3d中的3D Quiver数据
Great question! I’ve run into this exact issue with 3D quiver animations in matplotlib before—clearing the axis every frame is a performance killer and breaks your carefully set axis configurations. Let’s fix this by updating the existing quiver directly, just like you do with your 2D line plot.
First, Clarify the Object Type
You mentioned seeing the quiver as a Line3DCollection—that’s actually the 2D quiver implementation. For 3D, ax1.quiver() returns a mpl_toolkits.mplot3d.art3d.Quiver3D instance, which has built-in methods to modify its data without rebuilding the entire object.
Core Methods to Update the Quiver
The Quiver3D class has two key methods for dynamic updates:
set_UVC(U, V, W): Updates the direction components of the vector (this is your equivalent ofline0.set_ydata())set_offsets(offsets)+set_zdir(z): Use these if you need to change the starting point (X,Y,Z) of the arrow (not needed in your case since you’re starting at (0,0,0), but useful to know)
Modified Code Implementation
Here’s how to adjust your existing code to update the quiver efficiently:
- Initialize the quiver with a default vector (so we have an object to modify later):
# Initialize with a zero vector instead of NaN vec0 = ax1.quiver(0, 0, 0, 0, 0, 0, length=1)
- Update the animate function to modify the existing quiver:
def animate(i, ser, ys_roll): # Get USB readings pos = get_readings(ser) # Update 2D roll plot as before ys_roll.append(pos[0]) ys_roll = ys_roll[-log_size:] line0.set_ydata(ys_roll) # Update the 3D quiver directly (no clearing or rebuilding!) vec0.set_UVC(pos[3], pos[4], pos[5]) # Return ONLY the artists that changed (critical for blit=True) return line0, vec0
- Fix the animation initialization:
Your originalFuncAnimationsetup is mostly correct, but ensure the returned objects match what you’re updating. The blitting system needs to know exactly which elements to redraw each frame.
Key Notes
- This approach keeps your axis settings intact (no more resetting limits after
ax1.clear()) - It’s far more performant than recreating the quiver every frame, which is essential for smooth real-time animations with ESP32 data
- If you ever need to change the starting point (X,Y,Z) of the arrow, you can use:
# For a single starting point (e.g., (x, y, z)) vec0.set_offsets(np.array([[x, y]])) # Pass 2D array for X/Y offsets vec0.set_zdir(z) # Set the Z component of the start point
Full Modified Code Snippet
import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation from mpl_toolkits import mplot3d # The number of values that are going to be charted log_size = 200 # Create a figure instance and add subplots fig = plt.figure() ax = fig.add_subplot(2, 1, 1) # Create static arrays for 2D plot xs = list(range(0, log_size)) ys_roll = [np.NaN] * log_size # Set 2D plot limits ax.set_xlim([0, log_size]) ax.set_ylim([-180, 180]) # Setup 2D line line0, = ax.plot(xs, ys_roll, label="Roll") # Establish 2D chart parameters plt.title('Roll, Pitch, and Yaw') plt.xlabel('X') plt.ylabel('Angle') plt.legend() # Setup 3D subplot and initial quiver (zero vector) ax1 = fig.add_subplot(212, projection='3d') ax1.set_xlim([-1, 1]) ax1.set_ylim([-1, 1]) ax1.set_zlim([-1, 1]) vec0 = ax1.quiver(0, 0, 0, 0, 0, 0, length=1) # Format plot plt.xticks(rotation=45, ha='right') plt.subplots_adjust(bottom=0.30) ser = ... # Serial port setup here def get_readings(ser): # Function to get serial port readings and return an np.array() with 6 elements: # Roll, Pitch, Yaw, and vector U, V, W. ... return np.array([roll_val, pitch_val, yaw_val, U, V, W]) def animate(i, ser, ys_roll): pos = get_readings(ser) # Update 2D roll data ys_roll.append(pos[0]) ys_roll = ys_roll[-log_size:] line0.set_ydata(ys_roll) # Update 3D quiver vector vec0.set_UVC(pos[3], pos[4], pos[5]) # Return updated artists for blitting return line0, vec0 # Setup animation ani = animation.FuncAnimation(fig, animate, fargs=(ser, ys_roll), interval=10, blit=True) plt.show()
内容的提问来源于stack exchange,提问作者Plant_Boy

