为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

紫衣仙女 2021-07-07 17:09:21
我想创建一个包含箭头的matplotlib图,其头部的形状与数据坐标无关。这类似于FancyArrowPatch,但是当箭头长度小于头部长度时,会收缩以适应箭头的长度。目前,我通过将宽度转换为显示坐标、计算显示坐标中的头部长度并将其转换回数据坐标来设置箭头的长度来解决这个问题。只要轴的尺寸不发生变化,这种方法就可以很好地工作,这可能是由于set_xlim(),set_ylim()或tight_layout()例如。我想通过在绘图尺寸发生变化时重新绘制箭头来涵盖这些情况。目前我通过注册一个函数on_draw(event)来处理这个axes.get_figure().canvas.mpl_connect("resize_event", on_draw)但这仅适用于交互式后端。我还需要一个解决方案,将绘图保存为图像文件。还有其他地方可以注册我的回调函数吗?
查看完整描述

2 回答

?
Helenr

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)



查看完整回答
反对 回复 2021-07-21
  • 2 回答
  • 0 关注
  • 364 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信