matplotlib - Tkinter 中 Matplotlib 图的滚动条
问题描述
我正在尝试让滚动条Tkinter
用于matplotlib
数字。滚动条不适应加载图像的宽度和高度,我不知道如何解决这个问题。任何帮助,将不胜感激。
图片:
最终结果:
代码:
import os
from PIL import Image
import tkinter as tk
from tkinter import ttk, filedialog
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
from functools import partial
class AutoScrollbar(ttk.Scrollbar):
def __init__(self,
parent,
*args,
**kwargs):
'''
'''
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
def set(self,
low,
high):
'''
'''
if float(low) <= 0.0 and float(high) >= 1.0:
self.tk.call('grid',
'remove',
self)
else:
self.grid()
ttk.Scrollbar.set(self,
low,
high)
class DoubleScrollbarFrame(ttk.Frame):
def __init__(self,
parent,
*args,
**kwargs):
'''
'''
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.canvas = tk.Canvas(self)
self.frame = ttk.Frame(self.canvas)
self.scroll_x = AutoScrollbar(self,
orient = tk.HORIZONTAL)
self.scroll_y = AutoScrollbar(self,
orient = tk.VERTICAL)
self.sizegrip = ttk.Sizegrip(self)
self.canvas.config(xscrollcommand = self.scroll_x.set,
yscrollcommand = self.scroll_y.set)
self.scroll_x.config(command = self.canvas.xview)
self.scroll_y.config(command = self.canvas.yview)
self.canvas.create_window((0,0),
window = self.frame,
anchor = 'nw')
self.frame.bind('<Configure>',
self.set_scrollregion)
def position_widgets(self,
**kwargs):
'''
'''
self.scroll_x.grid(row = 1,
column = 0,
sticky = 'ew')
self.scroll_y.grid(row = 0,
column = 1,
sticky = 'ns')
self.canvas.grid(row = 0,
column = 0,
sticky = 'nsew')
self.sizegrip.grid(row = 1,
column = 1,
sticky = 'se')
#NOTE: Do not use geometry manager with `self.frame`. This will
# pass control from the canvas to grid and the canvas will then
# no longer know how much to grow.
def set_scrollregion(self,
event):
'''
'''
self.canvas.configure(scrollregion = self.canvas.bbox('all'))
class Graph(ttk.Frame):
def __init__(self,
parent,
axis_off = True,
*args,
**kwargs):
'''
'''
self.parent = parent
self.axis_off = axis_off
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.graph_container = DoubleScrollbarFrame(self)
self.figure = plt.figure()
self.figure.subplots_adjust(top = 1,
right = 1,
left= 0,
bottom = 0,
wspace = 0,
hspace = 0)
self.axis = self.figure.add_subplot(1,1,1)
if self.axis_off:
self.axis.set_axis_off()
self.mpl_canvas = FigureCanvasTkAgg(self.figure,
self.graph_container.frame)
self.toolbar_frame = ttk.Frame(self)
self.toolbar = NavigationToolbar2Tk(self.mpl_canvas,
self.toolbar_frame)
def position_widgets(self):
'''
'''
self.toolbar.update()
self.toolbar_frame.grid(row = 1,
column = 0,
sticky = 'sew')
self.mpl_canvas.draw()
self.mpl_canvas.get_tk_widget().grid(row = 0,
column = 0,
sticky = 'nsew')
self.graph_container.grid(row = 0,
column = 0,
sticky = 'nsew')
def load_image(self,
path):
'''
'''
self.mpl_canvas.flush_events()
self.axis.clear()
self.image = Image.open(path)
#NOTE: Dots per inch (dpi) in matplotlib should be used for
# printing to paper media only. dpi is set at the time of
# printing/scanning. Computer screen resolution is fixed by
# the screen hardware and given in pixels per inch (ppi).
# An image scanned at higher dpi will appear crisper on a
# computer screen because of the difference in halftone/
# dithering at each pixel, but the total size and number of
# pixels in the image will be the same.
# Matplotlib use 72 pixels per inch (ppi) for its figures by
# default, and this cannot be changed. Thus, if you increase
# dots per inch (dpi), the figure will appear bigger when
# printed to screen (i.e. on the computer monitor) because
# it will use more pixels to represent the same features.
# When printed to paper media, however, the image will be
# the same size regardless of dpi, but have finer halftoning/
# dithering for an improved appearance.
# Matplotlib sets sizes in terms of inches, so the scan dpi
# must be known in order to get the physical size of the
# image.
if self.image.info.get('dpi'):
self.scan_dpi, _ = self.image.info['dpi']
else:
self.scan_dpi = 100
self.w_pel, self.h_pel = self.image.size
self.graph_w_in = self.w_pel / self.scan_dpi
self.graph_h_in = self.h_pel / self.scan_dpi
self.graph_aspect_ratio = self.graph_h_in / self.graph_w_in
self.zoom = 1
self.figure.set_size_inches(self.graph_w_in * self.zoom,
self.graph_h_in * self.zoom)
self.image = np.array(self.image)
self.axis.imshow(self.image)
self.mpl_canvas.draw()
class FileBrowser(tk.Frame):
def __init__(self,
parent,
path_type = 'file',
label_text = '',
file_types = (('*','All File Types...'),),
*args,
**kwargs):
'''
'''
self.parent = parent
self.path_type = path_type
self.label_text = label_text
self.file_types = file_types
super().__init__(parent,
*args,
**kwargs)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.label = tk.Label(self.parent,
text = self.label_text)
self.path_entry = tk.Entry(self.parent,
width = 50)
self.button = ttk.Button(self.parent,
text = 'Browse...',
command = partial(self.open_file_dialog,
self.path_type,
self.file_types))
def position_widgets(self):
'''
'''
opts = {'padx': (5,5),
'pady': (5,5)}
self.label.grid(row = 0,
column = 0,
sticky = 'e',
**opts)
self.path_entry.grid(row = 0,
column = 1,
sticky = 'w',
**opts)
self.button.grid(row = 0,
column = 2,
sticky = 'w',
**opts)
def open_file_dialog(self,
path_type,
file_types):
'''
'''
init_dir = os.getcwd()
if path_type == 'file':
self.path = filedialog.askopenfilename(initialdir = init_dir,
title = 'Select file...',
filetypes = file_types)
elif path_type == 'directory':
self.path = filedialog.askdirectory(initialdir = init_dir,
title = 'Select directory...')
self.path_entry.delete(0,tk.END)
self.path_entry.insert(0,self.path)
def get_path(self):
'''
'''
return self.path_entry.get()
class Loader(ttk.Frame):
'''
'''
def __init__(self,
parent,
*args,
**kwargs):
'''
'''
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.input_label_frame = ttk.LabelFrame(self,
text = 'Input')
self.output_label_frame = ttk.LabelFrame(self,
text = 'Output')
self.create_file_browser(self.input_label_frame)
self.create_ok_button(self.input_label_frame)
self.create_output_window(self.output_label_frame)
def create_file_browser(self,
label_frame):
'''
'''
self.browser_frame = ttk.LabelFrame(label_frame,
text = 'Select File')
self.file_browser = FileBrowser(self.browser_frame,
path_type = 'file',
label_text = 'File:',
file_types = (('*.tif', 'TIF'),
('*.png', 'PNG'),))
def create_ok_button(self,
label_frame):
'''
'''
self.input_ok_button = ttk.Button(label_frame,
text = 'OK',
command = self.calibrate)
def calibrate(self):
'''
'''
path = self.file_browser.get_path()
if path == '':
messagebox.showerror(title = 'No File Selected',
message = 'No file chosen. Please select file.')
return
_, ext = os.path.splitext(path)
if ext.lower() not in ('.tif', '.png'):
messagebox.showerror(title = 'File Is Not \"*.tif\" or \"*.png\"',
message = 'File must be a \"*.tif\" or \"*.png\" image file. Please reselect file and try again.')
img = plt.imread(path)
self.graph.load_image(path)
def create_output_window(self,
label_frame):
'''
'''
self.output_frame = ttk.Frame(label_frame)
self.graph = Graph(self.output_frame)
def position_widgets(self,
**kwargs):
'''
'''
#OK Button
self.input_ok_button.grid(row = 4,
column = 0,
sticky = 'e')
#Frames
self.input_label_frame.grid(row = 0,
column = 0,
sticky = 'nsew')
self.browser_frame.grid(row = 1,
column = 0,
sticky = 'nw')
self.file_browser.grid(row = 0,
column = 0,
sticky = 'nsew')
self.output_label_frame.grid(row = 0,
column = 1,
sticky = 'nw')
self.graph.grid(row = 0,
column = 0,
sticky = 'nsew')
self.output_frame.grid(row = 0,
column = 0,
sticky = 'nsew')
class MainApp(tk.Tk):
def __init__(self,
title,
*args,
**kwargs):
'''
'''
self._title = title
super().__init__(*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
#Set window title
self.title(self._title)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.loader = Loader(self)
def position_widgets(self):
'''
'''
self.loader.grid(row = 0,
column = 0,
sticky = 'nsew')
if __name__ == '__main__':
#Create GUI
root = MainApp('MainApp')
#Run program
root.mainloop()
解决方案
matplotlib
人物是画布。因此,它的容器框架不会调整到它的大小,bbox("all")
也不会工作。相反,我们必须更新大小以matplotlib
canvas
匹配figure
大小和顶级大小。canvas
scrollregion
figure
这是工作输出。
工作输出
这是工作代码。
代码
import os
from PIL import Image
import tkinter as tk
from tkinter import ttk, filedialog
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np
from functools import partial
#Matplotlib Default DPI
DEFAULT_DPI = 100
#Matplotlib Default PPI
DEFAULT_PPI = 72
class AutoScrollbar(ttk.Scrollbar):
def __init__(self,
parent,
*args,
**kwargs):
'''
'''
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
def set(self,
low,
high):
'''
'''
if float(low) <= 0.0 and float(high) >= 1.0:
self.tk.call('grid',
'remove',
self)
else:
self.grid()
ttk.Scrollbar.set(self,
low,
high)
class DoubleScrollbarFrame(ttk.Frame):
def __init__(self,
parent,
*args,
**kwargs):
'''
'''
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.canvas = tk.Canvas(self)
self.frame = ttk.Frame(self.canvas)
self.scroll_x = AutoScrollbar(self,
orient = tk.HORIZONTAL)
self.scroll_y = AutoScrollbar(self,
orient = tk.VERTICAL)
self.sizegrip = ttk.Sizegrip(self)
self.canvas.config(xscrollcommand = self.scroll_x.set,
yscrollcommand = self.scroll_y.set)
self.scroll_x.config(command = self.canvas.xview)
self.scroll_y.config(command = self.canvas.yview)
self.canvas.create_window((0,0),
window = self.frame,
anchor = 'nw')
self.frame.bind('<Configure>',
self.set_scrollregion)
def position_widgets(self,
**kwargs):
'''
'''
self.scroll_x.grid(row = 1,
column = 0,
sticky = 'ew')
self.scroll_y.grid(row = 0,
column = 1,
sticky = 'ns')
self.canvas.grid(row = 0,
column = 0,
sticky = 'nsew')
self.sizegrip.grid(row = 1,
column = 1,
sticky = 'se')
#NOTE: Do not use geometry manager with `self.frame`. This will
# pass control from the canvas to grid and the canvas will then
# no longer know how much to grow.
def set_scrollregion(self,
event):
'''
'''
self.canvas.configure(scrollregion = self.canvas.bbox('all'))
class Graph(ttk.Frame):
def __init__(self,
parent,
axis_off = True,
*args,
**kwargs):
'''
'''
self.parent = parent
self.axis_off = axis_off
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.figure = plt.figure()
self.figure.subplots_adjust(top = 1,
right = 1,
left= 0,
bottom = 0,
wspace = 0,
hspace = 0)
self.axis = self.figure.add_subplot(1,1,1)
if self.axis_off:
self.axis.set_axis_off()
self.canvas = tk.Canvas(self)
self.frame = ttk.Frame(self.canvas)
self.mpl_canvas = FigureCanvasTkAgg(self.figure,
self.frame)
self.mpl_canvas.draw()
self.scroll_x = AutoScrollbar(self,
orient = tk.HORIZONTAL)
self.scroll_y = AutoScrollbar(self,
orient = tk.VERTICAL)
self.sizegrip = ttk.Sizegrip(self)
self.canvas.config(xscrollcommand = self.scroll_x.set,
yscrollcommand = self.scroll_y.set)
self.scroll_x.config(command = self.canvas.xview)
self.scroll_y.config(command = self.canvas.yview)
self.cwid = self.canvas.create_window((0,0),
window = self.frame,
anchor = 'nw')
self.frame.bind('<Configure>',
self.set_scrollregion)
self.toolbar_frame = ttk.Frame(self)
self.toolbar = NavigationToolbar2Tk(self.mpl_canvas,
self.toolbar_frame)
def set_scrollregion(self,
event):
'''
'''
w, h = self.figure.get_size_inches()
w = int(w * DEFAULT_DPI)
h = int(h * DEFAULT_DPI)
scrollregion = (0,0,w,h)
self.canvas.configure(scrollregion = scrollregion)
def position_widgets(self):
'''
'''
self.scroll_x.grid(row = 1,
column = 0,
sticky = 'ew')
self.scroll_y.grid(row = 0,
column = 1,
sticky = 'ns')
self.canvas.grid(row = 0,
column = 0,
sticky = 'nsew')
self.mpl_canvas.get_tk_widget().grid(row = 0,
column = 0,
sticky = 'nsew')
self.sizegrip.grid(row = 1,
column = 1,
sticky = 'se')
#NOTE: Do not use geometry manager with `self.frame`. This will
# pass control from the canvas to grid and the canvas will then
# no longer know how much to grow.
self.toolbar.update()
self.toolbar_frame.grid(row = 2,
column = 0,
sticky = 'sew')
def load_image(self,
path):
'''
'''
self.mpl_canvas.flush_events()
self.axis.clear()
self.image = Image.open(path)
#NOTE: Dots per inch (dpi) in matplotlib should be used for
# printing to paper media only. dpi is set at the time of
# printing/scanning. Computer screen resolution is fixed by
# the screen hardware and given in pixels per inch (ppi).
# An image scanned at higher dpi will appear crisper on a
# computer screen because of the difference in halftone/
# dithering at each pixel, but the total size and number of
# pixels in the image will be the same.
# Matplotlib use 72 pixels per inch (ppi) for its figures by
# default, and this cannot be changed. Thus, if you increase
# dots per inch (dpi), the figure will appear bigger when
# printed to screen (i.e. on the computer monitor) because
# it will use more pixels to represent the same features.
# When printed to paper media, however, the image will be
# the same size regardless of dpi, but have finer halftoning/
# dithering for an improved appearance.
# Matplotlib sets sizes in terms of inches, so the scan dpi
# must be known in order to get the physical size of the
# image.
if self.image.info.get('dpi'):
self.scan_dpi, _ = self.image.info['dpi']
else:
self.scan_dpi = DEFAULT_DPI # matplotlib default
self.w_pel, self.h_pel = self.image.size
self.graph_w_in = self.w_pel / self.scan_dpi
self.graph_h_in = self.h_pel / self.scan_dpi
self.figure.set_size_inches(self.graph_w_in,
self.graph_h_in)
self.image = np.array(self.image)
self.axis.imshow(self.image)
self.mpl_canvas.get_tk_widget().configure(
width = self.graph_w_in * DEFAULT_DPI,
height = self.graph_h_in * DEFAULT_DPI)
self.mpl_canvas.draw()
self.canvas.update()
class FileBrowser(tk.Frame):
def __init__(self,
parent,
path_type = 'file',
label_text = '',
file_types = (('*','All File Types...'),),
*args,
**kwargs):
'''
'''
self.parent = parent
self.path_type = path_type
self.label_text = label_text
self.file_types = file_types
super().__init__(parent,
*args,
**kwargs)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.label = tk.Label(self.parent,
text = self.label_text)
self.path_entry = tk.Entry(self.parent,
width = 50)
self.button = ttk.Button(self.parent,
text = 'Browse...',
command = partial(self.open_file_dialog,
self.path_type,
self.file_types))
def position_widgets(self):
'''
'''
opts = {'padx': (5,5),
'pady': (5,5)}
self.label.grid(row = 0,
column = 0,
sticky = 'e',
**opts)
self.path_entry.grid(row = 0,
column = 1,
sticky = 'w',
**opts)
self.button.grid(row = 0,
column = 2,
sticky = 'w',
**opts)
def open_file_dialog(self,
path_type,
file_types):
'''
'''
init_dir = os.getcwd()
if path_type == 'file':
self.path = filedialog.askopenfilename(initialdir = init_dir,
title = 'Select file...',
filetypes = file_types)
elif path_type == 'directory':
self.path = filedialog.askdirectory(initialdir = init_dir,
title = 'Select directory...')
self.path_entry.delete(0,tk.END)
self.path_entry.insert(0,self.path)
def get_path(self):
'''
'''
return self.path_entry.get()
class Loader(ttk.Frame):
'''
'''
def __init__(self,
parent,
*args,
**kwargs):
'''
'''
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.input_label_frame = ttk.LabelFrame(self,
text = 'Input')
self.output_label_frame = ttk.LabelFrame(self,
text = 'Output')
self.create_file_browser(self.input_label_frame)
self.create_ok_button(self.input_label_frame)
self.create_output_window(self.output_label_frame)
def create_file_browser(self,
label_frame):
'''
'''
self.browser_frame = ttk.LabelFrame(label_frame,
text = 'Select File')
self.file_browser = FileBrowser(self.browser_frame,
path_type = 'file',
label_text = 'File:',
file_types = (('*.tif', 'TIF'),
('*.png', 'PNG'),))
def create_ok_button(self,
label_frame):
'''
'''
self.input_ok_button = ttk.Button(label_frame,
text = 'OK',
command = self.calibrate)
def calibrate(self):
'''
'''
path = self.file_browser.get_path()
if path == '':
messagebox.showerror(title = 'No File Selected',
message = 'No file chosen. Please select file.')
return
_, ext = os.path.splitext(path)
if ext.lower() not in ('.tif', '.png'):
messagebox.showerror(title = 'File Is Not \"*.tif\" or \"*.png\"',
message = 'File must be a \"*.tif\" or \"*.png\" image file. Please reselect file and try again.')
img = plt.imread(path)
self.graph.load_image(path)
def create_output_window(self,
label_frame):
'''
'''
self.output_frame = ttk.Frame(label_frame)
self.graph = Graph(self.output_frame)
def position_widgets(self,
**kwargs):
'''
'''
#OK Button
self.input_ok_button.grid(row = 4,
column = 0,
sticky = 'e')
#Frames
self.input_label_frame.grid(row = 0,
column = 0,
sticky = 'nsew')
self.browser_frame.grid(row = 1,
column = 0,
sticky = 'nw')
self.file_browser.grid(row = 0,
column = 0,
sticky = 'nsew')
self.output_label_frame.grid(row = 0,
column = 1,
sticky = 'nw')
self.graph.grid(row = 0,
column = 0,
sticky = 'nsew')
self.output_frame.grid(row = 0,
column = 0,
sticky = 'nsew')
class MainApp(tk.Tk):
def __init__(self,
title,
*args,
**kwargs):
'''
'''
self._title = title
super().__init__(*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
#Set window title
self.title(self._title)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
'''
'''
self.loader = Loader(self)
def position_widgets(self):
'''
'''
self.loader.grid(row = 0,
column = 0,
sticky = 'nsew')
if __name__ == '__main__':
#Create GUI
root = MainApp('MainApp')
#Run program
root.mainloop()
推荐阅读
- javascript - 什么是“??” 用 JavaScript 做什么?
- laravel - Laravel - 每个新的一天我都有这个错误:无法打开流:权限被拒绝
- apache-kafka - 为什么 Kafka KeyValueStore 在分区重新平衡时会丢失状态?
- java - 将代码从 JAVA 转换为 C#、Socket Streams
- oracle - 更新记录的最有效方法
- azure-functions - Azure 功能发送带附件的电子邮件
- python - 从 Twitter 用户列表 (csv) 中抓取加入日期/用户信息
- excel - 使用 VBA 将工作表添加到 Excel 时出现随机错误
- hyperscript - 如何在 _hyperscript js 中使用变量
- python - Kivy - 通过引用根属性动态添加按钮