【问题标题】:How can I get tight_layout() in matplotlib to work with inset plots?如何让 matplotlib 中的tight_layout() 用于处理插图?
【发布时间】:2018-02-22 07:51:47
【问题描述】:

我正在使用 matplotlib.pyplot 制作带有多个子图的图。这就是我最终想要的:一个 2x2 的“主要”图阵列。其中每一个在图中都有两条曲线,每条曲线使用不同的 y 轴。此外,我希望在每一个中都有一个较小的插图。

到目前为止,我已经有了第一部分,使用这个工作示例代码:

import matplotlib.pyplot as plt
import numpy as np
import os
import shutil
import time
import sys

#Simplest working example of tight_layout and plots problem

def two_scales(ax1, time, data1, data2, c1, c2, xlabel, y1label, y2label):

    ax2 = ax1.twinx()

    ax1.plot(time, data1, color=c1)
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel(y1label)

    ax2.plot(time, data2, color=c2)
    ax2.set_ylabel(y2label)
    return ax1, ax2

# Change color of each axis
def color_y_axis(ax, color):
    """Color your axes."""
    for t in ax.get_yticklabels():
        t.set_color(color)
    return None

def insetPlots():

    t = np.arange(0.01, 10.0, 0.01)


    #Figure stuff
    fig, baseAxes = plt.subplots(2,2,figsize=(10, 6))
    baseAxesFlattened = baseAxes.flatten()


    for i, dat in enumerate(baseAxesFlattened):

        s1 = np.exp((i+1)*t)
        s2 = .3*np.sin((i+1)*.2 * np.pi * t)

        #Plotting them together
        tempAx1, tempAx2 = two_scales(baseAxesFlattened[i], t, s1, s2, 'b', 'r','heyheyhey','yayaya','woopwoop')

        #Changing the color of the axes
        color_y_axis(tempAx1, 'b')
        color_y_axis(tempAx2, 'r')


    plt.tight_layout()
    #plt.figure(figsize=(6, 8))
    picname="/mypath/testtesttest.png"
    plt.savefig(picname)

insetPlots()

这是什么产生的,到目前为止还不错:

现在我想添加插图。我可以很容易地做到这一点:

import matplotlib.pyplot as plt
import numpy as np
import os
import shutil
import time
import sys

#Simplest working example of tight_layout and plots problem

def two_scales(ax1, time, data1, data2, c1, c2, xlabel, y1label, y2label):

    ax2 = ax1.twinx()

    ax1.plot(time, data1, color=c1)
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel(y1label)

    ax2.plot(time, data2, color=c2)
    ax2.set_ylabel(y2label)
    return ax1, ax2

# Change color of each axis
def color_y_axis(ax, color):
    """Color your axes."""
    for t in ax.get_yticklabels():
        t.set_color(color)
    return None

def insetPlots():

    t = np.arange(0.01, 10.0, 0.01)


    #Figure stuff
    fig, baseAxes = plt.subplots(2,2,figsize=(10, 6))
    baseAxesFlattened = baseAxes.flatten()


    for i, dat in enumerate(baseAxesFlattened):

        s1 = np.exp((i+1)*t)
        s2 = .3*np.sin((i+1)*.2 * np.pi * t)

        #Plotting them together
        tempAx1, tempAx2 = two_scales(baseAxesFlattened[i], t, s1, s2, 'b', 'r','heyheyhey','yayaya','woopwoop')

        #Changing the color of the axes
        color_y_axis(tempAx1, 'b')
        color_y_axis(tempAx2, 'r')

        pos = tempAx1.get_position()
        #print(pos)
        posString = str(pos)
        x0Ind, y0Ind, x1Ind, y1Ind = posString.find('x0'),posString.find('y0'),posString.find('x1'),posString.find('y1')
        #print(x0Ind, y0Ind, x1Ind, y1Ind)
        x0, y0, x1, y1 = float(posString[x0Ind+3:y0Ind-2]), float(posString[y0Ind+3:x1Ind-2]), float(posString[x1Ind+3:y1Ind-2]), float(posString[y1Ind+3:-1])
        #print(x0, y0, x1, y1)

        mainPlotW = x1 - x0
        mainPlotH = y1 - y0

        w, h = 0.3*mainPlotW, 0.25*mainPlotH
        left, bottom, width, height = [x0 + .15*mainPlotW, y0 + .7*mainPlotH, w, h]
        insetAx = fig.add_axes([left, bottom, width, height])

        #insetAx.plot(range(6)[::-1], color='green')
        s3 = np.sin(.2 * np.pi * t/(i+1))
        insetAx.plot(t,s3, color='green')


    #plt.tight_layout()
    #plt.figure(figsize=(6, 8))
    picname="/mypath/testtesttest.png"
    plt.savefig(picname)

insetPlots()

请注意,这里我注释掉了tight_layout()。这会产生这个,它在我想要的位置有插图:

所以这在正确的位置有插图,但是因为tight_layout() 已经消失了,所以主要图的轴标签是重叠的。如果我有 tight_layout() (因此,与上面的代码完全相同,但该行未注释),我会得到:

主要地块的轴不再重叠,但插图现在位于错误的位置。当我运行代码时,我也会收到此警告:

/usr/local/lib/python3.6/dist-packages/matplotlib/figure.py:2022: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  warnings.warn("This figure includes Axes that are not compatible "

我怎样才能让它们都工作?我怀疑我做错了一些简单的事情,比如以错误的方式放置插图。

编辑:我找到了解决方案,但它很难看,我希望不是“正确”的方法。我怀疑tight_layout() 正在四处移动,因此在tight_layout() 之后,插入图的位置(取决于主要图的位置)相对于主要图的位置变得混乱。所以我通过绘制主要情节,进行紧凑布局,然后添加插图解决了这个问题:

import matplotlib.pyplot as plt
import numpy as np
import os
import shutil
import time
import sys

#Simplest working example of tight_layout and plots problem

def two_scales(ax1, time, data1, data2, c1, c2, xlabel, y1label, y2label):

    ax2 = ax1.twinx()

    ax1.plot(time, data1, color=c1)
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel(y1label)

    ax2.plot(time, data2, color=c2)
    ax2.set_ylabel(y2label)
    return ax1, ax2

# Change color of each axis
def color_y_axis(ax, color):
    """Color your axes."""
    for t in ax.get_yticklabels():
        t.set_color(color)
    return None

def insetPlots():

    t = np.arange(0.01, 10.0, 0.01)


    #Figure stuff
    fig, baseAxes = plt.subplots(2,2,figsize=(10, 6))
    baseAxesFlattened = baseAxes.flatten()

    majorAxes = []

    for i, dat in enumerate(baseAxesFlattened):

        s1 = np.exp((i+1)*t)
        s2 = .3*np.sin((i+1)*.2 * np.pi * t)

        #Plotting them together
        tempAx1, tempAx2 = two_scales(baseAxesFlattened[i], t, s1, s2, 'b', 'r','heyheyhey','yayaya','woopwoop')

        majorAxes.append(tempAx1)

        #Changing the color of the axes
        color_y_axis(tempAx1, 'b')
        color_y_axis(tempAx2, 'r')


    plt.tight_layout()

    for i, dat in enumerate(baseAxesFlattened):

        tempAx1 = majorAxes[i]
        pos = tempAx1.get_position()
        #print(pos)
        posString = str(pos)
        x0Ind, y0Ind, x1Ind, y1Ind = posString.find('x0'),posString.find('y0'),posString.find('x1'),posString.find('y1')
        #print(x0Ind, y0Ind, x1Ind, y1Ind)
        x0, y0, x1, y1 = float(posString[x0Ind+3:y0Ind-2]), float(posString[y0Ind+3:x1Ind-2]), float(posString[x1Ind+3:y1Ind-2]), float(posString[y1Ind+3:-1])
        #print(x0, y0, x1, y1)

        mainPlotW = x1 - x0
        mainPlotH = y1 - y0

        w, h = 0.3*mainPlotW, 0.25*mainPlotH
        left, bottom, width, height = [x0 + .15*mainPlotW, y0 + .7*mainPlotH, w, h]
        insetAx = fig.add_axes([left, bottom, width, height])


        #insetAx.plot(range(6)[::-1], color='green')
        s3 = np.sin(.2 * np.pi * t/(i+1))
        insetAx.plot(t,s3, color='green')


    #plt.tight_layout()
    #plt.figure(figsize=(6, 8))
    picname="/mypath/testtesttest.png"
    plt.savefig(picname)

insetPlots()

有没有更简洁的方法来做到这一点?

【问题讨论】:

  • 看来你得出了和我一样的结论。我发帖时没有看到您的编辑。无论如何,我确实认为这是处理您的特定情况的“正确”方式

标签: python matplotlib


【解决方案1】:

我建议使用mpl_toolkits.axes_grid1.inset_locator.InsetPosition 来定位插图。这大大简化了事情,不需要将绘图大小与任何东西相乘。

然后您可以选择在创建插图之前或之后调用fig.tight_layout(),结果图不会改变(尽管在之后调用它会给出警告,在这种情况下您可以忽略)。

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
import numpy as np

def two_scales(ax1, time, data1, data2, c1, c2, xlabel, y1label, y2label):

    ax2 = ax1.twinx()

    ax1.plot(time, data1, color=c1)
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel(y1label)

    ax2.plot(time, data2, color=c2)
    ax2.set_ylabel(y2label)
    return ax1, ax2

# Change color of each axis
def color_y_axis(ax, color):
    """Color your axes."""
    for t in ax.get_yticklabels():
        t.set_color(color)
    return None

def insetPlots():

    t = np.arange(0.01, 10.0, 0.01)
    #Figure stuff
    fig, baseAxes = plt.subplots(2,2,figsize=(10, 6))
    baseAxesFlattened = baseAxes.flatten()

    for i, ax in enumerate(baseAxesFlattened):

        s1 = np.exp((i+1)*t)
        s2 = .3*np.sin((i+1)*.2 * np.pi * t)
        #Plotting them together
        tempAx1, tempAx2 = two_scales(ax, t, s1, s2, 'b', 'r',
                                      'heyheyhey','yayaya','woopwoop')
        #Changing the color of the axes
        color_y_axis(tempAx1, 'b')
        color_y_axis(tempAx2, 'r')

    fig.tight_layout()

    for i, ax in enumerate(baseAxesFlattened):

        insetAx = fig.add_axes([0, 0, 1, 1], label="{}".format(i))
        ip = InsetPosition(ax, [.15, 0.7, 0.3, 0.25]) #posx, posy, width, height
        insetAx.set_axes_locator(ip)

        insetAx.plot(range(6)[::-1], color='green')
        s3 = np.sin(.2 * np.pi * t/(i+1))
        insetAx.plot(t,s3, color='green')

    # putting tight_layout here will produce a warning, 
    # yet the resulting plot is the same
    # fig.tight_layout() 


insetPlots()
plt.show()

【讨论】:

    【解决方案2】:

    tight_layout() 只是用于大多数常见情节的有用工具,但它不能处理所有情况。

    在您的特定情况下,我认为您最好在创建插入轴之前调用tight_layout(),并使用生成的轴位置为您的插入找到正确的坐标

    import matplotlib.pyplot as plt
    import numpy as np
    import os
    import shutil
    import time
    import sys
    
    #Simplest working example of tight_layout and plots problem
    
    def two_scales(ax1, time, data1, data2, c1, c2, xlabel, y1label, y2label):
    
        ax2 = ax1.twinx()
    
        ax1.plot(time, data1, color=c1)
        ax1.set_xlabel(xlabel)
        ax1.set_ylabel(y1label)
    
        ax2.plot(time, data2, color=c2)
        ax2.set_ylabel(y2label)
        return ax1, ax2
    
    # Change color of each axis
    def color_y_axis(ax, color):
        """Color your axes."""
        for t in ax.get_yticklabels():
            t.set_color(color)
        return None
    
    def insetPlots():
    
        t = np.arange(0.01, 10.0, 0.01)
    
    
        #Figure stuff
        fig, baseAxes = plt.subplots(2,2,figsize=(10, 6))
        baseAxesFlattened = baseAxes.flatten()
    
    
        for i, ax in enumerate(baseAxesFlattened):
    
            s1 = np.exp((i+1)*t)
            s2 = .3*np.sin((i+1)*.2 * np.pi * t)
    
            #Plotting them together
            tempAx1, tempAx2 = two_scales(ax, t, s1, s2, 'b', 'r','heyheyhey','yayaya','woopwoop')
    
            #Changing the color of the axes
            color_y_axis(tempAx1, 'b')
            color_y_axis(tempAx2, 'r')
    
        fig.tight_layout()
    
        for i, ax in enumerate(baseAxesFlattened):
            pos = ax.get_position()
            #print(pos)
    
            mainPlotW = pos.x1 - pos.x0
            mainPlotH = pos.y1 - pos.y0
    
            w, h = 0.3*mainPlotW, 0.25*mainPlotH
            left, bottom, width, height = [pos.x0 + .15*mainPlotW, pos.y0 + .7*mainPlotH, w, h]
            insetAx = fig.add_axes([left, bottom, width, height])
    
            insetAx.plot(range(6)[::-1], color='green')
            s3 = np.sin(.2 * np.pi * t/(i+1))
            insetAx.plot(t,s3, color='green')
    
    
    insetPlots()
    

    PS 您正在使用 pos 变量做一些非常奇怪的事情,将其转换为 str,然后再将其转换回 float。我已经在我的代码的第二个循环中简化了你的代码

    【讨论】:

    • 感谢您的回复。关于 pos 变量,我这样做是因为 get_position 返回了一个“bbox”对象,我不知道我可以从中获取 x1/etc 成员,所以我做了一个非常贫民窟的事情来解析字符串以获取数字.. . 但这似乎更聪明。
    猜你喜欢
    • 1970-01-01
    • 2012-01-05
    • 1970-01-01
    • 2020-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多