I am trying to get scrollbars to work in Tkinter for matplotlib figures. The scrollbars don't adjust to the width and height of the loaded image and I don't know how to troubleshoot this. Any help would be appreciated.
Image:
End Result:
Code:
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() 

