目录8

文章Matplotlib第 24 章

向量场

Matplotlib · 第 24 / 43 章

quiver() —— 向量场箭头

quiver() 在每个取样点上画一个箭头,箭头方向 = (U, V) 的方向,长度 ∝ 向量模长。它回答的是「每一点往哪走、走多快」。缺点很明确:密度一高就糊,而且箭头长度受 scale 控制,跨图比较时必须统一。

Python20 行
Axes.quiver(
    X, Y, U, V,        # 位置 + 两个分量;也可以只给 (U, V)
    C=None,            # ★ 第 5 个位置参数:按这个值给箭头上色
    units="width",     # 'width' / 'xy' / 'inches' / 'dots'
    angles="uv",       # ★ 'uv'(按方向算角度)/ 'xy'(按数据坐标)
    scale=None,        # 缩放系数:值越大箭头越短
    scale_units=None,  # 与 scale 配套的单位
    width=0.005,       # 箭杆宽度(相对子图宽度)
    headwidth=3,       # 箭头宽 = width 的倍数
    headlength=5,      # 箭头长 = width 的倍数
    headaxislength=4.5,# 箭头与箭杆的重叠长度
    minshaft=1,        # 最小箭杆长度(短路时不画箭头)
    minlength=1,       # 短于此长度的向量不画
    pivot="tail",      # ★ 箭头锚点:tail / mid / middle / tip
    color=None,
    alpha=None,
    clim=None,
    data=None,
    **kwargs
)
参数 一句话 默认值的影响
U / V 两个分量 单位不必归一,长度会按模长体现
C 上色依据 不给就单色;给了自动配 cmap
scale 长度缩放 和箭头长度成反比
angles 角度算法 'uv'(视觉角度)/ 'xy'(真实斜率)
pivot 锚点 'tail':格点是箭尾
width / headwidth / headlength 箭头形状 相对 width 的倍数
minlength 最小可见长度 1,短向量直接丢掉

最小示例

Python12 行
import numpy as np
import matplotlib.pyplot as plt

xq = np.linspace(-2, 2, 11)
yq = np.linspace(-2, 2, 11)
Xq, Yq = np.meshgrid(xq, yq)
Uq = -Yq * 0.8          # 绕原点的旋转场
Vq = Xq * 0.8

fig, ax = plt.subplots()
ax.quiver(Xq, Yq, Uq, Vq, scale=30)
plt.show()

向量场箭头最小示例

scale:和箭头长度成反比

Python2 行
ax.quiver(Xq, Yq, Uq, Vq, scale=20)    # 箭头长(会冲出坐标框)
ax.quiver(Xq, Yq, Uq, Vq, scale=60)    # 箭头短(留出大片空白)

这条关系经常记反:scale 是除数。scale 越大 → 同样的模长画出越短的箭头。

units 决定“长度”以什么为参照:'width'(默认,子图宽度的倍数)、'xy'(数据坐标)、'inches'、'dots'。想画“物理上正确”的箭头(比如速度矢量在数据坐标里的真实长度)用 units="xy" + scale=1。

C:按模长或第三维上色

Python3 行
M = np.hypot(Uq, Vq)
q = ax.quiver(Xq, Yq, Uq, Vq, M, cmap="viridis", scale=25)
fig.colorbar(q, ax=ax)

第 5 个位置参数就是 C。只能按标量上色——想按方向区分颜色得先算 arctan2(V, U) 再传进去。

pivot / angles:锚点和角度算法

Python2 行
ax.quiver(Xq, Yq, Uq, Vq, scale=25, pivot="mid")
ax.quiver(Xq, Yq, Uq, Vq, scale=25, angles="xy")
  • pivot="tail"(默认)→ 格点是箭尾;"mid" → 格点是箭头中点;"tip" → 格点是箭尖。
  • angles="uv"(默认)→ 箭头方向由 (U, V) 直接决定,和坐标轴比例无关;"xy" → 按数据坐标算,坐标轴不等比例时箭头会歪。

画流场默认值通常就对;画“真实的力/位移”(比如地图上的风)时 angles="xy" + units="xy" 才是物理正确的组合。

width / headwidth / headlength:箭头形状

Python2 行
ax.quiver(Xq, Yq, Uq, Vq, scale=25,
          width=0.006, headwidth=4, headlength=6, headaxislength=5)

width 是箭杆宽度(相对子图宽度的比例,默认 0.005),后三个都是**width 的倍数**,不是绝对值。所以放大 width 会让整个箭头等比变粗。

箭头太密时先减 width(让它细),比减 scale(让它短)更能保持可读性。

返回对象
Python14 行
q = ax.quiver(Xq, Yq, Uq, Vq)
print(type(q).__name__)
# 输出: Quiver

print(q.U.shape, q.V.shape)
# 输出: (121,) (121,)

print(q.scale, q.pivot, q.angles)
# 输出: None tail uv      ← scale=None 表示「自动算缩放」

q.set_UVC(Uq * 2, Vq * 2)     # ★ 换数据:动画里比重新 quiver 快得多
q.set_array(np.hypot(Uq, Vq))
q.set_clim(0, 3)
plt.show()

Quiver 是一个 Collection,q.U / q.V 存的是扁平化后的分量(11×11 = 121)。做动态场(风场动画、粒子流)时用 set_UVC() 更新,不要每帧重建。

注意事项

  • scale 与箭头长度成反比,跨图比较必须固定 scale 和 scale_units。
  • 默认 angles="uv" 时箭头方向不受坐标轴比例影响;"xy" 才会。
  • C 只能接标量;按方向上色要先算 arctan2。
  • width 及其倍数是相对量,改子图尺寸后视觉粗细会变。
  • 箭头相互覆盖严重时,用 streamplot 替代——quiver 不适合表达“连续流线”。
  • q.U 是扁平数组,形状还原要用 .reshape(X.shape)。

综合示例:旋转场 / 模长上色 / 稀疏箭头

Python37 行
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False

xq = np.linspace(-2, 2, 15)
yq = np.linspace(-2, 2, 15)
Xq, Yq = np.meshgrid(xq, yq)

# 涡 + 发散:U、V 都是 (X, Y) 的函数
Uq = -Yq * 0.7 + Xq * 0.25
Vq = Xq * 0.7 + Yq * 0.25
M = np.hypot(Uq, Vq)

fig, axes = plt.subplots(1, 3, figsize=(15, 4.6))

# ---- (a) 单色 + 箭头从格点长出来 ----
axes[0].quiver(Xq, Yq, Uq, Vq, scale=28, width=0.005)
axes[0].set_title("(a) scale=28 + pivot='tail'")

# ---- (b) 按模长上色 ----
q = axes[1].quiver(Xq, Yq, Uq, Vq, M, cmap="plasma", scale=28, width=0.006)
fig.colorbar(q, ax=axes[1], label="|v|")
axes[1].set_title("(b) 第 5 参数 = 模长")

# ---- (c) 细箭头 + 锚点居中,密集场也不糊 ----
axes[2].quiver(Xq, Yq, Uq, Vq, scale=30, width=0.003, pivot="mid",
               headwidth=3.5, headlength=5, minlength=0.5)
axes[2].set_title("(c) width=0.003 + pivot='mid'")

for ax in axes:
    ax.set_aspect("equal")
    ax.set_xlim(-2.2, 2.2)
    ax.set_ylim(-2.2, 2.2)
plt.tight_layout()
plt.show()

(a) 和 (c) 讲同一件事:同样的数据,width 从 0.005 降到 0.003、锚点改成 mid,密集场立刻清爽一大截。