¿Matplotlib ofrece una función para distribuir varias figuras de manera uniforme en la pantalla? ¿O alguien sabe de una caja de herramientas que sea capaz de lograr esto? Me estoy cansando de hacer esto a mano.

import matplotlib.pyplot as plt
for i in range(5):
    plt.figure()
plt.show()

Esto crea cinco figuras que se mantienen una encima de la otra. Para verificar lo que está en la figura 1, tengo que mover las otras 4 figuras a un lado.

En MacOS, podría usar el atajo Ctrl + solo para echar un vistazo a todas las figuras. Alternativamente, podría escribir las parcelas en archivos e inspeccionar las imágenes en una galería. Pero me preguntaba si existe un administrador de ventanas personalizado para matplotlib que posiblemente ofrezca más flexibilidad.

En Matlab, me acostumbré a herramientas como spreadfigure o autoArrangeFigures.

0
normanius 29 abr. 2020 a las 16:40

2 respuestas

La mejor respuesta

Puede controlar la posición de la ventana de trazado utilizando el administrador de figuras de la siguiente manera:

import matplotlib.pyplot as plt

start_x, start_y, dx, dy = (0, 0, 640, 550)
for i in range(5):
    if i%3 == 0:
        x = start_x
        y = start_y  + (dy * (i//3) )
    plt.figure()
    mngr = plt.get_current_fig_manager()
    mngr.window.setGeometry(x, y, dx, dy)
    x += dx
plt.show()

Esto dará como resultado cinco gráficos que se muestran uno al lado del otro de la siguiente manera: ingrese la descripción de la imagen aquí

¡Con suerte, esto es lo que estás buscando!

1
Anwarvic 29 abr. 2020 a las 16:10

Parece que matplotlib no ofrece una función tan lista para usar. Además, no hay una forma "independiente del backend" para controlar la geometría de la figura, como se discutió aquí.

Por lo tanto, escribí tile_figures() para implementar esta mini-función que extiende la sugerencia de Anwarvic mediante una lógica de mosaico y una simple abstracción de back-end. Actualmente solo admite backends Qt o Tk, pero ciertamente también puede extenderse a otros backends.

Feliz mosaico!


Uso

tile_figures(cols=3, rows=2, screen_rect=None, tile_offsets=None)

# You may have to adjust the available screen area and a tile offset 
# for nice results. This works well for my MacOS.
tile_figure(screen_rect=(0,22,1440,740), tile_offsets=(0,22))

# Run a test with 10 figures. Note that you cannot switch the backend dynamically.
# It's best to set mpl.use(<backend>) at the very beginning of your script.
# https://matplotlib.org/faq/usage_faq.html#what-is-a-backend
test(n_figs=10, backend="Qt5Agg", screen_rect=(0,22,1440,750), tile_offsets=(0,22))

Resultado

enter image description here


Implementación

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

def screen_geometry(monitor=0):
    try:
        from screeninfo import get_monitors
        sizes = [(s.x, s.y, s.width, s.height) for s in get_monitors()]
        return sizes[monitor]
    except ModuleNotFoundError:
        default = (0, 0, 900, 600)
        print("screen_geometry: module screeninfo is no available.")
        print("Returning default: %s" % default)
        return default

def set_figure_geometry(fig, backend, x, y, w, h):
    if backend in ("Qt5Agg", "Qt4Agg"):
        fig.canvas.manager.window.setGeometry(x, y, w, h)
        #fig.canvas.manager.window.statusBar().setVisible(False)
        #fig.canvas.toolbar.setVisible(True)
    elif backend in ("TkAgg",):
        fig.canvas.manager.window.wm_geometry("%dx%d+%d+%d" % (w,h,x,y))
    else:
        print("This backend is not supported yet.")
        print("Set the backend with matplotlib.use(<name>).")
        return

def tile_figures(cols=3, rows=2, screen_rect=None, tile_offsets=None):
    """
    Tile figures. If more than cols*rows figures are present, cols and
    rows are adjusted. For now, a Qt- or Tk-backend is required.

        import matplotlib
        matplotlib.use('Qt5Agg')
        matplotlib.use('TkAgg')

    Arguments: 
        cols, rows:     Number of cols, rows shown. Will be adjusted if the 
                        number of figures is larger than cols*rows.
        screen_rect:    A 4-tuple specifying the geometry (x,y,w,h) of the 
                        screen area used for tiling (in pixels). If None, the 
                        system's screen is queried using the screeninfo module.
        tile_offsets:   A 2-tuple specifying the offsets in x- and y- direction.
                        Can be used to compensate the title bar height.
    """    
    assert(isinstance(cols, int) and cols>0)
    assert(isinstance(rows, int) and rows>0)
    assert(screen_rect is None or len(screen_rect)==4)
    backend = mpl.get_backend()
    if screen_rect is None:
        screen_rect = screen_size()
    if tile_offsets is None:
        tile_offsets = (0,0)
    sx, sy, sw, sh = screen_rect
    sx += tile_offsets[0]
    sy += tile_offsets[1]
    fig_ids = plt.get_fignums()
    # Adjust tiles if necessary.
    tile_aspect = cols/rows
    while len(fig_ids) > cols*rows:
        cols += 1
        rows = max(np.round(cols/tile_aspect), rows)
    # Apply geometry per figure.
    w = int(sw/cols)
    h = int(sh/rows)
    for i, num in enumerate(fig_ids):
        fig = plt.figure(num)
        x = (i%cols) *(w+tile_offsets[0])+sx
        y = (i//cols)*(h+tile_offsets[1])+sy
        set_figure_geometry(fig, backend, x, y, w, h)

def test(n_figs=10, backend="Qt5Agg", **kwargs):
    mpl.use(backend)
    plt.close("all")
    for i in range(n_figs):
        plt.figure()
    tile_figures(**kwargs)
    plt.show()

El desplazamiento del mosaico en la dirección y se elige mejor como la altura de la barra de título. En mi MacOS es 22. Este valor puede consultarse mediante programación utilizando, por ejemplo, Qt.

from PyQt5 import QtWidgets as qtw
enum = qtw.QStyle.PM_TitleBarHeight
style = qtw.QApplication.style()
tile_offset_y = style.pixelMetric(enum)
0
normanius 30 abr. 2020 a las 13:54