【问题标题】:Why subplots are not taking all space allowed by figure in Matplotlib embedded with Tkinter?为什么子图没有占用嵌入 Tkinter 的 Matplotlib 中图形所允许的所有空间?
【发布时间】:2025-02-25 00:10:01
【问题描述】:

我想在 Tkinter 窗口中显示很多子图,并且能够向下滚动以查看所有大小合适的图。但是,一切都已打包,似乎子图并没有占用我图中允许的所有空间,并且仅限制在窗口的空间内。我怎样才能让它更间隔,让子图更大?

我尝试了使用 tight_layout() 选项的不同填充,在我的小部件中更改图形大小和 Tkinter 的其他参数,例如 fillexpand

import tkinter as tk
from tkinter import Scrollbar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

import matplotlib.pyplot as plt

class ScrollableWindow:

    def __init__(self, master, fig, **options):

        master.geometry("%dx%d+0+0" % (800, 500))
        master.focus_set()

        fig_wrapper = tk.Frame(master, width=800, height=fig.get_figheight())
        fig_wrapper.pack(fill=tk.BOTH, expand=True)

        fig_canvas = FigureCanvasTkAgg(fig, master=fig_wrapper)

        scrollbar = Scrollbar(fig_wrapper, orient=tk.VERTICAL, command=fig_canvas.get_tk_widget().yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        fig_canvas.get_tk_widget().pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
        fig_canvas.get_tk_widget().config(yscrollcommand = scrollbar.set, scrollregion=fig_canvas.get_tk_widget().bbox("all"), width=800, height=1000)



n_col, n_row = 3, 11

fig, axes = plt.subplots(figsize=(n_col,n_row*2), ncols=n_col, nrows=n_row)
for i in range(axes.shape[0]):
    for j in range(axes.shape[1]):
        axes[i,j].set_xlabel("xlabel")
        axes[i,j].set_ylabel("ylabel")
fig.tight_layout()
showStatsWindow = tk.Tk()
showStatsWindow_ = ScrollableWindow(showStatsWindow, fig)
showStatsWindow.mainloop()

这是一个示例,其中包含其外观的空白图。我想每行有 3 或 4 个子图,但它们都被挤在一起了。如您所见,我的窗口下方有更多空间,它随 figsize 参数而变化,但都是空白的。

【问题讨论】:

  • fig.subplots_adjust(bottom=0.10, right=0.96, left= 0.08, top=0.95,wspace=0.10)
  • 它不能解决问题。这些情节仍然没有占据我图中的所有空间。
  • 最重要的问题是图形不遵守其初始尺寸,而是在 tk 小部件内调整大小。一旦你解决了这个问题,你就可以在所需大小的图形内调用tight_layout。
  • 你的问题是滚动条chekout如何设置滚动条here

标签: python user-interface matplotlib tkinter subplot


【解决方案1】:

让我首先从 tkinter-Windowsize 开始,它显示了 matplotlib 图,例如 subplots。实际上,固定的窗口大小master.geometry("%dx%d+0+0" % (800, 500)) 困扰着你。

当您简单地使用 tkinter 窗口时,它会 automatically 将自身调整为图形大小。相反,如果调整大小,图形也会调整大小。对于上述问题,有一个简单的解决方法。

  • 替换以下行:

    master.geometry("%dx%d+0+0" % (800, 500))
    
  • 与:

    master.resizable(width=False, height=False)
    

防止 tkinter 窗口被调整大小。 现在要使子图的大小更大或更小,您可以通过相应地更改figsize=() 参数来更改它。

fig, axes = plt.subplots(ncols=n_col, nrows=n_row, figsize=(7.5, 25))

我必须推荐您使用PyQT5,这在您给定的场景中更可行。下面给出了一个工作示例,而不是调用tkinter 自定义操作,例如pack()config() 等。

Qt5 将图形放入带有滚动条的画布中,以使图形保持其原始大小并且可以在 Qt 窗口中滚动。您不必处理类中的细节,而只需处理脚本末尾的调用。

访问参考:Developing GUIs in Python: Tkinter vs PyQt

import matplotlib
from PyQt5 import QtWidgets
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar

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


class ScrollableWindow(QtWidgets.QMainWindow):
    def __init__(self, fig):

        self.q_app = QtWidgets.QApplication([])

        QtWidgets.QMainWindow.__init__(self)

        #  To set this size of the Display Window
        self.setFixedSize(800, 500)

        self.widget = QtWidgets.QWidget()
        self.setCentralWidget(self.widget)
        self.widget.setLayout(QtWidgets.QVBoxLayout())
        self.widget.layout().setContentsMargins(0, 0, 0, 0)
        self.widget.layout().setSpacing(10)

        self.fig = fig
        self.canvas = FigureCanvas(self.fig)
        self.canvas.draw()
        self.scroll = QtWidgets.QScrollArea(self.widget)
        self.scroll.setWidget(self.canvas)

        self.nav = NavigationToolbar(self.canvas, self.widget)
        self.widget.layout().addWidget(self.nav)
        self.widget.layout().addWidget(self.scroll)

        self.show()
        exit(self.q_app.exec_())


fig, axes = plt.subplots(ncols=3, nrows=11, figsize=(7.5, 25))

for i in range(axes.shape[0]):
    for j in range(axes.shape[1]):
        axes[i, j].set_xlabel("x-label")
        axes[i, j].set_ylabel("y-label")


fig.tight_layout()
ScrollableWindow(fig)

这是使用PyQT5的结果:

【讨论】:

  • 感谢您的详细回复!这似乎很好地修复了数字周围的空白,但不是数字的滚动问题。保持在一个视图中,滚动条向下滚动到空白区域。
【解决方案2】:

问题在于,如果您想查看如何正确设置滚动条,您的滚动条设置不正确,请查看Vertical scrollbar for frame in Tkinter, PythonHow to get frame in canvas window to expand to the size of the canvas?

您绘制图形的框架没有扩大......

这就是我修复它的方法

import tkinter as tk
from tkinter import Scrollbar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt


class ScrollableWindow:

    def __init__(self, master, fig, **options):
        def on_configure(event):
            # update scrollregion after starting 'mainloop'
            # when all widgets are in canvas
            canvas.configure(scrollregion=canvas.bbox('all'))

            # expand canvas_frame when canvas changes its size
            canvas_width = event.width
            canvas.itemconfig(canvas_frame, width=canvas_width)


        # --- create canvas with scrollbar ---
        canvas = tk.Canvas(master, )
        canvas.pack(side=tk.LEFT, fill='both', expand=True)

        scrollbar = tk.Scrollbar(master, command=canvas.yview)
        scrollbar.pack(side=tk.RIGHT, fill='both')

        canvas.configure(yscrollcommand=scrollbar.set)

        # update scrollregion after starting 'mainloop'
        # when all widgets are in canvas
        canvas.bind('<Configure>', on_configure)

        # --- put frame in canvas ---

        fig_wrapper = tk.Frame(canvas)

        canvas_frame= canvas.create_window((0, 0), window=fig_wrapper,)

        master.geometry("%dx%d+0+0" % (800, 500))
        master.focus_set()

        fig_canvas = FigureCanvasTkAgg(fig, master=fig_wrapper)
        fig_canvas.get_tk_widget().pack(fill=tk.BOTH, side=tk.LEFT, expand=True)


n_col, n_row = 3, 11

fig, axes = plt.subplots(figsize=(n_col*2,n_row*2), ncols=n_col, nrows=n_row,)
for i in range(axes.shape[0]):
    for j in range(axes.shape[1]):
        axes[i,j].set_xlabel("xlabel")
        axes[i,j].set_ylabel("ylabel")
fig.tight_layout()
showStatsWindow = tk.Tk()
showStatsWindow_ = ScrollableWindow(showStatsWindow, fig)
showStatsWindow.mainloop()

这是我得到的结果

【讨论】:

    【解决方案3】:

    如果您需要通过 tkinter 网格调用此函数,则可以进行更多可选自定义。

    import tkinter as tk
    from tkinter import *
    from tkinter import Scrollbar
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    
    
    class ScrollableTkAggY(FigureCanvasTkAgg):
        def __init__(self, figure, master, *args, **kwargs):
            # --- create canvas with scrollbar ---
            self.canvas = tk.Canvas(master)
            self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
            self.canvas.rowconfigure(0, weight=1)
            self.canvas.columnconfigure(0, weight=1)
    
            self.fig_wrapper = tk.Frame(self.canvas)
            self.fig_wrapper.grid(row=0, column=0, sticky=tk.NSEW)
            self.fig_wrapper.rowconfigure(0, weight=1)
            self.fig_wrapper.columnconfigure(0, weight=1)
    
            super(ScrollableTkAggY, self).__init__(figure, master=self.fig_wrapper, *args, **kwargs)
            self.tkagg = self.get_tk_widget()
            self.tkagg.grid(row=0, column=0, sticky=tk.NSEW)
    
            self.vbar = Scrollbar(self.canvas, orient=tk.VERTICAL, command=self.canvas.yview)
            self.vbar.grid(row=0, column=1, sticky=tk.NS)
    
            self.canvas.configure(yscrollcommand=self.vbar.set, scrollregion=self.canvas.bbox(tk.ALL))
    
            # when all widgets are in canvas
            self.canvas.bind('<Configure>', self.on_configure)
            # --- put frame in canvas ---
            self.canvas_frame = self.canvas.create_window((0, 0), window=self.fig_wrapper, anchor=tk.NW)
    
            ScrollableTkAggY_meths = vars(ScrollableTkAggY).keys()
            methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
            methods = methods.difference(ScrollableTkAggY_meths)
    
            for m in methods:
                if m[0] != '_' and m != 'config' and m != 'configure':
                    setattr(self, m, getattr(self.canvas, m))
    
        def __str__(self):
            return str(self.canvas)
    
        # expand canvas_frame when canvas changes its size
        def on_configure(self, event):
            # update scrollregion after starting 'mainloop'
    
            self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL))
            # when all widgets are in canvas
            canvas_width = event.width
            self.canvas.itemconfig(self.canvas_frame, width=canvas_width - 20)
    
    
    class ScrollableTkAggX(FigureCanvasTkAgg):
        def __init__(self, figure, master, *args, **kwargs):
            # --- create canvas with scrollbar ---
            self.canvas = tk.Canvas(master)
            self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
            self.canvas.rowconfigure(0, weight=1)
            self.canvas.columnconfigure(0, weight=1)
    
            self.fig_wrapper = tk.Frame(self.canvas)
            self.fig_wrapper.grid(row=0, column=0, sticky=tk.NSEW)
            self.fig_wrapper.rowconfigure(0, weight=1)
            self.fig_wrapper.columnconfigure(0, weight=1)
    
            super(ScrollableTkAggX, self).__init__(figure, master=self.fig_wrapper, *args, **kwargs)
            self.tkagg = self.get_tk_widget()
            self.tkagg.grid(row=0, column=0, sticky=tk.NSEW)
    
            self.hbar = Scrollbar(self.canvas, orient=tk.HORIZONTAL, command=self.canvas.xview)
            self.hbar.grid(row=1, column=0, sticky=tk.EW)
    
            self.canvas.configure(xscrollcommand=self.hbar.set, scrollregion=self.canvas.bbox(tk.ALL))
    
            # when all widgets are in canvas
            self.canvas.bind('<Configure>', self.on_configure)
            # --- put frame in canvas ---
            self.canvas_frame = self.canvas.create_window((0, 0), window=self.fig_wrapper, anchor=tk.NW)
    
            ScrollableTkAggX_meths = vars(ScrollableTkAggX).keys()
            methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
            methods = methods.difference(ScrollableTkAggX_meths)
    
            for m in methods:
                if m[0] != '_' and m != 'config' and m != 'configure':
                    setattr(self, m, getattr(self.canvas, m))
    
        def __str__(self):
            return str(self.canvas)
    
            # expand canvas_frame when canvas changes its size
    
        def on_configure(self, event):
            # update scrollregion after starting 'mainloop'
    
            self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL))
            # when all widgets are in canvas
            canvas_height = event.height
            self.canvas.itemconfig(self.canvas_frame, height=canvas_height - 20)
    
    
    class mclass:
        def __init__(self, window):
            self.figY = Figure(figsize=(10, 30))
            self.axesY = self.figY.subplots(ncols=3, nrows=10)
    
            self.figX = Figure(figsize=(30, 10))
            self.axesX = self.figX.subplots(ncols=10, nrows=3)
    
            self.canvasY = ScrollableTkAggY(figure=self.figY, master=window)
            self.canvasY.grid(row=0, column=0, sticky='nsew')
            self.canvasY.rowconfigure(0, weight=1)
            self.canvasY.columnconfigure(0, weight=1)
    
            self.canvasX = ScrollableTkAggX(figure=self.figX, master=window)
            self.canvasX.grid(row=1, column=0, sticky='nsew')
            self.canvasX.rowconfigure(0, weight=1)
            self.canvasX.columnconfigure(0, weight=1)
    
            window.geometry("%dx%d+0+0" % (800, 600))
            window.focus_set()
    
            self.do_plot()
    
        def do_plot(self):
            # Color used in mpl online documentation.
            mpl_grey_rvb = (51. / 255., 51. / 255., 51. / 255.)
            self.figY.suptitle("Matplotlib's math rendering engine ScrollableTkAggY",
                               color=mpl_grey_rvb, fontsize=14, weight='bold')
    
            self.figX.suptitle("Matplotlib's math rendering engine ScrollableTkAggX",
                               color=mpl_grey_rvb, fontsize=14, weight='bold')
    
            # Plotting features demonstration formulae
    
            for i in range(self.axesY.shape[0]):
                for j in range(self.axesY.shape[1]):
                    self.axesY[i, j].set_xlabel("xlabel")
                    self.axesY[i, j].set_ylabel("ylabel")
            self.figY.tight_layout()
            self.canvasY.draw()
    
            for i in range(self.axesX.shape[0]):
                for j in range(self.axesX.shape[1]):
                    self.axesX[i, j].set_xlabel("xlabel")
                    self.axesX[i, j].set_ylabel("ylabel")
            self.figX.tight_layout()
            self.canvasX.draw()
    
    
    if __name__ == '__main__':
        window = Tk()
        start = mclass(window)
        window.rowconfigure(0, weight=1)
        window.rowconfigure(1, weight=1)
        window.columnconfigure(0, weight=1)
        # window.columnconfigure(1, weight=1)
        window.mainloop()
    

    这就是结果。 enter image description here

    【讨论】:

      最近更新 更多