在 matplotlib 中跨子图绘制分界线

Posted

技术标签:

【中文标题】在 matplotlib 中跨子图绘制分界线【英文标题】:Draw dividing line across subplots in matplotlib 【发布时间】:2021-03-30 17:08:54 【问题描述】:

我被要求生成一些用于多基线研究设计的图表,这是一种特殊类型的图表。我借此机会学习了更多关于 Matplotlib 和 Pandas 的知识,但我遇到的一件事是区分 BASE 和 INTERVENTION 的分界线。我需要它继续通过多个子图并很好地扩展。有没有办法完成这样的事情?我尝试过使用 Lines.Line2D 和 ConnectionPatch,但我无法正确地进行缩放和确定位置。

到目前为止我的(简单)代码。

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

y = np.array([0,1,2,3,4])
fig, axs = plt.subplots(3, sharex=True, sharey=True)
fig.suptitle("I1 - Reakce na změnu prvku")
axs[0].plot(df.index,df['A'], color='lightblue', label="A")
axs[1].plot(df.index,df['N'], color='darkblue', label="N")
axs[2].plot(df.index,df['P'], color='blue', label="P")

plt.yticks(np.arange(y.min(), y.max(), 1))
plt.show()

到目前为止我的情节(上面代码的结果):

上下文的示例图:

【问题讨论】:

听起来您想在图表中添加注释,例如垂直或水平虚线以及文本? matplotlib 有这样的注释,我很乐意为您指出正确的方向或给您举个例子 嗯,是的,但更具体地说,我需要子图范围“外部”示例图中的虚线并创建无缝线。我可以在每个子图上画垂直线,这也可以,但我很好奇是否有一种方法可以像示例中那样绘制它。 【参考方案1】:

借用Pablo's helpful answer,似乎使用fig.transFigure 可以访问每个子图中的坐标,并且您可以在所有这些坐标之间画线。这可能是最好的方法,因为它可以直接确定起点和终点。由于您的 x 坐标方便地从 1 到 12,您还可以将每个子图绘制成两部分,以便在点之间留出间隙以供注释线通过。

import numpy as np
import pandas as pd

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch

y = np.array([0,1,2,3,4])

## recreate your data
df = pd.DataFrame(
    'A':[0, 1, 1, 1, 2, 2, 3, 2, 3] + [float("nan")]*3,
    'N':[1, 0, 0, 2, 1, 1, 2, 3, 3, 3, 3, 3],
    'P':[0, 1, 1, 1, 2, 1, 1, 1, 2, 3, 3, 3],
    ,  
    index=range(1,13)
)


fig, axs = plt.subplots(3, sharex=True, sharey=True)
fig.suptitle("I1 - Reakce na změnu prvku")

## create a gap in the line
axs[0].plot(df.index[0:3],df['A'][0:3], color='lightblue', label="A", marker='.')
axs[0].plot(df.index[3:12],df['A'][3:12], color='lightblue', label="A", marker='.')

## create a gap in the line
axs[1].plot(df.index[0:8],df['N'][0:8], color='darkblue', label="N", marker='.')
axs[1].plot(df.index[8:12],df['N'][8:12], color='darkblue', label="N", marker='.')

## create a gap in the line
axs[2].plot(df.index[0:10],df['P'][0:10], color='blue', label="P", marker='.')
axs[2].plot(df.index[10:12],df['P'][10:12], color='blue', label="P", marker='.')

plt.yticks(np.arange(y.min(), y.max(), 1))


transFigure = fig.transFigure.inverted()

## Since your subplots have a ymax value of 3, setting the end y-coordinate
## of each line to just above that value should help it display outside of the figure

coord1 = transFigure.transform(axs[0].transData.transform([3.5,3]))
coord2 = transFigure.transform(axs[1].transData.transform([3.5,3.5]))
coord3 = transFigure.transform(axs[1].transData.transform([8.5,3.5]))
coord4 = transFigure.transform(axs[2].transData.transform([8.5,3.5]))
coord5 = transFigure.transform(axs[2].transData.transform([10.5,3.5]))
coord6 = transFigure.transform(axs[2].transData.transform([10.5,0]))

## add a vertical dashed line
line1 = matplotlib.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a horizontal dashed line
line2 = matplotlib.lines.Line2D((coord2[0],coord3[0]),(coord2[1],coord3[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a vertical dashed line
line3 = matplotlib.lines.Line2D((coord3[0],coord4[0]),(coord3[1],coord4[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a horizontal dashed line
line4 = matplotlib.lines.Line2D((coord4[0],coord5[0]),(coord4[1],coord5[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a vertical dashed line
line5 = matplotlib.lines.Line2D((coord5[0],coord6[0]),(coord5[1],coord6[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

fig.lines.extend([line1, line2, line3, line4, line5])
plt.show()

【讨论】:

哇,非常感谢!这正是我一直在寻找的。您的评论也写得很好且解释性很好,而不是“woodoo magic”代码块。 没问题——很高兴我的回答对你有帮助!【参考方案2】:

我的直觉,这种问题是在图形坐标中画一条线。我遇到的一个问题是找到连续轴之间的中心区域的位置。我的代码很丑,但它可以工作,并且与每个轴的相对大小或轴之间的间距无关,如下所示:

from matplotlib.lines import Line2D
def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    from itertools import zip_longest
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)




xconn = [0.15, 0.35, 0.7]  # position of the vertical lines in each subplot, in data coordinates


fig, axs = plt.subplots(3,1, gridspec_kw=dict(hspace=0.6, height_ratios=[2,0.5,1]))



#
# Draw the separation line, should be done at the very end when the limits of the axes have been set etc.
#
# convert the value of xconn in each axis to figure coordinates
xconn = [fig.transFigure.inverted().transform(ax.transData.transform([x,0]))[0] for x,ax in zip(xconn,axs)]
yconn = []  # y-values of the connecting lines, in figure coordinates
for ax in axs:
    bbox = ax.get_position()
    yconn.extend([bbox.y1, bbox.y0])
# replace each pairs of values corresponding to the bottom and top of each pairs of axes by the average
yconn[1:-1] = np.ravel([[np.mean(ys)]*2 for ys in grouper(yconn[1:-1], 2)]).tolist()

l = Line2D(np.repeat(xconn,2), yconn, transform=fig.transFigure, ls='--', lw=1, c='k')
fig.add_artist(l)

【讨论】:

以上是关于在 matplotlib 中跨子图绘制分界线的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中跨子模块使用强制通用 buildToolsVersion 有啥影响?

如何在 react.js 中跨子组件持久化数据或状态?

matplotlib:子图绘制

Matplotlib:每个时间序列子图绘制多条线

python使用matplotlib:subplot绘制多个子图

使用 matplotlib 子图绘制 pandas groupby 输出