matplotlib 子图之间的箭头
Arrows between matplotlib subplots
我决定尝试一下 this 示例代码。我能够弄清楚如何在两个子图之间画一条直线,即使这条线在其中一个子图的边界之外。
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
fig = plt.figure(figsize=(10, 5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
axs = [ax1, ax2]
# Fixing random state for reproducibility
np.random.seed(19680801)
# generate some random test data
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
# plot violin plot
axs[0].violinplot(all_data,
showmeans=False,
showmedians=True)
axs[0].set_title('Violin plot')
# plot box plot
axs[1].boxplot(all_data)
axs[1].set_title('Box plot')
# adding horizontal grid lines
for ax in axs:
ax.yaxis.grid(True)
ax.set_xticks([y + 1 for y in range(len(all_data))])
ax.set_xlabel('Four separate samples')
ax.set_ylabel('Observed values')
for tick in ax.xaxis.get_major_ticks():
tick.label.set_fontsize(20)
plt.setp(axs[0], xticklabels=['x1', 'x2', 'x3', 'x4'])
transFigure = fig.transFigure.inverted()
coord1 = transFigure.transform(ax1.transData.transform([5,10]))
coord2 = transFigure.transform(ax2.transData.transform([2,-10]))
line = mpl.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]),
c='k', lw=5, transform=fig.transFigure)
fig.lines.append(line)
是的,添加的那行很难看,但我只是想让它发挥作用。
但是,我真正想做的是在子图之间制作一个箭头,如果没有 jury-rigging 我自己的箭头尾巴,我无法弄清楚。有没有办法使用 matplotlib.pyplot.arrow
class?
我也想在两个子图之间画一个箭头,但我什至不知道从哪里开始!然而,原始问题中子图示例之间的界线给了我足够的线索开始......
首先,我将原始问题中的代码简化为一个最小的工作示例:
from matplotlib import lines, pyplot as plt
fig = plt.figure()
# First subplot
ax1 = fig.add_subplot(121)
plt.plot([0, 1], [0, 1])
# Second subplot
ax2 = fig.add_subplot(122)
plt.plot([0, 1], [0, 1])
# Add line from one subplot to the other
xyA = [0.5, 1.0]
ax1.plot(*xyA, "o")
xyB = [0.75, 0.25]
ax2.plot(*xyB, "o")
transFigure = fig.transFigure.inverted()
coord1 = transFigure.transform(ax1.transData.transform(xyA))
coord2 = transFigure.transform(ax2.transData.transform(xyB))
line = lines.Line2D(
(coord1[0], coord2[0]), # xdata
(coord1[1], coord2[1]), # ydata
transform=fig.transFigure,
color="black",
)
fig.lines.append(line)
# Show figure
plt.show()
这会产生以下输出:
然后,使用 this blog post, I thought the answer was to create a matplotlib.patches.FancyArrowPatch
and append it to fig.patches
(instead of creating a matplotlib.lines.Line2D
and appending it to fig.lines
). After consulting the matplotlib.patches.FancyArrowPatch
documentation,加上一些试验和错误,我想出了一些在 matplotlib 3.1.2 中有效的东西:
from matplotlib import patches, pyplot as plt
fig = plt.figure()
# First subplot
ax1 = fig.add_subplot(121)
plt.plot([0, 1], [0, 1])
# Second subplot
ax2 = fig.add_subplot(122)
plt.plot([0, 1], [0, 1])
# Add line from one subplot to the other
xyA = [0.5, 1.0]
ax1.plot(*xyA, "o")
xyB = [0.75, 0.25]
ax2.plot(*xyB, "o")
transFigure = fig.transFigure.inverted()
coord1 = transFigure.transform(ax1.transData.transform(xyA))
coord2 = transFigure.transform(ax2.transData.transform(xyB))
arrow = patches.FancyArrowPatch(
coord1, # posA
coord2, # posB
shrinkA=0, # so tail is exactly on posA (default shrink is 2)
shrinkB=0, # so head is exactly on posB (default shrink is 2)
transform=fig.transFigure,
color="black",
arrowstyle="-|>", # "normal" arrow
mutation_scale=30, # controls arrow head size
linewidth=3,
)
fig.patches.append(arrow)
# Show figure
plt.show()
但是,根据下面的评论,这在 matplotlib 3.4.2
中不起作用,你会得到这个:
请注意,箭头的末端没有与目标点(橙色圆圈)对齐,而它们应该对齐。
此 matplotlib
版本更改也导致原始行示例以同样的方式失败。
不过,还有更好的补丁!使用 ConnectionPatch
(docs), which is a subclass of FancyArrowPatch
, instead of using FancyArrowPatch
directly as the ConnectionPatch
is designed specifically for this use case and deals with the transform more correctly, as shown in this matplotlib
documentation example:
fig = plt.figure()
# First subplot
ax1 = fig.add_subplot(121)
plt.plot([0, 1], [0, 1])
# Second subplot
ax2 = fig.add_subplot(122)
plt.plot([0, 1], [0, 1])
# Add line from one subplot to the other
xyA = [0.5, 1.0]
ax1.plot(*xyA, "o")
xyB = [0.75, 0.25]
ax2.plot(*xyB, "o")
# ConnectionPatch handles the transform internally so no need to get fig.transFigure
arrow = patches.ConnectionPatch(
xyA,
xyB,
coordsA=ax1.transData,
coordsB=ax2.transData,
# Default shrink parameter is 0 so can be omitted
color="black",
arrowstyle="-|>", # "normal" arrow
mutation_scale=30, # controls arrow head size
linewidth=3,
)
fig.patches.append(arrow)
# Show figure
plt.show()
这会在 matplotlib 3.1.2
和 matplotlib 3.4.2
中产生正确的输出,如下所示:
要在 matplotlib 3.4.2
中绘制一条连接两个子图的正确位置的线,请像上面那样使用 ConnectionPatch
,但要使用 arrowstyle="-"
(即没有箭头,所以只有一条线)。
注意:您不能使用:
plt.arrow
因为它会自动添加到当前轴,所以只出现在一个子图中
matplotlib.patches.Arrow
因为坐标轴图形变换使箭头倾斜
matplotlib.patches.FancyArrow
因为这也会导致箭头倾斜
我决定尝试一下 this 示例代码。我能够弄清楚如何在两个子图之间画一条直线,即使这条线在其中一个子图的边界之外。
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
fig = plt.figure(figsize=(10, 5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
axs = [ax1, ax2]
# Fixing random state for reproducibility
np.random.seed(19680801)
# generate some random test data
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
# plot violin plot
axs[0].violinplot(all_data,
showmeans=False,
showmedians=True)
axs[0].set_title('Violin plot')
# plot box plot
axs[1].boxplot(all_data)
axs[1].set_title('Box plot')
# adding horizontal grid lines
for ax in axs:
ax.yaxis.grid(True)
ax.set_xticks([y + 1 for y in range(len(all_data))])
ax.set_xlabel('Four separate samples')
ax.set_ylabel('Observed values')
for tick in ax.xaxis.get_major_ticks():
tick.label.set_fontsize(20)
plt.setp(axs[0], xticklabels=['x1', 'x2', 'x3', 'x4'])
transFigure = fig.transFigure.inverted()
coord1 = transFigure.transform(ax1.transData.transform([5,10]))
coord2 = transFigure.transform(ax2.transData.transform([2,-10]))
line = mpl.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]),
c='k', lw=5, transform=fig.transFigure)
fig.lines.append(line)
是的,添加的那行很难看,但我只是想让它发挥作用。
但是,我真正想做的是在子图之间制作一个箭头,如果没有 jury-rigging 我自己的箭头尾巴,我无法弄清楚。有没有办法使用 matplotlib.pyplot.arrow
class?
我也想在两个子图之间画一个箭头,但我什至不知道从哪里开始!然而,原始问题中子图示例之间的界线给了我足够的线索开始......
首先,我将原始问题中的代码简化为一个最小的工作示例:
from matplotlib import lines, pyplot as plt
fig = plt.figure()
# First subplot
ax1 = fig.add_subplot(121)
plt.plot([0, 1], [0, 1])
# Second subplot
ax2 = fig.add_subplot(122)
plt.plot([0, 1], [0, 1])
# Add line from one subplot to the other
xyA = [0.5, 1.0]
ax1.plot(*xyA, "o")
xyB = [0.75, 0.25]
ax2.plot(*xyB, "o")
transFigure = fig.transFigure.inverted()
coord1 = transFigure.transform(ax1.transData.transform(xyA))
coord2 = transFigure.transform(ax2.transData.transform(xyB))
line = lines.Line2D(
(coord1[0], coord2[0]), # xdata
(coord1[1], coord2[1]), # ydata
transform=fig.transFigure,
color="black",
)
fig.lines.append(line)
# Show figure
plt.show()
这会产生以下输出:
然后,使用 this blog post, I thought the answer was to create a matplotlib.patches.FancyArrowPatch
and append it to fig.patches
(instead of creating a matplotlib.lines.Line2D
and appending it to fig.lines
). After consulting the matplotlib.patches.FancyArrowPatch
documentation,加上一些试验和错误,我想出了一些在 matplotlib 3.1.2 中有效的东西:
from matplotlib import patches, pyplot as plt
fig = plt.figure()
# First subplot
ax1 = fig.add_subplot(121)
plt.plot([0, 1], [0, 1])
# Second subplot
ax2 = fig.add_subplot(122)
plt.plot([0, 1], [0, 1])
# Add line from one subplot to the other
xyA = [0.5, 1.0]
ax1.plot(*xyA, "o")
xyB = [0.75, 0.25]
ax2.plot(*xyB, "o")
transFigure = fig.transFigure.inverted()
coord1 = transFigure.transform(ax1.transData.transform(xyA))
coord2 = transFigure.transform(ax2.transData.transform(xyB))
arrow = patches.FancyArrowPatch(
coord1, # posA
coord2, # posB
shrinkA=0, # so tail is exactly on posA (default shrink is 2)
shrinkB=0, # so head is exactly on posB (default shrink is 2)
transform=fig.transFigure,
color="black",
arrowstyle="-|>", # "normal" arrow
mutation_scale=30, # controls arrow head size
linewidth=3,
)
fig.patches.append(arrow)
# Show figure
plt.show()
但是,根据下面的评论,这在 matplotlib 3.4.2
中不起作用,你会得到这个:
请注意,箭头的末端没有与目标点(橙色圆圈)对齐,而它们应该对齐。
此 matplotlib
版本更改也导致原始行示例以同样的方式失败。
不过,还有更好的补丁!使用 ConnectionPatch
(docs), which is a subclass of FancyArrowPatch
, instead of using FancyArrowPatch
directly as the ConnectionPatch
is designed specifically for this use case and deals with the transform more correctly, as shown in this matplotlib
documentation example:
fig = plt.figure()
# First subplot
ax1 = fig.add_subplot(121)
plt.plot([0, 1], [0, 1])
# Second subplot
ax2 = fig.add_subplot(122)
plt.plot([0, 1], [0, 1])
# Add line from one subplot to the other
xyA = [0.5, 1.0]
ax1.plot(*xyA, "o")
xyB = [0.75, 0.25]
ax2.plot(*xyB, "o")
# ConnectionPatch handles the transform internally so no need to get fig.transFigure
arrow = patches.ConnectionPatch(
xyA,
xyB,
coordsA=ax1.transData,
coordsB=ax2.transData,
# Default shrink parameter is 0 so can be omitted
color="black",
arrowstyle="-|>", # "normal" arrow
mutation_scale=30, # controls arrow head size
linewidth=3,
)
fig.patches.append(arrow)
# Show figure
plt.show()
这会在 matplotlib 3.1.2
和 matplotlib 3.4.2
中产生正确的输出,如下所示:
要在 matplotlib 3.4.2
中绘制一条连接两个子图的正确位置的线,请像上面那样使用 ConnectionPatch
,但要使用 arrowstyle="-"
(即没有箭头,所以只有一条线)。
注意:您不能使用:
plt.arrow
因为它会自动添加到当前轴,所以只出现在一个子图中matplotlib.patches.Arrow
因为坐标轴图形变换使箭头倾斜matplotlib.patches.FancyArrow
因为这也会导致箭头倾斜