Построение части линии FancyArrowPatch в пунктирном стиле - PullRequest
1 голос
/ 27 февраля 2020

Я пытаюсь нарисовать часть matplotlib.patches.FancyArrowPatch в пунктирном стиле. Используя этот пост pyplot: пунктирная линия с FancyArrowPatch , мне удалось подобраться довольно близко к нему:

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()

enter image description here

To Насколько я понимаю, синяя линия не совпадает с черной, потому что iter_segments () преобразует кривые в прямые линии с слишком низкой плотностью точек.

Как мне сделать, чтобы получить лучший результат?

1 Ответ

2 голосов
/ 28 февраля 2020

Вы можете оценить кривую Безье, которая создается стрелкой вручную.

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()

enter image description here

Как вы заметили две кривые, т.е. исходная стрелка и созданная вручную кривая Безье не накладываются друг на друга. Это связано с тем, что matplotlib оценивает кривую Безье в пространстве экрана, а ручная версия оценивает ее в пространстве данных.
Чтобы получить одинаковую кривую в обоих случаях, нам необходимо выполнить оценку в пространстве экрана, которая показана на следующий (где мы также строим три более точных узла, как в данных, так и в пиксельном пространстве).

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()

enter image description here

...