以虚线样式绘制部分 FancyArrowPatch 线

Plotting part of FancyArrowPatch line in dashed style

我正在尝试以虚线样式绘制 matplotlib.patches.FancyArrowPatch 的一部分。使用这个 post ,我设法非常接近它:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

plt.figure()

kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k', connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
plt.gca().add_patch(arrow)
points = np.array([i[0] for i in arrow.get_path().iter_segments(curves = False)])
# arrow.remove()
a, = plt.plot(points[:-3,0], points[:-3,1])
plt.plot(points[-4:,0], points[-4:,1],  linestyle = '--', color = a.get_color())
plt.tight_layout()
plt.show()

据我了解,蓝线与黑线不匹配,因为 iter_segments() 将曲线转换为点密度太低的直线。

我应该怎么做才能获得更好的结果?

您可以手动评估箭头生成的贝塞尔曲线。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom

fig, ax = plt.subplots()

kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k', 
          connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)


bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)

def bezier(points, t=[0,1], num=200):
    N = len(points)
    t = np.linspace(*t, num=num)
    curve = np.zeros((num, 2))
    for i in range(N):
        curve += np.outer(bernstein(N - 1, i, t), points[i])
    return curve

verts = arrow.get_path().vertices
curve1 = bezier(verts, t=[0.0, 0.5], num=100)
curve2 = bezier(verts, t=[0.5, 1.0], num=100)
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson")
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson")

plt.show()

正如您所注意到的,两条曲线,即原始箭头和手动创建的贝塞尔曲线,没有相互重叠。这是因为 matplotlib 在屏幕 space 中评估贝塞尔曲线,而手动版本在数据 space.
中评估它 为了在这两种情况下获得相同的曲线,我们需要在屏幕 space 中进行评估,如下所示(我们还在数据和像素 [=23= 中绘制了三个贝塞尔曲线节点) ]).

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom

fig, ax = plt.subplots()

kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k', 
          connectionstyle = "arc3, rad = -0.4" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
ax.autoscale()
print(arrow.get_path().vertices)

bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)

def bezier(points, t=[0,1], num=200):
    N = len(points)
    t = np.linspace(*t, num=num)
    curve = np.zeros((num, 2))
    for i in range(N):
        curve += np.outer(bernstein(N - 1, i, t), points[i])
    return curve

trans = ax.transData
trans_inv = trans.inverted()
verts = trans.transform(arrow.get_path().vertices)
curve1 = trans_inv.transform(bezier(verts, t=[0.0, 0.5], num=100))
curve2 = trans_inv.transform(bezier(verts, t=[0.5, 1.0], num=100))

ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson", zorder=0)
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson", zorder=0)

from matplotlib.transforms import IdentityTransform
ax.plot(*trans.transform(arrow.get_path().vertices).T, ls="", marker="o", 
        color="C1", ms=7, transform=IdentityTransform())
ax.plot(*arrow.get_path().vertices.T, ls="", marker="o", color="C0", ms=3)

plt.show()