Matplotlib Figure嵌入Tkinter Frame尺寸自适应适配问题
Tkinter Frame嵌入Matplotlib图表尺寸适配问题修复
问题现象
在主Frame内创建6个等分布局的子Frame后,将Matplotlib绘制的图表依次嵌入子Frame时,出现图表尺寸超出Frame边界的显示异常:
- 尝试调用
frame.winfo_width()、frame.winfo_height()获取Frame实际尺寸调整图表大小未生效 - 不接受新增Y轴滚动条的规避方案,需要实现图表与Frame尺寸的自动适配
复现代码
from tkinter import * import tkinter as tk from tkinter import ttk import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk from matplotlib.figure import Figure import numpy as np import pandas as pd import matplotlib.dates as mdates from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator) import matplotlib import mplcursors import string import os string.ascii_lowercase n = 5 m = 25 cols11 = string.ascii_lowercase[:m] df = pd.DataFrame(np.random.randint(0, n,size=(n , m)), columns=list(cols11)) newWindow = tk.Tk() newWindow.state('zoomed') #newWindow.geometry('{}x{}'.format(460, 350)) newWindow.title('Workflow Chart') main_frame2_1=tk.Frame(newWindow,bg="pink") main_frame2_1.pack(fill=tk.BOTH,expand=True) main_frame2_1.grid_columnconfigure(0, weight=1,uniform=1) main_frame2_1.grid_rowconfigure(0, weight=1,uniform=1) main_frame2_1.grid_columnconfigure(1, weight=1,uniform=1) main_frame2_1.grid_rowconfigure(1, weight=1,uniform=1) main_frame2_1.grid_rowconfigure(2, weight=1,uniform=1) frame1_1=tk.Frame(main_frame2_1,bg="red") frame1_1.grid(row=0, column=0, sticky='nsew') frame1_2=tk.Frame(main_frame2_1,bg="green") frame1_2.grid(row=0, column=1, sticky='nsew') frame1_3=tk.Frame(main_frame2_1,bg="black") frame1_3.grid(column=0,row=1,sticky='nsew') frame1_4=tk.Frame(main_frame2_1,bg="pink") frame1_4.grid(column=1,row=1,sticky='nsew') frame1_5=tk.Frame(main_frame2_1,bg="red") frame1_5.grid(column=0,row=2,sticky='nsew') frame1_6=tk.Frame(main_frame2_1,bg="green") frame1_6.grid(column=1,row=2,sticky='nsew') frame1_2.update() print(frame1_2.winfo_width(),frame1_2.winfo_height()) r=frame1_2.winfo_width() s=frame1_2.winfo_height() def create_plot(dataset1,dataset2,dataset3): f, ax = plt.subplots() p1,=ax.plot(dataset1,dataset2,linestyle='-', marker='o', color='#0343df',markersize=3,markerfacecolor='red',markeredgecolor='red',label='Actuals') p2,=ax.plot(dataset1,dataset3,linestyle='-', marker='o', color='#15b01a',markersize=3,markerfacecolor='#f97306',markeredgecolor='#f97306',label='Target') ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator())) #plt.xlabel("Date") plt.ylabel("Example") plt.title("Example") plt.legend(loc='best', prop={'size': 8}) ax.xaxis.set_minor_locator(MultipleLocator(1)) #ax.xaxis.grid(True, which='minor', linestyle='--', color='#d8dcd6') ax.yaxis.set_minor_locator(MultipleLocator(200)) #ax.yaxis.grid(True, which='minor', linestyle='--', color='#d8dcd6') #plt.grid() mplcursors.cursor() #plt.axhline(0, color='black') for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(8) return f class VerticalNavigationToolbar2Tk(NavigationToolbar2Tk): def __init__(self, canvas, window): super().__init__(canvas, window, pack_toolbar=False) def _Button(self, text, file,toggle, command): img_file = os.path.join(matplotlib.get_data_path(), 'images', file ) im = tk.PhotoImage(master=self, file=img_file) im = im.subsample(2, 2) b = tk.Button(master=self, text=text, padx=2, pady=2, image=im, command=command) b._ntimage = im b.pack(side=tk.TOP) return b def _Spacer(self): s = tk.Frame(self, width=10, relief=tk.RIDGE, bg="DarkGray", padx=2) s.pack(side=tk.TOP, pady=1) return s def set_message(self, s): pass fig1 = create_plot(df.index,df.iloc[:, 4],df.iloc[:, 6]) canvas = FigureCanvasTkAgg(fig1, master=frame1_1) canvas.draw() canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) toolbar = VerticalNavigationToolbar2Tk(canvas, frame1_1) toolbar.update() toolbar.pack(side=tk.LEFT, fill=tk.Y) canvas.draw() canvas.get_tk_widget().pack() fig2 = create_plot(df.index,df.iloc[:, 4],df.iloc[:, 6]) canvas = FigureCanvasTkAgg(fig2, master=frame1_2) canvas.draw() canvas.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH, expand=1) #toolbar = NavigationToolbar2Tk(canvas, frame1,pack_toolbar=True) toolbar = VerticalNavigationToolbar2Tk(canvas, frame1_2) toolbar.update() toolbar.pack(side=tk.RIGHT, fill=tk.Y) canvas.draw() canvas.get_tk_widget().pack() fig3 = create_plot(df.index,df.iloc[:, 0],df.iloc[:, 2]) canvas = FigureCanvasTkAgg(fig3, master=frame1_3) canvas.draw() canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) #toolbar = NavigationToolbar2Tk(canvas, frame1,pack_toolbar=True) toolbar = VerticalNavigationToolbar2Tk(canvas, frame1_3) toolbar.update() toolbar.pack(side=tk.LEFT, fill=tk.Y) canvas.draw() canvas.get_tk_widget().pack() fig4 = create_plot(df.index,df.iloc[:, 4],df.iloc[:, 6]) canvas = FigureCanvasTkAgg(fig4, master=frame1_4) canvas.draw() canvas.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH, expand=1) #toolbar = NavigationToolbar2Tk(canvas, frame1,pack_toolbar=True) toolbar = VerticalNavigationToolbar2Tk(canvas, frame1_4) toolbar.update() toolbar.pack(side=tk.RIGHT, fill=tk.Y) canvas.draw() canvas.get_tk_widget().pack() newWindow.mainloop()
问题根因
- 重复调用
canvas.get_tk_widget().pack():第一次pack设置了fill=BOTH, expand=1的缩放属性,第二次无参数调用会覆盖之前的布局配置,导致画布无法跟随父容器伸缩 - Figure初始化逻辑问题:使用
plt.subplots()创建的Figure默认带固定英寸尺寸,没有开启容器自适应,不会主动匹配父Frame大小 - 尺寸获取时机错误:在窗口未完成完整布局渲染时就调用
winfo_width()/winfo_height(),拿到的是控件初始化的默认值,不是布局完成后的实际尺寸,手动传值设置大小自然不生效
修复方案
- 删除所有重复的
canvas.get_tk_widget().pack()调用,每个画布只保留一次pack配置,确保fill=tk.BOTH, expand=True属性生效 - 修改
create_plot函数的Figure创建逻辑,不用plt.subplots()默认配置,改用Figure(figsize=(1,1), tight_layout=True)初始化极小尺寸画布,配合tight_layout自动裁剪边距,让画布主动适配容器 - 补全剩余2个Frame(frame1_5、frame1_6)的图表嵌入逻辑,所有子Frame统一布局规则
- 可选:给画布绑定父容器的
<Configure>事件,窗口缩放时自动触发重绘,保证尺寸变化时适配始终生效
核心修改代码片段
# 修改create_plot函数 def create_plot(dataset1,dataset2,dataset3): # 替换plt.subplots()为手动创建Figure,设置初始极小尺寸+自动紧凑布局 f = Figure(figsize=(1,1), tight_layout=True) ax = f.add_subplot(111) p1,=ax.plot(dataset1,dataset2,linestyle='-', marker='o', color='#0343df',markersize=3,markerfacecolor='red',markeredgecolor='red',label='Actuals') p2,=ax.plot(dataset1,dataset3,linestyle='-', marker='o', color='#15b01a',markersize=3,markerfacecolor='#f97306',markeredgecolor='#f97306',label='Target') ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator())) ax.set_ylabel("Example") ax.set_title("Example") ax.legend(loc='best', prop={'size': 8}) ax.xaxis.set_minor_locator(MultipleLocator(1)) ax.yaxis.set_minor_locator(MultipleLocator(200)) mplcursors.cursor(ax) for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(8) return f # 嵌入画布时删除重复pack,示例如下 fig1 = create_plot(df.index,df.iloc[:, 4],df.iloc[:, 6]) canvas = FigureCanvasTkAgg(fig1, master=frame1_1) toolbar = VerticalNavigationToolbar2Tk(canvas, frame1_1) toolbar.update() toolbar.pack(side=tk.LEFT, fill=tk.Y) canvas.get_tk_widget().pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) canvas.draw()
修复效果
6个等分Frame内的Matplotlib图表会完全贴合Frame边界,不会出现溢出问题,窗口缩放/尺寸调整时图表会同步自适应大小,无需新增滚动条。
内容的提问来源于stack exchange,提问作者Orlando
相关产品推荐
相关产品推荐

