Matplotlib:每次绘制图形时调用的自定义函数

我想创建一个包含箭头的matplotlib图,其头部的形状与数据坐标无关。这类似于FancyArrowPatch,但是当箭头长度小于头部长度时,会收缩以适应箭头的长度。

目前,我通过将宽度转换为显示坐标、计算显示坐标中的头部长度并将其转换回数据坐标来设置箭头的长度来解决这个问题。

只要轴的尺寸不发生变化,这种方法就可以很好地工作,这可能是由于set_xlim()set_ylim()tight_layout()例如。我想通过在绘图尺寸发生变化时重新绘制箭头来涵盖这些情况。目前我通过注册一个函数on_draw(event)来处理这个

axes.get_figure().canvas.mpl_connect("resize_event", on_draw)

但这仅适用于交互式后端。我还需要一个解决方案,将绘图保存为图像文件。还有其他地方可以注册我的回调函数吗?


紫衣仙女
浏览 365回答 2
2回答

Helenr

我找到了该问题的解决方案,但是,它不是很优雅。我发现,在非交互式后端调用的唯一回调函数是子类的draw_path()方法AbstractPathEffect。我创建了一个AbstractPathEffect子类,在其draw_path()方法中更新箭头的顶点。我仍然愿意为我的问题提供其他可能更直接的解决方案。import numpy as npfrom numpy.linalg import normfrom matplotlib.patches import FancyArrowfrom matplotlib.patheffects import AbstractPathEffectclass 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)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python