【问题标题】:Arrows between matplotlib subplotsmatplotlib 子图之间的箭头
【发布时间】:2020-03-23 04:02:42
【问题描述】:

我决定尝试一下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)

是的,添加的行很丑,但我只是想让它发挥作用。

但是,我真正想做的是在子图之间画一个箭头,如果没有陪审团操纵我自己的箭头尾巴,我不知道怎么做。有没有办法使用matplotlib.pyplot.arrow 类来做到这一点?

【问题讨论】:

    标签: python matplotlib subplot


    【解决方案1】:

    我还想在两个子图之间画一个箭头,但我什至不知道从哪里开始!但是,原始问题中子图示例之间的界线给了我足够的线索来开始......

    首先,我将原始问题中的代码简化为一个最小的工作示例:

    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,我认为答案是创建一个matplotlib.patches.FancyArrowPatch 并将其附加到fig.patches(而不是创建一个matplotlib.lines.Line2D 并将其附加到fig.lines )。在咨询了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()
    

    但是,根据下面的 cmets,这在 matplotlib 3.4.2 中不起作用,您可以从那里得到:

    请注意,箭头的末端不与目标点(橙色圆圈)对齐,他们应该这样做。

    matplotlib 版本更改也会导致原始行示例以同样的方式失败。

    但是,有一个更好的补丁!使用ConnectionPatch (docs),它是FancyArrowPatch 的子类,而不是直接使用FancyArrowPatch,因为ConnectionPatch 是专门为此用例设计的,可以更正确地处理转换,如下所示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.2matplotlib 3.4.2 中产生正确的输出,如下所示:

    要绘制一条连接matplotlib 3.4.2 中两个子图的正确定位线,请使用ConnectionPatcharrowstyle="-"(即没有箭头,所以只有一条线)。

    注意:您不能使用:

    • plt.arrow 因为它会自动添加到当前坐标区,所以只出现在一个子图中

    • matplotlib.patches.Arrow 轴图形变换使箭头倾斜

    • matplotlib.patches.FancyArrow 因为这也会导致箭头倾斜

    【讨论】:

    • 很接近,但对我来说,箭头的头部和尾部只比我用coord1coord2设置的高一点。有什么方法可以让这些 y 坐标准确吗?
    • 是的。使用matplotlib.patches.FancyArrowPatch 参数shrinkA=0(尾部)和shrinkB=0(头部)。出于某种原因,默认值为 2。答案已相应编辑。
    • “收缩”参数似乎对我没有任何作用。我想知道这是否与我的图形后端 Gt5 有关。我尝试将其更改为自动,但没有效果。需要明确的是,只有 y1(在尾部),而不是箭头的 x1、x2 或 y2 太高。
    • 这是您自己的小提琴图示例还是我发布的最小工作示例?您能否标记预期点以查看它有多远(例如start = [0.5, 1.0]; ax1.plot(*start, marker="x"); end = [0.75, 0.25]; ax2.plot(*end, marker="x"); coord1 = transFigure.transform(ax1.transData.transform(start)); coord2 = transFigure.transform(ax2.transData.transform(end)))并上传输出图像?
    • 啊,我现在明白了! matplotlib 中发生了一些狡猾的事情。使用 python 3.8.5 和 matplotlib 3.1.2,图像如上所述是正确的。但是对于 matplotlib 3.4.2,箭头位置是错误的。但是,有一个更好的补丁!使用 ConnectionPatch(文档:v.ht/A2V3),它是 FancyArrowPatch 的子类,而不是直接使用 FancyArrowPatch,因为 ConnectionPatch 是专门为此用例设计的,可以更正确地处理转换(例如:v.ht/ZMTh)。发表此评论后,我会相应地更新答案。
    猜你喜欢
    • 2021-04-24
    • 1970-01-01
    • 1970-01-01
    • 2017-03-17
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    相关资源
    最近更新 更多