简体   繁体   English

matplotlib:在堆叠散点图中对齐y轴标签

[英]matplotlib: Aligning y-axis labels in stacked scatter plots

In the plot bellow i have two scatter plots which have different number scale, so their Y-axis labels are not aligned. 在下图中我有两个散点图,它们具有不同的数字刻度,因此它们的Y轴标签没有对齐。 Is there any way I can force the horizontal alignment in the y-axis labels? 有什么办法可以在y轴标签上强制水平对齐吗?

import matplotlib.pylab as plt
import random
import matplotlib.gridspec as gridspec

random.seed(20)
data1 = [random.random() for i in range(10)]
data2 = [random.random()*1000 for i in range(10)]

gs = gridspec.GridSpec(2,1)
fig = plt.figure()

ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r'Label One', size =16)

ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r'Label Two', size =16)

plt.show()

堆积的散点图

You can use the set_label_coords method. 您可以使用set_label_coords方法。

import matplotlib.pylab as plt
import random
import matplotlib.gridspec as gridspec

random.seed(20)
data1 = [random.random() for i in range(10)]
data2 = [random.random()*1000 for i in range(10)]

gs = gridspec.GridSpec(2,1)
fig = plt.figure()

ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r'Label One', size =16)
ax.get_yaxis().set_label_coords(-0.1,0.5)

ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r'Label Two', size =16)
ax.get_yaxis().set_label_coords(-0.1,0.5)

在此输入图像描述

Since the writing of this question matplotlib has added an easy to use function that aligns labels. 自编写此问题以来,matplotlib添加了一个易于使用的功能,可以对齐标签。 The correct way to force alignment of the labels is to use the function fig.align_labels() before showing the figure. 强制对齐标签的正确方法是在显示图形之前使用函数fig.align_labels()

If you need more fine grained control, you may also use the functions Figure.align_xlabels() or Figure.align_ylabels() . 如果需要更精细的粒度控制,还可以使用函数Figure.align_xlabels()Figure.align_ylabels()

Here is a working version of the code posted in the question. 这是问题中发布的代码的工作版本。 Only one line has been added (the second to last line) to enact the solution. 只添加了一行(第二行到最后一行)来制定解决方案。

import matplotlib.pylab as plt
import random
import matplotlib.gridspec as gridspec

random.seed(20)
data1 = [random.random() for i in range(10)]
data2 = [random.random()*1000 for i in range(10)]

gs = gridspec.GridSpec(2,1)
fig = plt.figure()

ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r'Label One', size =16)

ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r'Label Two', size =16)

fig.align_labels()
plt.show()

Please refer to the Matplotlib Documentation on Aligning Labels for more information. 有关更多信息,请参阅有关对齐标签的Matplotlib文档

As posted in the comment, what you are looking for is solved using set_label_coords() as described here . 正如在评论中发布的那样,您正在寻找的是使用此处所述的 set_label_coords()来解决的。 For your case it will be something like: 对于你的情况,它将是这样的:

labelx = -0.5

ax = fig.add_subplot(gs[0])
ax.plot(data1)
ax.set_ylabel(r'Label One', size=16)
ax.yaxis.set_label_coords(labelx, 0.5)

ax = fig.add_subplot(gs[1])
ax.plot(data2)
ax.set_ylabel(r'Label Two', size=16)
ax.yaxis.set_label_coords(labelx, 0.5)

Here is a function I've written up for automatically aligning the labels, but it doesn't seem to work in a script, only interactively. 这是我为自动对齐标签而编写的一个函数,但它似乎不能在脚本中工作,只能以交互方式工作。

def align_labels(axes_list,axis='y',align=None):
    if align is None:
        align = 'l' if axis == 'y' else 'b'
    yx,xy = [],[]
    for ax in axes_list:
        yx.append(ax.yaxis.label.get_position()[0])
        xy.append(ax.xaxis.label.get_position()[1])

    if axis == 'x':
        if align in ('t','top'):
            lim = max(xy)
        elif align in ('b','bottom'):
            lim = min(xy)
    else:
        if align in ('l','left'):
            lim = min(yx)
        elif align in ('r','right'):
            lim = max(yx)

    if align in ('t','b','top','bottom'):
        for ax in axes_list:
            t = ax.xaxis.label.get_transform()
            x,y = ax.xaxis.label.get_position()
            ax.xaxis.set_label_coords(x,lim,t)
    else:
        for ax in axes_list:
            t = ax.yaxis.label.get_transform()
            x,y = ax.yaxis.label.get_position()
            ax.yaxis.set_label_coords(lim,y,t)

And an example: 一个例子:

fig,ax = subplots(2,2)
ax00,ax01 = ax[0]
ax10,ax11 = ax[1]
ax00.set_ylim(1000,5000)
ax00.set_ylabel('top')
ax10.set_ylabel('bottom')
ax10.set_xlabel('left')
ax11.set_xlabel('right')
ax11.xaxis.axis_date()
fig.autofmt_xdate()
#we have to call draw() so that matplotlib will figure out the automatic positions
fig.canvas.draw()
align_labels(ax[:,0],'y')
align_labels(ax[1],'x')

示例图

I provide a solution at the end, but first I tell which way does not lead to success. 我最后提供了一个解决方案,但首先我告诉哪种方式不会带来成功。

I revisited this issue recently, and spent quite some time with trying various solutions, ie trying almost all possible combinations of transformations between the different coordinate systems, and their relationship with the tight_layout() . 我最近重新审视了这个问题,并花了很长时间尝试各种解决方案,即尝试几乎所有可能的不同坐标系之间的转换组合,以及它们与tight_layout()的关系。 I experimented only with backend_pdf , so I can not tell about interactive media. 我只用backend_pdf实验,所以我无法讲述交互式媒体。 But briefly, my conclusion is that no matter how you try to find out the positions and try to transform them, it is not possible at this level to align the axis labels. 但简而言之,我的结论是,无论您如何尝试找出位置并尝试对其进行转换,在此级别上都无法对齐轴标签。 I guess somehow it should be possible, for example somehow internally matplotlib is able to align the axes of the subplots themselves. 我想它应该是可能的,例如某种程度上matplotlib内部能够对齐子图本身的轴。 Only with drawing into the pdf file twice and doing something like below inbetween, I could achieve better positions, but still not aligned: 只有两次绘制到pdf文件并执行类似下面的内容,我才能获得更好的位置,但仍然没有对齐:

# sorry for the `self`, this is from a class
def align_x_labels(self):
    self.lowest_ax = min(self.axes.values(),
                         key = lambda ax: ax.xaxis.label.get_position()[1])
    self.lowest_xlab_dcoo = self.lowest_ax.transData.transform(
        self.lowest_ax.xaxis.label.get_position())
    list(
        map(
                lambda ax: \
                    ax.xaxis.set_label_coords(
                        self.fig.transFigure.inverted().transform(
                            ax.transAxes.transform((0.5, 0.5)))[0],
                        self.fig.transFigure.inverted().transform(
                            self.lowest_xlab_dcoo)[1],
                        transform = self.fig.transFigure
                    ),
                self.axes.values()
            )
    )

It is a shame such a basic functionality can not be achieved, and it is obscure how the different coordinate spaces are transformed and rescaled at the different steps of plotting. 令人遗憾的是,无法实现这样的基本功能,并且在不同的绘图步骤中如何变换和重新缩放不同的坐标空间是模糊的。 I would appreciate very much to see a clear explanation of this, because the matplotlib webpage only outlines the architecture, presents simple cases, but fails to explain situations like this. 我非常感谢看到对此的明确解释,因为matplotlib网页仅概述了体系结构,提供了简单的案例,但未能解释这样的情况。 Also I am surprised that methods accepting or returning coordinates do not tell in their docstring what types of coordinates are those. 另外,我很惊讶接受或返回坐标的方法并没有告诉他们的文档字符串是什么类型的坐标。 Finally I found very useful this tutorial . 最后我发现本教程非常有用。

Solution

At the end, instead of messing with the transformations, I created in the GridSpec an additional row of zero height and invisible axes (same can be done with a zero width column for y axis labels). 最后,我没有弄乱变换,而是在GridSpec创建了一个零高度和不可见轴的附加行(对于y轴标签,可以使用零宽度列完成相同的行)。 Then I added labels for these subplots, setting the verticalalignment to top . 然后我为这些子图添加了标签,将verticalalignment设置为top

# get one of the zero height phantom subplots to `self.ax`:
self.get_subplot(i, 1)
# set empty ticklabels:
self.ax.xaxis.set_ticklabels([])
self.ax.yaxis.set_ticklabels([])
# set the axis label:
self.ax.set_xlabel(labtext, fontproperties = self.fp_axis_lab)
# and this is matter of aesthetics
# sometimes bottom or center might look better:
self.ax.xaxis.label.set_verticalalignment('top')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM