【问题标题】:Python Matplotlib line plot aligned with contour/imshowPython Matplotlib 线图与轮廓/imshow 对齐
【发布时间】:2014-11-18 01:44:56
【问题描述】:

如何使用 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)

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    matplotlib 坐标轴的轮廓由三个东西控制:

    1. 图中的坐标区边界框(由子图规范或特定范围控制,例如 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()
    

    但是,您可能会注意到这看起来不太正确。这是因为第二个子图控制着第一个子图的范围,这是因为我们绘制的顺序。

    基本上,对于共享轴,我们最后绘制的任何内容都将控制初始范围,所以如果我们只是交换我们绘制的顺序:

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

    当然,如果您不关心被链接的绘图的交互式缩放/平移,您可以完全跳过共享轴,然后:

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

    【讨论】:

    • 不。这种解释适用于纵横比,但我无法使用此处的信息(如所示)在图下方生成与图相同宽度的多个图。您提供了一些提示,例如,fig.add_axes([left, bottom, width, height])。我将尝试解决这个问题并回复并获得我自己的赏金:)。
    • @mankoff - 加油! :) 无论如何,希望这能给你一些想法。
    【解决方案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) 来更改框。 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 实际上,这是您的想法吗? matplotlib.org/users/gridspec.html
    • 不,因为它不能处理 imshow 图形所需的纵横比。我确实找到了解决方案(部分归功于您的回答,因此为您分配了奖金)。接受的答案符合我的要求。
    【解决方案3】:

    撰写本文时的两个现有答案很有帮助,但不提供解决方案。解决方案如下。这里使用的关键,而不是其他答案,是直接访问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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-05
      • 1970-01-01
      • 2013-10-23
      • 2020-09-29
      • 2012-12-13
      • 1970-01-01
      相关资源
      最近更新 更多