How do you set the absolute position of figure windows with matplotlib?
Asked Answered
S

11

64

I'm writing a simple Python application that uses matplotlib to display a few figures on screen. The number of figures generated is based on user input and changes throughout the application's life. The user has the ability to issue a "plot" command to generate a new figure window with the selected data series. In order to improve the user experience, I would like to provide another command that would programmatically arrange all open figure windows in some convenient arrangement (e.g. tile them across the available screen space).

I believe to have found APIs that allow me to adjust the size of the figure window (in pixels), but haven't had any success in finding a way to set their absolute position on screen. Is there a way to do this without delving into the details of whatever backend is in use? I would like to do this in a backend-agnostic way so I can avoid relying upon implementation details that might change in the future.

Scullery answered 16/9, 2011 at 19:41 Comment(0)
H
18

there is not that I know a backend-agnostic way to do this, but definitely it is possible to do it for some common backends, e.g., WX, tkagg etc.

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

per @tim at the comment section below, you might wanna switch to

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

instead. For TkAgg, just change it to

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

So I think you can exhaust through all the backends that are capable of doing this, if imposing a certain one is not an option.

Hemphill answered 16/9, 2011 at 21:58 Comment(6)
Thanks. I'm using TKAgg on my system, but I don't think I can assume that the end user will. I might be able to enforce that the user must use some subset of them, though.Scullery
@Jason R it really depends on how important this specified positioning is in your application. If it were merely for improving user experience, you can simply go for whichever subset you can take care of, otherwise, can you generate only one window, and dynamically place each plot as sub_plots while erasing old sub_plots?Hemphill
Dunno why, but for me (Windows, Python Idle, Python 2.7, started from Notepad++) the thismanager.window.wm_geometry("+500+0") worked, the thismanager.window.SetPosition((500, 0)) didnt ;-)Jedediah
Trying both solutions, I get 'MainWindow' object has no attribute 'SetPosition' and 'MainWindow' has no attribute 'wm_geometry'Firework
I copy-pasted your code and I get the following errors: 1) on plt.use("wx"), I get "AttributeError: 'module' object has no attribute 'use'" 2) on mngr.window.SetPosition(), I get "# AttributeError: SetPosition". (I run PY ver 2.7)Osmunda
Correction of the above comment: I got the following error on matplotlib.use("wx"): "AttributeError: 'module' object has no attribute 'use'". Then of course mngr.window.SetPosition() won't work.Osmunda
P
55

Found the solution for QT backend:

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)

If one doesn't know the x- and y-width one can read them out first, like so:

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

and then set the new position with the same size:

mngr.window.setGeometry(newX, newY, dx, dy)
Psoas answered 13/11, 2013 at 1:30 Comment(7)
note that instead of mngr = get_current_fig_manager(), we can also use fig.canvas.managerBasie
First of all, fig, ax = subplots() doesn't work. It must be fig, ax = plt.subplots(). Then, on mngr.window.setGeometry(), I get "# AttributeError: SetPosition".Osmunda
First of all, thanks for the friendly pointer to the bug in the code. Second, I don't understand how the use of the attribute "setGeometry()" leads to an attribute error mentioning SetPosition. Possibly, your PyQT isn't activated as the backend. Try ipython --pylab=pyqt5 to make sure pyqt is active.Psoas
In Pycharm I get AttributeError: '_tkinter.tkapp' object has no attribute 'setGeometry'Coccidiosis
You are in the wrong post, this post is the QT solution, you obviously work with tkinter.Psoas
Another note: this positions the figure area itself, the title bar and borders (provided by the OS) are extra. On Windows 10 with default settings, this is a 30px title bar and 1 px border all the way round, so your window will be newX + 2 wide and newY + 32 tall, and could be partially outside the screen.Bicephalous
AttributeError: 'FigureManagerBase' object has no attribute 'window'Toshiatoshiko
T
43

With help from the answers thus far and some tinkering on my own, here's a solution that checks for the current backend and uses the correct syntax.

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()
Tripterous answered 23/6, 2016 at 18:39 Comment(7)
Hey, I just tried this and got an error: AttributeError: 'AxesSubplot' object has no attribute 'canvas'Trip
This is the only answer here that would work for me, specifically using f.canvas.manager.window.move(). The syntax is simplest as well.Firework
Nice, but move plt.show outside the function, because it blocks. The main reason you might want this is specifically when you have many figures!Unpaid
@Unpaid thanks for the tip, I edited ... I always run pyplot in interactive mode so it doesn't block for me, but no reason not to make the changeTripterous
Good tip. I added this to my ipython startup script. Also added if isinstance(f,int) : f = plt.figure(f) to the move_figure function so it works with figure numbers or objects. (i.e. move_figure(1,500,500))Burushaski
Thank you. This did not initially work for me on my system, which is using the "MacOSX" backend by default, but if I set things to matplotlib.use("TkAgg") it did. For what it is worth, I just posted on Reddit about my learning experience here.Strangeness
@JeffreyGoldberg if you can figure out the syntax for MacOSX backend, I will add it to the answer as another option. Unfortunately I don't have access to a Mac to discover the proper syntax myselfTripterous
H
18

there is not that I know a backend-agnostic way to do this, but definitely it is possible to do it for some common backends, e.g., WX, tkagg etc.

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

per @tim at the comment section below, you might wanna switch to

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

instead. For TkAgg, just change it to

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

So I think you can exhaust through all the backends that are capable of doing this, if imposing a certain one is not an option.

Hemphill answered 16/9, 2011 at 21:58 Comment(6)
Thanks. I'm using TKAgg on my system, but I don't think I can assume that the end user will. I might be able to enforce that the user must use some subset of them, though.Scullery
@Jason R it really depends on how important this specified positioning is in your application. If it were merely for improving user experience, you can simply go for whichever subset you can take care of, otherwise, can you generate only one window, and dynamically place each plot as sub_plots while erasing old sub_plots?Hemphill
Dunno why, but for me (Windows, Python Idle, Python 2.7, started from Notepad++) the thismanager.window.wm_geometry("+500+0") worked, the thismanager.window.SetPosition((500, 0)) didnt ;-)Jedediah
Trying both solutions, I get 'MainWindow' object has no attribute 'SetPosition' and 'MainWindow' has no attribute 'wm_geometry'Firework
I copy-pasted your code and I get the following errors: 1) on plt.use("wx"), I get "AttributeError: 'module' object has no attribute 'use'" 2) on mngr.window.SetPosition(), I get "# AttributeError: SetPosition". (I run PY ver 2.7)Osmunda
Correction of the above comment: I got the following error on matplotlib.use("wx"): "AttributeError: 'module' object has no attribute 'use'". Then of course mngr.window.SetPosition() won't work.Osmunda
H
8

For Qt4Agg, this worked for me.

fig = figure()
fig.canvas.manager.window.move(0,0)

Tested on Win7, mpl version 1.4.2, python 2.7.5

Hyssop answered 15/5, 2015 at 20:49 Comment(0)
M
6

Inspired by @theo answer, I wrote a script to move and resize a window to a specific standard position on the screen. This was tested with the Qt4Agg backend:

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")
Millesimal answered 13/3, 2015 at 18:46 Comment(1)
How would I go about adding a middle option?Trip
S
3

This also works:

fig = figure()
fig.canvas.manager.window.Move(100,400)

If you want to send a plot to an image and have it open with the default image manager (which likely remembers position) use this from here:

fig.savefig('abc.png')
from PIL import Image
im = Image.open("abc.jpg")
im.rotate(0).show()
Selfexistent answered 14/6, 2013 at 4:43 Comment(2)
with small m in moveBondswoman
To combine the two previous comments: At least when using the Qt backend (Qt4Agg, and probably also Qt5Agg), the correct command to resize is fig.canvas.manager.window.move(100,400). Seems to also be true for GTK3Agg, so I'll edit this answer.Freethinker
P
3

The following worked for me.

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

if backend == 'TkAgg':  
    f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))
Pani answered 27/5, 2018 at 4:57 Comment(1)
to build upon this answer: mngr = plt.get_current_fig_manager() mngr.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))Teagan
E
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)
Exclave answered 6/1, 2015 at 3:9 Comment(0)
M
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()
Multifoil answered 16/2, 2020 at 16:45 Comment(0)
R
1

For the windows platform you could install and use pyfig module from Pyfig.

Example on how to manipulate the figure windows is given below:

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')
Repeat answered 11/1, 2013 at 11:47 Comment(0)
P
0

How about defining a function to raise the window to the top level and move it toward the top-left corner (for example) like this:

def topfig():
    figmgr = get_current_fig_manager()
    figmgr.canvas.manager.window.raise_()
    geom = figmgr.window.geometry()
    x,y,dx,dy = geom.getRect()
    figmgr.window.setGeometry(10, 10, dx, dy)

Then whenever you open a new figure you just type "topfig()". Is there a way to pre-define topfig so it will always be available?

Papotto answered 9/5, 2015 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.