这里的主要问题是使用
FigureCanvasTkAgg
绘制太多数据(~36 个子图)时,当用户滚动时,Tkinter 的 Scrollbar
过于迟缓和缓慢。这个问题有什么解决方案吗,我不希望我的用户遇到这个问题。谢谢。
您可以在这里尝试此代码:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
class App:
def __init__(self, root):
self.root = root
self.root.title("Scrollable Matplotlib Plot with 9 Subplots")
# Create a frame for the canvas and scrollbars
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True)
# Create a canvas for the Matplotlib figure
self.canvas = tk.Canvas(frame)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Create vertical scrollbar
self.v_scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL, command=self.canvas.yview)
self.v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Create horizontal scrollbar
self.h_scrollbar = tk.Scrollbar(root, orient=tk.HORIZONTAL, command=self.canvas.xview)
self.h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
# Configure canvas with scrollbars
self.canvas.configure(yscrollcommand=self.v_scrollbar.set)
self.canvas.configure(xscrollcommand=self.h_scrollbar.set)
# Create a figure and multiple subplots
self.fig, self.axs = plt.subplots(6, 6, figsize=(10, 10)) # 3x3 grid of subplots
# Generate some data for the plots
x = np.linspace(0, 10, 100)
for i, ax in enumerate(self.axs.flat):
ax.plot(x, np.sin(x + i), label=f'Sine Wave {i+1}')
ax.set_title(f'Plot {i+1}')
ax.legend()
# Add the Matplotlib figure to the canvas
self.figure_canvas = FigureCanvasTkAgg(self.fig, master=self.canvas)
self.figure_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Bind the canvas to scrolling
self.canvas.create_window((0, 0), window=self.figure_canvas.get_tk_widget(), anchor='nw')
# Update the scroll region
self.update_scroll_region()
# Bind the resize event to update scroll region
self.root.bind("<Configure>", self.on_resize)
def update_scroll_region(self):
"""Update the scroll region based on the canvas content."""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def on_resize(self, event):
"""Update the scroll region when the window is resized."""
self.update_scroll_region()
# Create the main window
root = tk.Tk()
app = App(root)
root.mainloop()
速度变慢的原因是滚动条触发了画布上的重绘,这对底层 matplotlib 绘图进行了不必要的重绘,而 matploltib 速度很慢。
您可以通过子类化
FigureCanvasTkAgg
来跳过 matplotlib 重绘,并在画布滚动时跳过下一次重绘,因为重绘发生在将来,而不是在滚动回调内。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import time
class MyCanvas(FigureCanvasTkAgg):
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
self._skip_redraw = False
def skip_redraw(self):
self._skip_redraw = True
def draw(self):
if self._skip_redraw:
self._skip_redraw = False
return
super().draw()
class App:
def __init__(self, root):
self.root = root
self.root.title("Scrollable Matplotlib Plot with 9 Subplots")
self.last_update_time = time.time()
# Create a frame for the canvas and scrollbars
frame = tk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True)
# Create a canvas for the Matplotlib figure
self.canvas = tk.Canvas(frame)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Create vertical scrollbar
self.v_scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL, command=self.canvas.yview)
self.v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Create horizontal scrollbar
self.h_scrollbar = tk.Scrollbar(root, orient=tk.HORIZONTAL, command=self.canvas.xview)
self.h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
# Configure canvas with scrollbars
def my_h_scroll_command(*args):
self.figure_canvas.skip_redraw()
self.canvas.xview(*args)
def my_v_scroll_command(*args):
self.figure_canvas.skip_redraw()
self.canvas.yview(*args)
self.v_scrollbar.configure(command=my_v_scroll_command)
self.h_scrollbar.configure(command=my_h_scroll_command)
self.canvas.configure(yscrollcommand=self.v_scrollbar.set)
self.canvas.configure(xscrollcommand=self.h_scrollbar.set)
# Create a figure and multiple subplots
self.fig, self.axs = plt.subplots(6, 6, figsize=(10, 10)) # 3x3 grid of subplots
# Generate some data for the plots
x = np.linspace(0, 10, 100)
for i, ax in enumerate(self.axs.flat):
ax.plot(x, np.sin(x + i), label=f'Sine Wave {i+1}')
ax.set_title(f'Plot {i+1}')
ax.legend()
# Add the Matplotlib figure to the canvas
self.figure_canvas = MyCanvas(self.fig, master=self.canvas)
self.figure_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Bind the canvas to scrolling
self.canvas.create_window((0, 0), window=self.figure_canvas.get_tk_widget(), anchor='nw')
# Update the scroll region
self.update_scroll_region()
# Bind the resize event to update scroll region
self.root.bind("<Configure>", self.on_resize)
def update_scroll_region(self):
"""Update the scroll region based on the canvas content."""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def on_resize(self, event):
"""Update the scroll region when the window is resized."""
self.update_scroll_region()
# Create the main window
root = tk.Tk()
app = App(root)
root.mainloop()