【问题标题】:How to tile matplotlib figures evenly on screen?如何在屏幕上均匀地平铺 matplotlib 图形?
【发布时间】:2020-04-29 13:40:12
【问题描述】:

matplotlib 是否提供在屏幕上均匀分布多个图形的功能?或者有人知道能够实现这一目标的工具箱吗?我已经厌倦了手动操作。

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

这会创建五个相互重叠的人物。要检查图 1 上的内容,我必须将其他 4 个数字移到一边。

在 MacOS 上,我可以使用 Ctrl+ 快捷方式来查看所有数字。或者,我可以将绘图写入文件并检查图库中的图像。但我想知道是否有一个用于 matplotlib 的自定义窗口管理器,它可能会提供更多的灵活性。

在Matlab中,我习惯了spreadfigureautoArrangeFigures等工具。

【问题讨论】:

  • subplots 适合您吗?类似于this
  • @Anwarvic 谢谢,但这不是我想要的。如果已经在使用子图,那么在子图中组合不同图形的内容会很复杂。如果图形内容被压缩到网格中,绘图的外观也可能会发生变化。
  • 我已经添加了我的答案,希望它能解决您的问题

标签: python matplotlib figure window-managers


【解决方案1】:

您可以使用图形管理器控制绘图窗口的位置,如下所示:

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

这将产生五个并排显示的图表,如下所示:

希望这就是您要找的东西!

【讨论】:

  • 非常感兴趣这是否适用于 Windows。您的答案适用于 MacOS,对吗?我收到以下错误:AttributeError: '_tkinter.tkapp' object has no attribute 'setGeometry'。或者,这个错误是否与“后端”tkinter 等有关?
  • 您能否先在独立脚本中使用此代码,然后再将其放入您的应用程序中?
  • @Anwarvic 谢谢!但这也不适用于 OSX 后端。我得到FigureManagerMac 对象没有属性window
  • @jpf 根据this SO 线程,无法以与后端无关的方式设置窗口位置和大小。但也许可以强制使用某个后端,并在所有平台上使用相同的方式。
  • 在我的脚本开头使用 import matplotlib; matplotlib.use("Qt5Agg") 对我来说适用于 MacOS 和 Windows。但它需要 Qt5/PyQt5。
【解决方案2】:

matplotlib 似乎没有提供这种开箱即用的功能。此外,正如here 所讨论的那样,没有“与后端无关”的方式来控制图形几何形状。

因此,我写了tile_figures() 来实现这个迷你功能,通过一些平铺逻辑和简单的后端抽象来扩展 Anwarvic 的建议。它目前仅支持 Qt 或 Tk 后端,但它当然也可以扩展到其他后端。

平铺快乐!


用法

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

结果


实施

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

最好选择 y 方向的 tile-offset 作为标题栏的高度。在我的 MacOS 上它是 22。可以使用例如 Qt 以编程方式查询该值。

from PyQt5 import QtWidgets as qtw
enum = qtw.QStyle.PM_TitleBarHeight
style = qtw.QApplication.style()
tile_offset_y = style.pixelMetric(enum)

【讨论】:

  • screen_size 未定义
  • @AimForClarity 已修复! screen_size() --> screen_geometry()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-24
  • 1970-01-01
相关资源
最近更新 更多