Python Matplotlib的线性图与轮廓/imshow对齐

10
如何使用Python和Matplotlib将一个子图的可视宽度设置为另一个子图的宽度?第一个图具有固定的纵横比和来自imshow的正方形像素。然后我想在下面放置一条线图,但无法这样做并使一切对齐。
我相当确定解决方案涉及此Transform Tutorial页面上的信息。我尝试过使用fig.transFigure、ax.transAxes、ax.transData等进行操作,但没有成功。我需要找到上面面板中轴线的宽度、高度和偏移量,然后能够设置下面面板中轴线的宽度、高度和偏移量。轴标签、刻度等不应包含或更改对齐方式。
例如,以下代码:
fig = plt.figure(1)
fig.clf()

data = np.random.random((3,3))
xaxis = np.arange(0,3)
yaxis = np.arange(0,3)

ax = fig.add_subplot(211)
ax.imshow(data, interpolation='none')
c = ax.contour(xaxis, yaxis, data, colors='k')

ax2 = fig.add_subplot(212)

enter image description here

3个回答

8
Matplotlib的轴线外框由三个因素控制:
  1. 轴线在图中的边界框(由subplot规范或特定范围控制,例如 fig.add_axes([left, bottom, width, height]))。轴线限制(不包括刻度标签)将始终在此框内。
  2. adjustable参数控制着通过更改数据限制或轴线“框”的形状来适应于限制或纵横比的变化。这可以是"datalim""box""box-forced"。(后者用于共享轴。)
  3. 轴线限制和纵横比。对于具有固定纵横比的情况,轴线框或数据限制(取决于adjustable)将更改以维护指定的纵横比。纵横比指的是数据坐标,而不是轴线直接的形状。
对于最简单的情况:
import numpy as np
import matplotlib.pyplot as plt

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

data = np.random.random((3,3))
xaxis = np.arange(0,3)
yaxis = np.arange(0,3)

axes[0].imshow(data, interpolation='none')
c = axes[0].contour(xaxis, yaxis, data, colors='k')

axes[1].set_aspect(1)

plt.show()

输入图像描述


共享坐标轴

但是,如果你想确保它始终保持相同的形状,并且同时使用相同的数据限制,可以这样做:

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2), sharex=True, sharey=True)
plt.setp(axes.flat, adjustable='box-forced')

data = np.random.random((5,3))
xaxis = np.arange(0,3)
yaxis = np.arange(0,5)

axes[0].imshow(data, interpolation='none')
c = axes[0].contour(xaxis, yaxis, data, colors='k')

axes[1].plot([-0.5, 2.5], [-0.5, 4.5])
axes[1].set_aspect(1)

plt.show()

enter image description here

然而,您可能会注意到这看起来不太对。这是因为由于我们绘制图形的顺序,第二个子图正在控制第一个子图的范围。

基本上,使用共享坐标轴,无论我们最后绘制什么都将控制最初的范围,所以如果我们只是交换绘制的顺序:

    import numpy as np
    import matplotlib.pyplot as plt

    fig, axes = plt.subplots(nrows=2, sharex=True, sharey=True)
    plt.setp(axes.flat, adjustable='box-forced')

    data = np.random.random((5,3))
    xaxis = np.arange(0,3)
    yaxis = np.arange(0,5)

    axes[1].plot([-0.5, 2.5], [-0.5, 4.5])
    axes[1].set_aspect(1)

    axes[0].imshow(data, interpolation='none')
    c = axes[0].contour(xaxis, yaxis, data, colors='k')

    plt.show()

enter image description here

当然,如果您不关心图表的交互式缩放/平移是否链接,则可以完全跳过共享轴,并仅执行以下操作:

import numpy as np
import matplotlib.pyplot as plt

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

data = np.random.random((5,3))
xaxis = np.arange(0,3)
yaxis = np.arange(0,5)

axes[0].imshow(data, interpolation='none')
c = axes[0].contour(xaxis, yaxis, data, colors='k')

axes[1].plot([-0.5, 2.5], [-0.5, 4.5])

# Copy extents and aspect from the first axes...
axes[1].set_aspect(axes[0].get_aspect())
axes[1].axis(axes[0].axis())

plt.show()

非共享轴

如果您不希望两个轴具有相同的数据范围,可以强制它们具有相同的大小(尽管如果您进行交互式缩放,则它们将不会链接)。为此,您需要根据第一个绘图的范围和范围/纵横比计算第二个绘图的纵横比。

import numpy as np
import matplotlib.pyplot as plt

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

data = np.random.random((3,3))
xaxis = np.arange(0,3)
yaxis = np.arange(0,3)

axes[0].imshow(data, interpolation='none')
c = axes[0].contour(xaxis, yaxis, data, colors='k')

axes[1].plot(np.linspace(0, 10, 100), np.random.normal(0, 1, 100).cumsum())

# Calculate the proper aspect for the second axes
aspect0 = axes[0].get_aspect()
if aspect0 == 'equal':
    aspect0 = 1.0
dy = np.abs(np.diff(axes[1].get_ylim()))
dx = np.abs(np.diff(axes[1].get_xlim()))

aspect = aspect0 / (float(dy) / dx)
axes[1].set_aspect(aspect)

plt.show()

enter image description here


不行。这个解释对于纵横比很好,但是我无法使用这里提供的信息(如所示)生成与图形相同宽度的多个图,在图形下方。您提供了一些提示,例如fig.add_axes([left, bottom, width, height])。我将尝试弄清楚并回复以获得自己的赏金 :)。 - mankoff
@mankoff - 加油! :) 希望这至少能给你一些想法。 - Joe Kington

2

您是否需要相对于第一个轴进行任意定位?您可以尝试使用figure的bbox进行操作。

ax2.set_position(ax.get_position().translated(0, -.5))会将第二个轴放置在第一个轴下方,并保持相同的基本形状。或者您也可以采用以下方法:

box = ax.get_position()

# Positioning code here

ax2.set_position(box)

在你的定位代码中,通过重新分配 (box = box.translated(0, -.5)) 或变异 (box.x1 += .1) 来更改框。Box 似乎使用属性 .p0、.x0、.y0 和 .p1、.x1、.y1 暴露了其左下角和右上角点;以及 .width 和 .height。

Box 大致上是一个图形坐标系,你也可以使用明确的原始数字 "设置宽度、高度和偏移量":ax2.set_position([left, bottom, width, height])

PS: 不幸的是,这个 bbox 也包括文本标签在内的宽度和高度。例如,你的第一个绘图的宽度为 0.27...,高度为 0.36... 通过更改尺寸,你不会扭曲文本,但这意味着要得到一个完美的正方形很难,除非你从一个正方形开始。


而 PS 意味着第二个图,具有不同的轴标签,将不能与脊柱对齐。:(。 - mankoff
@mankoff 实际上,这是您想要的更多内容吗?http://matplotlib.org/users/gridspec.html - Redoubts
不行,因为它不能处理imshow图形所需的纵横比。我找到了一个解决方案(部分归功于您的答案,因此给您分配了奖金)。被接受的答案做到了我想要的。 - mankoff

2

在撰写本文时,已有两个答案,虽然有帮助,但并没有提供解决方案。下面是一个解决方案。关键在于直接访问spine位置,而其他答案中没有提到。在访问之前需要使用plt.draw()更新坐标。

import numpy as np
from matplotlib import pyplot as plt
im = np.arange(256).reshape(16,16)

fig = plt.figure(1)
fig.clf()
ax = fig.add_subplot(211)

ax.imshow(im)
plt.show()

transAxes = ax.transAxes
invFig = fig.transFigure.inverted()

llx,urx = plt.xlim()
lly,ury = plt.ylim()

llx0, lly0 = transAxes.transform((0,0))
llx1, lly1 = transAxes.transform((1,1))

plt.draw()
spleft = ax.spines['left'].get_verts()
spright = ax.spines['right'].get_verts()
llx0 = spleft[0,0]
llx1 = spright[0,0]

axp = invFig.transform(((lly0,llx0),(lly1,llx1)))
ax2 = fig.add_axes([axp[0,1],axp[0,0]-0.5,axp[1,1]-axp[0,1],axp[1,0]-axp[0,0]])
ax2.plot(np.arange(10))
plt.draw()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接