Matplotlib图表嵌入Tkinter框架缩放时比例失调、内容截断求助
解决CTkFrame中Matplotlib图表缩放时内容被截断的问题
问题描述
当根窗口调整大小时,嵌入到CTkFrame中的Matplotlib图表会非等比例放大或缩小,导致X轴标签等图表内容被窗口边缘截断,需要让图表更好地保持自身比例与边界完整性。
原始代码
主程序代码
import CTkGraph import tkinter as tk if __name__ == '__main__': root = tk.Tk() root.geometry("800x600") root.rowconfigure(0, weight=1, uniform='u') root.columnconfigure(0, weight=1, uniform='u') graph = CTkGraph.CTkGraph(root) graph.grid(row=0, column=0, sticky="nsew") root.mainloop()
Graph类代码
import tkinter as tk import customtkinter as ctk from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk) from matplotlib.ticker import StrMethodFormatter import matplotlib.pyplot as plt import numpy as np import pandas as pd class CTkGraph(ctk.CTkFrame): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) allowed_keys = ['dataframe', 'xlabel', 'colors'] x = np.arange(0, 3, .01) y = 2 * np.sin(2 * np.pi * x) self.dataframe = pd.DataFrame({'x': x, 'y': y}) self.xlabel = 'x' self.ylabel = 'y' self.default_color = 'black' self.colors = {} for key, value in kwargs.items(): if key in allowed_keys: setattr(self, key, value) for column in self.dataframe.columns: if column != self.xlabel: self.colors.setdefault(column, self.default_color) self.fig, self.ax = plt.subplots(layout='tight') self.canvas = FigureCanvasTkAgg(self.fig, master=self) self.plot() def plot(self): self.ax.cla() self.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:,.1f}')) self.ax.set_xlabel(self.xlabel) self.ax.set_ylabel(self.ylabel) for column in self.dataframe.columns: if column != self.xlabel: self.ax.plot(self.dataframe[self.xlabel], self.dataframe[column], self.colors[column], linewidth=1) self.fig.tight_layout() self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.canvas.draw()
视图对比
正常视图:

异常表现(X轴标签被截断):

解决方案
核心思路是让Matplotlib自动管理布局,并在窗口尺寸变化时触发图表重新调整,避免内容被裁剪。具体修改如下:
1. 启用constrained_layout自动布局
创建Figure时替换原有的layout='tight'为constrained_layout=True,它会动态计算图表元素的位置,确保标签、标题等不会被窗口边缘截断:
# 替换原创建fig的代码 self.fig, self.ax = plt.subplots(constrained_layout=True)
2. 绑定窗口Resize事件
在__init__方法中,给Canvas的TK组件绑定<Configure>事件,每次尺寸变化时触发图表重绘:
# 在self.plot()之后添加 self.canvas_widget = self.canvas.get_tk_widget() self.canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.canvas_widget.bind('<Configure>', self.on_resize)
3. 添加Resize处理方法
在CTkGraph类中新增on_resize方法,确保尺寸变化时图表布局自适应更新:
def on_resize(self, event): # 触发重绘,让constrained_layout重新计算布局 self.canvas.draw()
4. 优化plot方法
移除plot方法中重复的pack调用和tight_layout,避免布局冲突:
def plot(self): self.ax.cla() self.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:,.1f}')) self.ax.set_xlabel(self.xlabel) self.ax.set_ylabel(self.ylabel) for column in self.dataframe.columns: if column != self.xlabel: self.ax.plot(self.dataframe[self.xlabel], self.dataframe[column], self.colors[column], linewidth=1) self.canvas.draw()
修改后的完整Graph类代码
import tkinter as tk import customtkinter as ctk from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk) from matplotlib.ticker import StrMethodFormatter import matplotlib.pyplot as plt import numpy as np import pandas as pd class CTkGraph(ctk.CTkFrame): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) allowed_keys = ['dataframe', 'xlabel', 'colors'] x = np.arange(0, 3, .01) y = 2 * np.sin(2 * np.pi * x) self.dataframe = pd.DataFrame({'x': x, 'y': y}) self.xlabel = 'x' self.ylabel = 'y' self.default_color = 'black' self.colors = {} for key, value in kwargs.items(): if key in allowed_keys: setattr(self, key, value) for column in self.dataframe.columns: if column != self.xlabel: self.colors.setdefault(column, self.default_color) # 使用constrained_layout自动管理布局 self.fig, self.ax = plt.subplots(constrained_layout=True) self.canvas = FigureCanvasTkAgg(self.fig, master=self) self.plot() # 获取canvas的tk组件并绑定resize事件 self.canvas_widget = self.canvas.get_tk_widget() self.canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.canvas_widget.bind('<Configure>', self.on_resize) def plot(self): self.ax.cla() self.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:,.1f}')) self.ax.set_xlabel(self.xlabel) self.ax.set_ylabel(self.ylabel) for column in self.dataframe.columns: if column != self.xlabel: self.ax.plot(self.dataframe[self.xlabel], self.dataframe[column], self.colors[column], linewidth=1) self.canvas.draw() def on_resize(self, event): # 尺寸变化时重绘图表,触发constrained_layout调整 self.canvas.draw()
额外说明
如果需要固定图表的宽高比例,可以在on_resize方法中添加aspect设置:
def on_resize(self, event): # 设置宽高比为1.5,可根据需求调整数值 self.ax.set_aspect(1.5, adjustable='box') self.canvas.draw()
内容的提问来源于stack exchange,提问作者Dak
相关产品推荐
相关产品推荐

