2 回答
TA贡献1780条经验 获得超4个赞
我找到了该问题的解决方案,但是,它不是很优雅。我发现,在非交互式后端调用的唯一回调函数是子类的draw_path()方法AbstractPathEffect。
我创建了一个AbstractPathEffect子类,在其draw_path()方法中更新箭头的顶点。
我仍然愿意为我的问题提供其他可能更直接的解决方案。
import numpy as np
from numpy.linalg import norm
from matplotlib.patches import FancyArrow
from matplotlib.patheffects import AbstractPathEffect
class AdaptiveFancyArrow(FancyArrow):
"""
A `FancyArrow` with fixed head shape.
The length of the head is proportional to the width the head
in display coordinates.
If the head length is longer than the length of the entire
arrow, the head length is limited to the arrow length.
"""
def __init__(self, x, y, dx, dy,
tail_width, head_width, head_ratio, draw_head=True,
shape="full", **kwargs):
if not draw_head:
head_width = tail_width
super().__init__(
x, y, dx, dy,
width=tail_width, head_width=head_width,
overhang=0, shape=shape,
length_includes_head=True, **kwargs
)
self.set_path_effects(
[_ArrowHeadCorrect(self, head_ratio, draw_head)]
)
class _ArrowHeadCorrect(AbstractPathEffect):
"""
Updates the arrow head length every time the arrow is rendered
"""
def __init__(self, arrow, head_ratio, draw_head):
self._arrow = arrow
self._head_ratio = head_ratio
self._draw_head = draw_head
def draw_path(self, renderer, gc, tpath, affine, rgbFace=None):
# Indices to certain vertices in the arrow
TIP = 0
HEAD_OUTER_1 = 1
HEAD_INNER_1 = 2
TAIL_1 = 3
TAIL_2 = 4
HEAD_INNER_2 = 5
HEAD_OUTER_2 = 6
transform = self._arrow.axes.transData
vert = tpath.vertices
# Transform data coordiantes to display coordinates
vert = transform.transform(vert)
# The direction vector alnog the arrow
arrow_vec = vert[TIP] - (vert[TAIL_1] + vert[TAIL_2]) / 2
tail_width = norm(vert[TAIL_2] - vert[TAIL_1])
# Calculate head length from head width
head_width = norm(vert[HEAD_OUTER_2] - vert[HEAD_OUTER_1])
head_length = head_width * self._head_ratio
if head_length > norm(arrow_vec):
# If the head would be longer than the entire arrow,
# only draw the arrow head with reduced length
head_length = norm(arrow_vec)
# The new head start vector; is on the arrow vector
if self._draw_head:
head_start = \
vert[TIP] - head_length * arrow_vec/norm(arrow_vec)
else:
head_start = vert[TIP]
# vector that is orthogonal to the arrow vector
arrow_vec_ortho = vert[TAIL_2] - vert[TAIL_1]
# Make unit vector
arrow_vec_ortho = arrow_vec_ortho / norm(arrow_vec_ortho)
# Adjust vertices of the arrow head
vert[HEAD_OUTER_1] = head_start - arrow_vec_ortho * head_width/2
vert[HEAD_OUTER_2] = head_start + arrow_vec_ortho * head_width/2
vert[HEAD_INNER_1] = head_start - arrow_vec_ortho * tail_width/2
vert[HEAD_INNER_2] = head_start + arrow_vec_ortho * tail_width/2
# Transform back to data coordinates
# and modify path with manipulated vertices
tpath.vertices = transform.inverted().transform(vert)
renderer.draw_path(gc, tpath, affine, rgbFace)
添加回答
举报