具有不同y轴的子图纵横比

9 投票
3 回答
12391 浏览
提问于 2025-04-17 16:04

我希望下面的代码能生成4个大小相同的子图,并且它们的x轴和y轴的比例是我自己设定的。根据下面的例子,我希望所有的子图看起来都和左上角的第一个子图一模一样。现在的问题是,y轴的大小和它的最大值有关联。这种情况是我想要避免的。

import matplotlib.pyplot as plt
import numpy as np

def main(): 

    fig = plt.figure(1, [5.5, 3])
    for i in range(1,5):
        fig.add_subplot(221+i-1, adjustable='box', aspect=1) 
        plt.plot(np.arange(0,(i)*4,i))

    plt.show()

if __name__ == "__main__": 
    main()

令人惊讶的是,matplotlib默认情况下会生成正确的效果(如下图所示):

   import  matplotlib.pyplot as plt 
   import numpy as np 

   def main(): 
       fig = plt.figure(1, [5.5, 3]) 
       for i in range(1,5): 
           fig.add_subplot(221+i-1) 
            plt.plot(np.arange(0,(i)*4,i)) 
       plt.show() 

我只是想在此基础上增加一个功能,让我可以控制x轴和y轴长度之间的比例。

这是我想要的效果:

3 个回答

1

理论部分

在matplotlib中,存在着不同的坐标系统。这些坐标系统之间的差异常常让很多人感到困惑。提问者想要的是显示坐标中的长宽比,但ax.set_aspect()实际上是在设置数据坐标中的长宽比。它们之间的关系可以用以下公式表示:

aspect = 1.0/dataRatio*dispRatio

这里,aspectset_aspect方法中使用的参数,dataRatio是数据坐标中的长宽比,而dispRatio你想要的显示坐标中的长宽比

实践部分

我们可以使用一个叫做get_data_ratio的方法来让我们的代码更简洁。下面是一个工作代码片段:

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=2, ncols=2)

dispRatio = 0.5
for i, ax in enumerate(axes.flat, start=1):
    ax.plot(np.arange(0, i * 4, i))
    ax.set(aspect=1.0/ax.get_data_ratio()*dispRatio, adjustable='box-forced')

plt.show()

我还写了一篇详细的文章,讲述了这些内容,可以在这里查看

2

结合了Joe Kington的回答和关于在matplotlib中共享轴的方形子图的新Python风格的内容,以及我找不到的另一篇帖子,我写了一段代码,可以精确设置框的比例到一个指定的值。

这里的desired_box_ratioN表示你想要的框的y边和x边之间的比例。temp_inverse_axis_ratioN是当前图形中x边和y边的比例;因为'aspect'是y和x的比例(而不是坐标轴),所以我们需要把aspect设置为desired_box_ratioN乘以temp_inverse_axis_ratioN。

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=2, ncols=2)

desired_box_ratioN = 1
for i, ax in enumerate(axes.flat, start=1):
    ax.plot(np.arange(0, i * 4, i))
    temp_inverse_axis_ratioN = abs( (ax.get_xlim()[1] - ax.get_xlim()[0])/(ax.get_ylim()[1] - ax.get_ylim()[0]) )
    ax.set(aspect = desired_box_ratioN * temp_inverse_axis_ratioN, adjustable='box-forced')

plt.show()
10

我不太明白你想问什么。

你是想让所有的图都有相同的数据范围吗?

如果是这样的话,可以使用共享坐标轴(我这里用的是 subplots,但如果你想用类似matlab的代码,可以不使用它):

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=2, ncols=2, sharey=True, sharex=True)
for i, ax in enumerate(axes.flat, start=1):
    ax.set(aspect=1)
    ax.plot(np.arange(0, i * 4, i))

plt.show()

enter image description here

如果你想让它们共享坐标轴的范围,但又希望有 adjustable='box'(也就是坐标轴的边界不是正方形),可以使用 adjustable='box-forced'

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=2, ncols=2, sharey=True, sharex=True)
for i, ax in enumerate(axes.flat, start=1):
    ax.set(aspect=1, adjustable='box-forced', xticks=range(i))
    ax.plot(np.arange(0, i * 4, i))

plt.show()

enter image description here


编辑:抱歉,我还是有点困惑。你想要的是这样的效果吗?

import matplotlib.pyplot as plt 
import numpy as np 

fig, axes = plt.subplots(nrows=2, ncols=2)
for i, ax in enumerate(axes.flat, start=1):
    ax.set(adjustable='datalim', aspect=1)
    ax.plot(np.arange(0, i * 4, i))

plt.show()

enter image description here


好吧,我想我终于明白你的问题了。我们对“长宽比”的理解完全不同。

在matplotlib中,图的长宽比指的是数据范围的相对比例。换句话说,如果图的长宽比是1,那么斜率为1的线会呈现45度角。你是认为长宽比是应用在坐标轴的轮廓上,而不是坐标轴上绘制的数据。

你只是想让子图的轮廓是正方形。(在这种情况下,它们的长宽比都是不同的,这是matplotlib定义的。)

在这种情况下,你需要一个正方形的图形。(还有其他方法,但制作一个正方形的图形要简单得多。matplotlib的坐标轴会填充与它们所在图形大小成比例的空间。)

import matplotlib.pyplot as plt 
import numpy as np 

# The key here is the figsize (it needs to be square). The position and size of
# axes in matplotlib are defined relative to the size of the figure.
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8,8))

for i, ax in enumerate(axes.flat, start=1):
    ax.plot(np.arange(0, i * 4, i))

# By default, subplots leave a bit of room for tick labels on the left.
# We'll remove it so that the axes are perfectly square.
fig.subplots_adjust(left=0.1)

plt.show()

enter image description here

撰写回答