gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 08:54:42 26 4
gpt4 key购买 nike

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

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

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

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

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

编辑:这是我当前正在使用的代码:

def draw_adaptive_arrow(axes, x, y, dx, dy,
tail_width, head_width, head_ratio, draw_head=True,
shape="full", **kwargs):
from matplotlib.patches import FancyArrow
from matplotlib.transforms import Bbox

arrow = None

def on_draw(event=None):
"""
Callback function that is called, every time the figure is resized
Removes the current arrow and replaces it with an arrow with
recalcualted head
"""
nonlocal tail_width
nonlocal head_width
nonlocal arrow
if arrow is not None:
arrow.remove()
# Create a head that looks equal, independent of the aspect
# ratio
# Hence, a transformation into display coordinates has to be
# performed to fix the head width to length ratio
# In this transformation only the height and width are
# interesting, absolute coordinates are not needed
# -> box origin at (0,0)
arrow_box = Bbox([(0,0),(0,head_width)])
arrow_box_display = axes.transData.transform_bbox(arrow_box)
head_length_display = np.abs(arrow_box_display.height * head_ratio)
arrow_box_display.x1 = arrow_box_display.x0 + head_length_display
# Transfrom back to data coordinates for plotting
arrow_box = axes.transData.inverted().transform_bbox(arrow_box_display)
head_length = arrow_box.width
if head_length > np.abs(dx):
# If the head would be longer than the entire arrow,
# only draw the arrow head with reduced length
head_length = np.abs(dx)
if not draw_head:
head_length = 0
head_width = tail_width
arrow = FancyArrow(
x, y, dx, dy,
width=tail_width, head_width=head_width, head_length=head_length,
length_includes_head=True, **kwargs)
axes.add_patch(arrow)

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



# Some place in the user code...

fig = plt.figure(figsize=(8.0, 3.0))
ax = fig.add_subplot(1,1,1)

# 90 degree tip
draw_adaptive_arrow(
ax, 0, 0, 4, 0, tail_width=0.4, head_width=0.8, head_ratio=0.5
)
# Still 90 degree tip
draw_adaptive_arrow(
ax, 5, 0, 2, 0, tail_width=0.4, head_width=0.8, head_ratio=0.5
)
# Smaller head, since otherwise head would be longer than entire arrow
draw_adaptive_arrow(
ax, 8, 0, 0.5, 0, tail_width=0.4, head_width=0.8, head_ratio=0.5
)
ax.set_xlim(0,10)
ax.set_ylim(-1,1)

# Does not work in non-interactive backend
plt.savefig("test.pdf")
# But works in interactive backend
plt.show()

最佳答案

这是一个没有回调的解决方案。我从问题中接管了大部分算法,因为我不确定我是否理解箭头的要求。我很确定这可以简化,但这也超出了问题的重点。

因此,我们在这里对 FancyArrow 进行子类化,并让它将自身添加到坐标区中。然后,我们重写draw方法来计算所需的参数,然后——这在某种程度上是不寻常的,并且在其他情况下可能会失败——在draw方法中再次调用__init__

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyArrow
from matplotlib.transforms import Bbox

class MyArrow(FancyArrow):

def __init__(self, *args, **kwargs):
self.ax = args[0]
self.args = args[1:]
self.kw = kwargs
self.head_ratio = self.kw.pop("head_ratio", 1)
self.draw_head = self.kw.pop("draw_head", True)
self.kw.update(length_includes_head=True)
super().__init__(*self.args,**self.kw)
self.ax.add_patch(self)
self.trans = self.get_transform()

def draw(self, renderer):
self.kw.update(transform = self.trans)

arrow_box = Bbox([(0,0),(0,self.kw["head_width"])])
arrow_box_display = self.ax.transData.transform_bbox(arrow_box)
head_length_display = np.abs(arrow_box_display.height * self.head_ratio)
arrow_box_display.x1 = arrow_box_display.x0 + head_length_display
# Transfrom back to data coordinates for plotting
arrow_box = self.ax.transData.inverted().transform_bbox(arrow_box_display)
self.kw["head_length"] = arrow_box.width
if self.kw["head_length"] > np.abs(self.args[2]):
# If the head would be longer than the entire arrow,
# only draw the arrow head with reduced length
self.kw["head_length"] = np.abs(self.args[2])
if not self.draw_head:
self.kw["head_length"] = 0
self.kw["head_width"] = self.kw["width"]

super().__init__(*self.args,**self.kw)
self.set_clip_path(self.ax.patch)
self.ax._update_patch_limits(self)
super().draw(renderer)



fig = plt.figure(figsize=(8.0, 3.0))
ax = fig.add_subplot(1,1,1)

# 90 degree tip
MyArrow( ax, 0, 0, 4, 0, width=0.4, head_width=0.8, head_ratio=0.5 )

MyArrow( ax, 5, 0, 2, 0, width=0.4, head_width=0.8, head_ratio=0.5 )
# Smaller head, since otherwise head would be longer than entire arrow
MyArrow( ax, 8, 0, 0.5, 0, width=0.4, head_width=0.8, head_ratio=0.5 )
ax.set_xlim(0,10)
ax.set_ylim(-1,1)

# Does not work in non-interactive backend
plt.savefig("test.pdf")
# But works in interactive backend
plt.show()

关于python - Matplotlib:自定义函数,每次绘制图形时调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52858022/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com