自动获取子图的坐标,以便将它们设置为图例的自动定位

Get automatically coordinates of subplots in order to set them for automatic positioning of legend

我第一次尝试手动设置Getdist tool制作的主要情节的主要图例的位置。

下图表示来自具有联合分布的协方差矩阵的 1/2 西格玛置信水平。它由 Getdist tool.

制作

生成此图的主要例程是:

    # g.settings
    g = plots.get_subplot_plotter()
    g.settings.figure_legend_frame = True
    g.settings.legend_fontsize = 21
        g.triangle_plot([matrix1, matrix2],
                          names,
                          filled = True,
                          contour_colors = ['darkblue','red'],
                          line_args = [{'lw':2, 'color':'darkblue'},
                          {'lw':2, 'color':'red'}]
                          )

g.add_legend(['Opt. Flat. No Gamma. - cross - standard situation - Criterion taking into accound a = 200',\
  'Pess. Flat. No Gamma. - cross - standard situation - Criterion taking into account a = 300' ],\
  bbox_to_anchor = [1.5, 8.5])

数值1.5好像对应图例的x-coordinate(宽度)8.5对应图例的y-coordinate(高度)

现在,我想自动执行此过程,而不是每次都手动设置图例的位置。

我希望图例的右上角位于第一个左上角框的顶部边框(正好在“1sigma ± 0.0012”标题下方的顶部边框水平)。

我还希望将图例推到图的右侧(直到图右下框的右边框:由 sigma8 "1sigma ± 0.001" title 标识;注意:我希望它位于 1.0 和 0.0 xticks 之前,就在右线边框的 x-coordinate 处)。

这里是我尝试获取左上框顶部边框的全局坐标(整个图)的内容:

# First, get y coordinates of top border for first Likelihood
  box1 = g.subplots[0,0]
  box1_coords = box1._position.bounds
  print('box1_coords = ', box1_coords)

我在执行时得到以下值:

box1_coords =  (0.125, 0.7860975609756098, 0.09451219512195125, 0.09390243902439022)

如您所见,这些值似乎是标准化的,所以如果我想将这些值插入到其中,我不知道如何处理:

bbox_to_anchor = [box1_coords[0], box1_coords[1]]

正如预期的那样,这行代码产生了错误的图例位置。

那么,我如何设法自动为 bbox_to_anchor 分配好的值以获得我想要的(y-coordinate 在由 [=23= 标识的左上框的顶部边框级别) ]) 并在右侧向上推到右下框的右边界(x-coordinate 由 sigma8 用 "1sigma ± 0.001" title 识别)?

更新 1

我尝试根据我的情况调整它们,但问题仍然存在。这是我所做的:

# g.settings
g = plots.get_subplot_plotter()

# get the max y position of the top left axis
top_left_plot = g.subplots[0,0].axes.get_position().ymax
# get the max x position of the bottom right axis
# it is -1 to reference the last plot
bottom_right_plot = g.subplots[-1,-1].axes.get_position().xmax

不知道为什么top_left_plotbottom_right_plot的值不是很好

我认为 subplots[0,0](对于图例的顶部 y-coordinate)指的是左上角的子图,subplots[-1,-1] 指的是右下角的子图(对于图例的右侧 x-coordinate传说)但考虑到这一点,它不起作用。

例如:

# g.settings
g = plots.get_subplot_plotter()
# Call triplot
g.triangle_plot([matrix1, matrix2],
                names,
                filled = True,
                legend_labels = [],
                contour_colors = ['darkblue','red'],
                line_args = [{'lw':2, 'color':'darkblue'},
                {'lw':2, 'color':'red'}])

g.add_legend(['Opt. Flat. No Gamma. - cross - standard situation - Criterion taking into accound a = 200',
'Pess. Flat. No Gamma. - cross - standard situation - Criterion taking into account a = 300'],
legend_loc='upper right',
bbox_to_anchor=(bottom_right_plot, top_left_plot)
)

我得到:

legend_coords y_max, x_max  0.88 0.9000000000000001

我不明白为什么 g.add_legend 没有考虑这些值(似乎介于 0.0 和 1.0 之间)。

@mullinscr的解,得到下图:

如果我通过强制获取图例位置的坐标:

top_left_plot = 8.3
bottom_right_plot = 1.0

这看起来像这个 post 的第一张图。但是这两个值并没有像它应该的那样包含在 0.0 和 1.0 之间。

更新 2

@mullinscr,谢谢,我关注了你的更新,但总是遇到问题。如果我直接在我的脚本中应用相同的代码片段,即:

g.add_legend(['An example legend - item 1'],
         legend_loc='upper right', # we want to specify the location of this point
         bbox_to_anchor=(bottom_right_plot, top_left_plot),
         bbox_transform=plt.gcf().transFigure, # this is the x and y co-ords we extracted above
         borderaxespad=0, # this means there is no padding around the legend
         edgecolor='black')

然后我得到下图:

如您所见,坐标并非真正预期的那样:x-coordinatey-coordinate 存在轻微偏移。

如果我将您的代码片段应用到我的图例文本中,我得到:

我给你我整个脚本的 link,与预期的相比,这可能会让你更容易看到错误:

My entire Python script

基本上按照您描述的方式工作。轴的 bboxes (xmin, ymin, width, height) 以图中的分数给出,plt.legend() 使用相同的格式,因此两者兼容。通过将图例的 upper right 角设置为最外侧轴定义的角,您可以获得干净的布局,而不必担心图例的确切大小。

import matplotlib.pyplot as plt

n = 4

# Create the subplot grid
# Alternative: fig, ax = plt.subplots(n, n); ax[i, j].remove() for j > i
fig = plt.figure()
gs = fig.add_gridspec(nrows=n, ncols=n)
ax = np.zeros((n, n), dtype=object)
for i in range(n):
    for j in range(n):
        if j <= i:
            ax[i, j] = fig.add_subplot(gs[i, j])
# add this to make the position of the legend easier to spot
ax[0, -1] = fig.add_subplot(gs[0, -1])

# Plot some dummy data 
ax[0, 0].plot(range(10), 'b-o', label='Dummy Label 4x4')

# Set the legend
y_max = ax[0][0].get_position().ymax
x_max = ax[-1][-1].get_position().xmax
fig.legend(loc='upper right', bbox_to_anchor=(x_max, y_max),
           borderaxespad=0)

plt.show()

一些陷阱可能是使用 Constrained Layout 或者在保存文件时使用 bbox_inches='tight',因为两者都以意想不到的方式搞砸了图例的位置。

有关图例放置的更多示例,我找到了 this collection 很有帮助。

这是我的回答,与@scleronomic 的回答相同,但我会指出一些在解决这个问题时让我感到困惑的事情。

下面是我的代码,用于重现您想要的定位,我尝试为您创建相同的子图布局,但通过 matplotlib 而不是 getdist —— 虽然结果相同。

如您所见,诀窍在于提取第一个和最后一个轴(top-left 和 lower-right)的位置数据,以供参考。您使用的边界方法 returns:轴的 x0、y0、宽度和高度(请参阅文档)。但是我们想要的是 maximum x 和 y,这样我们的图例角就在右上角。这可以通过使用 xmax 和 ymax 方法来实现:

axes.flatten()[-1].get_position().xmax
axes.flatten()[0].get_position().ymax

一旦我们有了这些变量,就可以将它们传递到 add_legend() 函数的 bbox_to_anchor 参数中,就像您所做的那样。但是,如果我们也使用 loc='upper right',它会告诉 matplotlib 我们希望将图例的右上角固定到这个右上角。最后,我们需要设置 borderaxespad=0 否则由于默认填充,图例不会准确地位于我们想要的位置。

请查看下面我的示例代码以及生成的图片。请注意,我留下了 top-right 图,因此您可以看到它正确排列。

另外,请注意,正如@scleronomic 所说,调用 plt.tight_layout() 等会弄乱这个定位。

import matplotlib.pyplot as plt

# code to layout subplots as in your example:
# --------------------------------------------
g, axes = plt.subplots(nrows=7, ncols=7,figsize=(10,10))

unwanted = [1,2,3,4,5,9,10,11,12,13,17,
            18,19,20,25,26,27,33,34,41]

for ax in axes.flatten():
    ax.plot([1,2], [1,2])
    ax.set_yticks([])
    ax.set_xticks([])

for n, ax in enumerate(axes.flatten()):    
    if n in unwanted:
        ax.remove()
        
# Code to answer your question:
# ------------------------------

# get the max y position of the top left axis
top_left_plot = axes.flatten()[0].get_position().ymax
# get the max x position of the bottom right axis
# it is -1 to reference the last plot
bottom_right_plot = axes.flatten()[-1].get_position().xmax

# I'm using the matplotlib so it is g.legend() not g.add_legend
# but g.add_legend() should work the same as it is a wrapper of th ematplotlib func
g.legend(['Opt. Flat. No Gamma. - cross - standard situation - Criterion taking into accound a = 200',
  'Pess. Flat. No Gamma. - cross - standard situation - Criterion taking into account a = 300'],
         loc='upper right', # we want to specify the location of this point
         bbox_to_anchor=(bottom_right_plot, top_left_plot), # this is the x and y co-ords we extracted above
         borderaxespad=0, # this means there is no padding around the legend
         edgecolor='black') # I set it black for this example

plt.show()

更新

在 @youpilat13 的评论之后,我进行了更多调查并安装了 getdist 以尝试使用该工具重新创建。最初我得到了相同的结果,但发现诀窍在于,与在 matplotlib 中进行此操作不同,您必须将图例的坐标转换为图形坐标。这可以通过 g.add_legend() 调用中的以下内容实现:

bbox_transform=plt.gcf().transFigure

这是一个完整的例子:

import getdist
from getdist import plots, MCSamples
from getdist.gaussian_mixtures import GaussianND
covariance = [[0.001**2, 0.0006*0.05, 0], [0.0006*0.05, 0.05**2, 0.2**2], [0, 0.2**2, 2**2]]
mean = [0.02, 1, -2] 
gauss=GaussianND(mean, covariance)
g = plots.get_subplot_plotter(subplot_size=3)
g.triangle_plot(gauss,filled=True)

top_left_plot = g.subplots.flatten()[0].get_position().ymax
bottom_right_plot = g.subplots.flatten()[-1].get_position().xmax

g.add_legend(['An example legend - item 1'],
         legend_loc='upper right', # we want to specify the location of this point
         bbox_to_anchor=(bottom_right_plot, top_left_plot),
         bbox_transform=plt.gcf().transFigure, # this is the x and y co-ords we extracted above
         borderaxespad=0, # this means there is no padding around the legend
         edgecolor='black')

生成的图像: