matplotlib:调整图形窗口大小而不缩放图形内容

时间:2014-08-20 04:31:59

标签: python matplotlib

当您调整图形大小时,Matplotlib会自动缩放图形窗口中的所有内容。通常这是用户想要的,但我经常想要增加窗口的大小,以便为其他东西腾出更多空间。在这种情况下,我希望预先存在的内容保持与更改窗口大小相同的大小。有谁知道一个干净的方法来做到这一点?

我唯一的想法是只调整图形窗口的大小,允许缩放图形内容,然后手动将每个内容缩放回原始大小。这似乎很痛苦,所以我希望有更好的方法。

3 个答案:

答案 0 :(得分:1)

我查看了AxesDivider模块,但它似乎不太适合我的问题。我也考虑过使用变换堆栈,但是我没有看到使用它的显着优势,而不仅仅是手动缩放。

以下是我提出的建议:

import matplotlib.pyplot as plt
import numpy as np
from copy import deepcopy

#Create the original figure with a plot in it.
x1 = [1,2,3]
y1 = [1,2,3]
fig = plt.figure(figsize = [5,5], facecolor = [0.9,0.9,0.9])
data_ax = fig.add_axes([0.1,0.1,0.8,0.8])
data_ax.plot(x1a, y1a)
plt.savefig('old_fig.png', facecolor = [0.9,0.9,0.9])

这是旧图:

enter image description here

#Set the desired scale factor for the figure window
desired_sf = [2.0, 1.5]
#Get the current figure size using deepcopy() so that it will not be updated when the 
#figure size gets changed 
old_fig_size = deepcopy(fig.get_size_inches())
#Change the figure size.  The forward = True option is needed to make the figure window 
#size update prior to saving.
fig.set_size_inches([old_fig_size[0] * desired_sf[0], old_fig_size[1] * desired_sf[1]], forward = True)
#For some reason, the new figure size does not perfectly match what I specified, so I    
#simply query the figure size after resizing.
fig.canvas.draw()
new_fig_size = fig.get_size_inches()
#Get the actual scaling factor
sf = new_fig_size / old_fig_size

#Go through the figure content and scale appropriately
for ax in fig.axes:
    pos = ax.get_position()
    ax.set_position([pos.x0 / sf[0], pos.y0 / sf[1], pos.width / sf[0], pos.height / sf[1]])

for text in fig.texts:
    pos = np.array(text.get_position())
    text.set_position(pos / sf)

for line in fig.lines:
    x = line.get_xdata()
    y = line.get_ydata()
    line.set_xdata(x / sf[0])
    line.set_ydata(y / sf[1])

for patch in fig.patches:
    xy = patch.get_xy()
    patch.set_xy(xy / sf)

fig.canvas.draw()
plt.savefig('new_fig.png', facecolor = [0.9,0.9,0.9])

这是新图(由于图片托管服务缩放了总图像尺寸,因此图表显示较小):

enter image description here

整蛊部分:

一个棘手的部分是forward = True中的fig.set_size_inches(new_size, forward = True)选项。

第二个棘手的部分是在调用fig.canvas.draw()时意识到数字大小会发生变化,这意味着实际比例因子(sf)不一定与所需的比例因子(desired_sf)匹配。也许如果我使用了转换堆栈,它会自动补偿调用fig.canvas.draw()时数字大小的变化...

答案 1 :(得分:0)

阅读此tutorial以了解转换堆栈的介绍。

简短的回答是,这种行为是matplotlib观察世界的方式所固有的。所有东西都是以相对单位(数据单位,轴分数和图形分数)定位/定义的,它们在渲染过程中只转换为屏幕单位,因此图书馆的任何部分都知道“大”的唯一位置。屏幕单位是数字大小(由fig.set_size_inches控制)。这样就可以根据图形调整大小。

可能对您有所帮助的一个工具是AxesDivider模块,但我对它的经验很少。

答案 2 :(得分:0)

Matplotlib似乎没有一种简单的方法可以在改变图形大小时冻结轴(或画布)大小。可能有一种方法通过“变换”,因为似乎frozen BBoxBase方法可能让人联想到MATLAB函数(在MEX上),但目前Matplotlib网站上没有提供任何文档。

一般来说,我建议单独构建图形结构,然后在实际渲染到新图形尺寸之前调整轴/画布的大小。

例如,在下文中,figure_3x1使用选项字典opts中指定的数字大小为{x = 1}构建一个包含3x1子图的通用数字,其中opts['figsize']为6.5 x 5.5英寸:

def varargin(pars,**kwargs):
"""
varargin-like option for user-defined parameters in any function/module
Use:
pars = varargin(pars,**kwargs)

Input:
- pars     : the dictionary of parameters of the calling function
- **kwargs : a dictionary of user-defined parameters

Output:
- pars     : modified dictionary of parameters to be used inside the calling
             (parent) function
"""
    for key,val in kwargs.iteritems():
        if key in pars:
            pars[key] = val
    return pars

def figure_3x1(**kwargs):
'''
figure_3x1(**kwargs) : Create grid plot for a 1-column complex figure composed of (top-to-bottom):
                     3 equal plots | custom space

Input:
- left,right,top,bottom
- vs      : [val] vertical space between plots
- figsize : (width,height) 2x1 tuple for figure size

Output:
- fig     : fig handler
- ax      : a list of ax handles (top-to-bottom)

'''

    opts = {'left'   : 0.1,
            'right'  : 0.05,
            'bottom' : 0.1,
            'top'    : 0.02,
            'vs'     : [0.03],
            'figsize': (6.5,5.5), # This is the figure size with respect
                                  # to axes will be sized
            }

    # User-defined parameters
    opts = varargin(opts,**kwargs)

    nrow = 3

    # Axis specification
    AxisWidth = 1.0-opts['left']-opts['right']
    AxisHeight = [(1.0-opts['bottom']-opts['top']-2*opts['vs'][0])/nrow]

    # Axis Grid
    # x position
    xpos = opts['left']
    # y position
    ypos = list()
    ypos.append(opts['bottom'])
    ypos.append(ypos[0]+AxisHeight[0]+opts['vs'][0])
    ypos.append(ypos[1]+AxisHeight[0]+opts['vs'][0])

    # Axis boxes (bottom-up)
    axBoxes = list()
    axBoxes.append([xpos,ypos[0],AxisWidth,AxisHeight[0]])
    axBoxes.append([xpos,ypos[1],AxisWidth,AxisHeight[0]])
    axBoxes.append([xpos,ypos[2],AxisWidth,AxisHeight[0]])

    fig = plt.figure(1, figsize=opts['figsize'])

    ax = list()
    for i in xrange(shape(axBoxes)[0]-1,-1,-1):
        ax_aux = fig.add_axes(axBoxes[i])
        ax.append(ax_aux)

    return fig, ax

enter image description here

现在,我想建立一个类似的图形,保持相同大小的轴和相同的位置(相对于左/下角),但在图中有更多的空间。例如,如果我想在其中一个图或多个y轴上添加一个颜色条,然后我有多个3x1图形需要一起显示以用于出版目的,这是有用的。我们的想法是弄清楚新图形尺寸的实际尺寸应该是为了容纳所有新元素 - 假设我们需要一个尺寸为7.5x5.5英寸的图形 - 然后重新调整轴框和它们的xy坐标为了达到这个新的数字尺寸,为了达到6.5x5.5英寸的原始尺寸。为此,我们在axref_size的{​​{1}}中引入了另一个选项optsfigure_3x1(**kwargs)(width, height)类型的元组,用于描述相对于图形大小(以英寸为单位)的数字我们希望在新的更大的数字中构建(和大小)轴。在首先相对于较大的数字计算这些轴的宽度(AxisWidth)和高度(AxisHeight)之后,我们将它们与leftbottom坐标一起调整大小,以及不同轴之间的垂直/水平空间,即hsvs,以达到原始axref_size数字的大小。

实际调整大小是通过以下方法实现的:

def freeze_canvas(opts, AxisWidth, AxisHeight):
'''
Resize axis to ref_size (keeping left and bottom margins fixed)
Useful to plot figures in a larger or smaller figure box but with canvas of the same canvas

Inputs :

opts : Dictionary
 Options for figure plotting. Must contain keywords: 'left', 'bottom', 'hs', 'vs', 'figsize', 'axref_size'.
AxisWidth : Value / List
AxisHeight: Value / List

Return:
opts, AxisWidth, AxisHeight
'''
    # Basic function to proper resize
    resize = lambda obj, dx : [val+dx*val for val in obj] if type(obj)==type(list()) else obj+dx*obj
    if opts['axref_size'] != None :
        # Compute size differences
        dw = (opts['axref_size'][0]-opts['figsize'][0])/opts['figsize'][0]
        dh = (opts['axref_size'][1]-opts['figsize'][1])/opts['figsize'][1]
        for k,v in opts.iteritems():
            if k=='left' or k=='hs' : opts[k] = resize(v,dw)
            if k=='bottom' or k=='vs' : opts[k] = resize(v,dh)
        AxisWidth = resize(AxisWidth,dw)
        AxisHeight = resize(AxisHeight,dh)
    return opts, AxisWidth, AxisHeight

然后将figure_3x1(**kwargs)编辑为:

def figure_3x1(**kwargs):
'''
figure_3x1(**kwargs) : Create grid plot for a 1-column complex figure composed of (top-to-bottom):
                     3 equal plots | custom space
Include also the option of specifying axes size according to a reference figure size
'''

    opts = {'left'   : 0.1,
            'right'  : 0.05,
            'bottom' : 0.1,
            'top'    : 0.02,
            'vs'     : [0.03],
            'figsize': (6.5,5.5), # This is the figure size with respect
                                  # to axes will be sized
            'axref_size': None}

    ...
    ...

    # Axis specification
    AxisWidth = 1.0-opts['left']-opts['right']
    AxisHeight = [(1.0-opts['bottom']-opts['top']-2*opts['vs'][0])/nrow]

    # Optional resizing
    opts, AxisWidth, AxisHeight = freeze_canvas(opts, AxisWidth, AxisHeight)

    # Axis Grid
    # x position

    ...
    ...

    return fig, ax

以这种方式打电话,如

figure_3x1(figsize=(7.5,5.5),axref_size=(6.5,5.5))

生成以下较大的数字(即7.5x5.5英寸),但子图的轴与上图中的6.5x5.5英寸相同。同样,正如其他回复中指出的那样,图表显示较小,因为图像托管服务会缩放总图像大小。

enter image description here