如何使用matplotlib设置图形窗口的绝对位置?

59

我正在编写一个简单的Python应用程序,使用matplotlib在屏幕上显示几个图形。生成的图形数量基于用户输入并随着应用程序的运行而改变。用户可以发出“plot”命令来生成新的图形窗口,并选择数据系列。为了提高用户体验,我想提供另一个命令,以便以某种方便的方式(例如将它们平铺在可用屏幕空间中)以编程方式排列所有打开的图形窗口。

我相信已经找到了允许我调整图形窗口大小(以像素为单位)的API,但是还没有找到设置它们在屏幕上绝对位置的方法。是否有办法在不深入研究正在使用的后端的细节的情况下进行操作?我想以一种与后端无关的方式进行操作,以避免依赖可能会在未来更改的实现细节。

11个回答

54

找到了QT后端的解决方案:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
mngr = plt.get_current_fig_manager()
# to put it into the upper left corner for example:
mngr.window.setGeometry(50,100,640, 545)

如果不知道x和y的宽度,可以先读取它们,方法如下:

# get the QTCore PyRect object
geom = mngr.window.geometry()
x,y,dx,dy = geom.getRect()

然后使用相同的大小设置新位置:

mngr.window.setGeometry(newX, newY, dx, dy)

6
请注意,我们可以使用fig.canvas.manager代替mngr = get_current_fig_manager()。 - poppie
2
首先,fig, ax = subplots() 是不起作用的。它必须是 fig, ax = plt.subplots()。然后,在 mngr.window.setGeometry() 上,我得到了“# AttributeError: SetPosition”。 - Apostolos
首先,感谢您友好地指出代码中的错误。其次,我不明白如何使用属性“setGeometry()”会导致提到SetPosition的属性错误。可能是因为您的PyQT没有激活为后端。请尝试使用“ipython --pylab=pyqt5”来确保pyqt处于活动状态。 - K.-Michael Aye
1
在Pycharm中,我遇到了AttributeError:'_tkinter.tkapp'对象没有属性'setGeometry' - Adriaan
1
另外需要注意的是,这个函数只能控制图形区域本身的位置,标题栏和边框(由操作系统提供)是额外的。在默认设置下的Windows 10中,标题栏高度为30像素,边框宽度为1像素,所以你的窗口宽度将会是newX + 2,高度将会是newY + 32,并且可能会部分超出屏幕。 - sandyscott
显示剩余2条评论

40

在此之前得到的回答以及自己的一些尝试帮助下,我得出了一个解决方案,它会检查当前后端并使用正确的语法。

import matplotlib
import matplotlib.pyplot as plt

def move_figure(f, x, y):
    """Move figure's upper left corner to pixel (x, y)"""
    backend = matplotlib.get_backend()
    if backend == 'TkAgg':
        f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))
    elif backend == 'WXAgg':
        f.canvas.manager.window.SetPosition((x, y))
    else:
        # This works for QT and GTK
        # You can also use window.setGeometry
        f.canvas.manager.window.move(x, y)

f, ax = plt.subplots()
move_figure(f, 500, 500)
plt.show()

1
嘿,我刚试了一下,出现了一个错误:AttributeError: 'AxesSubplot' object has no attribute 'canvas' - Rich
3
好的,请把 plt.show 函数放到函数之外,因为它会阻塞执行。这么做的主要原因是当你有多个图形时可能会用到。 - supergra
1
@supergra谢谢你的提示,我进行了编辑...我总是在交互模式下运行pyplot,这样它就不会阻塞我,但没有理由不进行更改。 - cxrodgers
2
好的提示。我将其添加到我的ipython启动脚本中。还在move_figure函数中添加了if isinstance(f,int) : f = plt.figure(f),以便它可以使用图形编号或对象。(即 move_figure(1,500,500) - argentum2f
1
谢谢。这最初在我的系统上没有起作用,因为默认使用“MacOSX”后端,但如果我将事情设置为matplotlib.use(“TkAgg”),它就可以了。值得一提的是,我刚刚在Reddit上发布了关于我的学习经验的帖子。 - Jeffrey Goldberg
显示剩余2条评论

17

据我所知,没有一种与后端无关的方法来做到这一点,但肯定可以针对一些常见的后端(如WX、tkagg等)来实现。

import matplotlib
matplotlib.use("wx")
from pylab import *
figure(1)
plot([1,2,3,4,5])
thismanager = get_current_fig_manager()
thismanager.window.SetPosition((500, 0))
show()

根据下面评论区的@tim所说,你可能想要切换到

thismanager.window.wm_geometry("+500+0")

相反使用TkAgg,只需将其更改为

thismanager.window.wm_geometry("+500+0")

所以我认为,如果强制使用特定的后端不是一个选项,你可以通过所有能够实现此功能的后端进行排查。


谢谢。我在我的系统上使用TKAgg,但我不认为我可以假设最终用户也会使用它。不过,我可能可以强制要求用户必须使用其中的某些子集。 - Jason R
@Jason R,这取决于指定位置在你的应用程序中有多重要。如果仅仅是为了改善用户体验,你可以选择任何你能处理的子集,否则,你能否生成一个窗口,并动态地将每个图形放置为子图形,同时擦除旧的子图形? - nye17
3
不知道为什么,但对于我来说(在Windows、Python Idle、Python 2.7上,从Notepad++启动),thismanager.window.wm_geometry("+500+0")有效,而thismanager.window.SetPosition((500, 0))则无效。;-) - tim
3
尝试两种解决方案后,我得到了“'MainWindow' 对象没有 'SetPosition' 属性”和“'MainWindow' 没有 'wm_geometry' 属性”的错误提示。 - pretzlstyle
1
我复制粘贴了你的代码,但是出现了以下错误:1)在 plt.use("wx") 上,我得到了 "AttributeError: 'module' object has no attribute 'use'" 的错误。2)在 mngr.window.SetPosition() 上,我得到了 "# AttributeError: SetPosition" 的错误。(我运行的是 PY ver 2.7) - Apostolos
1
更正上面的评论:我在 matplotlib.use("wx") 上遇到了以下错误:“AttributeError: 'module' object has no attribute 'use'”。然后当然 mngr.window.SetPosition() 就无法工作。 - Apostolos

8
对于Qt4Agg,这对我有用。
fig = figure()
fig.canvas.manager.window.move(0,0)

在Win7操作系统上测试,mpl版本为1.4.2,python版本为2.7.5。


6

受@theo回答的启发,我编写了一个脚本来将窗口移动并调整大小到屏幕上特定的标准位置。这已经在Qt4Agg后端进行了测试:

import matplotlib.pyplot as plt

def move_figure(position="top-right"):
    '''
    Move and resize a window to a set of standard positions on the screen.
    Possible positions are:
    top, bottom, left, right, top-left, top-right, bottom-left, bottom-right
    '''

    mgr = plt.get_current_fig_manager()
    mgr.full_screen_toggle()  # primitive but works to get screen size
    py = mgr.canvas.height()
    px = mgr.canvas.width()

    d = 10  # width of the window border in pixels
    if position == "top":
        # x-top-left-corner, y-top-left-corner, x-width, y-width (in pixels)
        mgr.window.setGeometry(d, 4*d, px - 2*d, py/2 - 4*d)
    elif position == "bottom":
        mgr.window.setGeometry(d, py/2 + 5*d, px - 2*d, py/2 - 4*d)
    elif position == "left":
        mgr.window.setGeometry(d, 4*d, px/2 - 2*d, py - 4*d)
    elif position == "right":
        mgr.window.setGeometry(px/2 + d, 4*d, px/2 - 2*d, py - 4*d)
    elif position == "top-left":
        mgr.window.setGeometry(d, 4*d, px/2 - 2*d, py/2 - 4*d)
    elif position == "top-right":
        mgr.window.setGeometry(px/2 + d, 4*d, px/2 - 2*d, py/2 - 4*d)
    elif position == "bottom-left":
        mgr.window.setGeometry(d, py/2 + 5*d, px/2 - 2*d, py/2 - 4*d)
    elif position == "bottom-right":
        mgr.window.setGeometry(px/2 + d, py/2 + 5*d, px/2 - 2*d, py/2 - 4*d)


if __name__ == '__main__':

    # Usage example for move_figure()

    plt.figure(1)
    plt.plot([0, 1])
    move_figure("top-right")

    plt.figure(2)
    plt.plot([0, 3])
    move_figure("bottom-right")

我该如何添加一个中间选项? - Rich

3
这也是可以实现的:
fig = figure()
fig.canvas.manager.window.Move(100,400)

如果你想将图形发送到图像,并希望使用默认的图像管理器打开它(通常会记住位置),请使用这里提供的方法:
fig.savefig('abc.png')
from PIL import Image
im = Image.open("abc.jpg")
im.rotate(0).show()

1
使用小写字母 m 来表示 move - user1329187
将前两个评论结合起来:至少在使用Qt后端(Qt4Agg,可能也包括Qt5Agg)时,正确的调整大小命令是fig.canvas.manager.window.move(100,400)。似乎对于GTK3Agg也是如此,因此我会编辑这个答案。 - Filip S.

3
以下方法适用于我:

import matplotlib  
matplotlib.use("TkAgg") # set the backend  

if backend == 'TkAgg':  
    f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))

为了在此基础上进行构建:mngr = plt.get_current_fig_manager() mngr.canvas.manager.window.wm_geometry("+%d+%d" % (x, y)) - Supamee

2
'''This is a way to resize the window to a given fraction of the screen.
It uses the screenSize in pixels. User specifies the fx and fy fraction
of the sreen or just a fraction. Couldn't fine how to really position the
window though. No hints in the current figmanager could be found.
But of course, this could be combined with mgr.canvas.move()

'''

import matplotlib.pyplot as plt
#pylab

def screenPos(f):
   '''reset window on screen to size given by fraction f
   where f may by a scalar or a tuple, and where all values
   are 0<=f<=1
   '''
   if type(f)==float: f=(f,) # assert we have a tuple
   mgr = plt.get_current_fig_manager()
   mgr.full_screen_toggle() # primitive but works
   py = mgr.canvas.height()
   px = mgr.canvas.width()
   mgr.resize(f[0]*px,f[-1]*py)
   return f[0]*px,f[-1]*py

px,py = screenPos(0.8)

2
def show_img(img, title=""):
    plt.imshow(img)
    plt.title(title)
    thismanager = plt.get_current_fig_manager()
    thismanager.window.wm_geometry("+100+100")
    plt.show()

1

对于 Windows 平台,您可以从Pyfig安装和使用 pyfig 模块。

以下是有关如何操作图形窗口的示例:

import pylab as p
import pyfig as fig
for ix in range(6): f = p.figure(ix)
fig.stack('all')
fig.stack(1,2)
fig.hide(1)
fig.restore(1)
fig.tile()
fig.pile()
fig.maximize(4)
fig.close('all')

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