具有不同y轴的子图纵横比
我希望下面的代码能生成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 个回答
理论部分
在matplotlib中,存在着不同的坐标系统。这些坐标系统之间的差异常常让很多人感到困惑。提问者想要的是显示坐标中的长宽比,但ax.set_aspect()
实际上是在设置数据坐标中的长宽比。它们之间的关系可以用以下公式表示:
aspect = 1.0/dataRatio*dispRatio
这里,aspect
是set_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()
我还写了一篇详细的文章,讲述了这些内容,可以在这里查看。
结合了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()
我不太明白你想问什么。
你是想让所有的图都有相同的数据范围吗?
如果是这样的话,可以使用共享坐标轴(我这里用的是 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()
如果你想让它们共享坐标轴的范围,但又希望有 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()
编辑:抱歉,我还是有点困惑。你想要的是这样的效果吗?
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()
好吧,我想我终于明白你的问题了。我们对“长宽比”的理解完全不同。
在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()