作者热门文章
- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我想创建一个包含箭头的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/
我是一名优秀的程序员,十分优秀!