Python Matplotlib线图与contour / imshow对齐

时间:2014-11-18 01:44:57

标签: python matplotlib

如何使用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 个答案:

答案 0 :(得分:8)

matplotlib轴的轮廓由三件事控制:

  1. 图中的轴边界框(由子图规范或特定范围控制,如fig.add_axes([left, bottom, width, height])。轴限制(不计入刻度标签)将始终位于此框内。
  2. adjustable参数控制是通过更改数据限制还是轴的形状“框”来控制限制或宽高比的变化。这可以是"datalim""box""box-forced"。 (后者用于共享轴。)
  3. 轴限制和纵横比。对于具有固定纵横比的图,将更改轴框或数据限制(取决于adjustable)以保持指定的纵横比。宽高比是指数据坐标,不是直接的轴的形状。
  4. 对于最简单的情况:

    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()
    

    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[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

答案 1 :(得分:2)

您是否在寻找相对于第一轴的任意定位?你可以玩这个人物的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)来改变框。框似乎显示了它的左下角和右上角,其属性为.p0,.x0,.y0和.p1,.x1,.y1;以及.width和.height

Box或多或少是一个数字坐标,您只需设置宽度,高度和偏移量即可。原始数字也是明确的:ax2.set_position([left, bottom, width, height])

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

答案 2 :(得分: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()