Reputation: 371
I want to show a lot of subplots in a Tkinter window and be able to scrolldown to see all the plots with a good size. However, it is all packed and it seems that the subplots don't take all the space allowed in my figure and only restricts to the space of the window. How can I make it more spaced out and make the subplots bigger?
I've tried different paddings with the tight_layout()
option, changing the figure size and other parameters of Tkinter like the fill
or expand
in my widgets.
import tkinter as tk
from tkinter import Scrollbar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
class ScrollableWindow:
def __init__(self, master, fig, **options):
master.geometry("%dx%d+0+0" % (800, 500))
master.focus_set()
fig_wrapper = tk.Frame(master, width=800, height=fig.get_figheight())
fig_wrapper.pack(fill=tk.BOTH, expand=True)
fig_canvas = FigureCanvasTkAgg(fig, master=fig_wrapper)
scrollbar = Scrollbar(fig_wrapper, orient=tk.VERTICAL, command=fig_canvas.get_tk_widget().yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
fig_canvas.get_tk_widget().pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
fig_canvas.get_tk_widget().config(yscrollcommand = scrollbar.set, scrollregion=fig_canvas.get_tk_widget().bbox("all"), width=800, height=1000)
n_col, n_row = 3, 11
fig, axes = plt.subplots(figsize=(n_col,n_row*2), ncols=n_col, nrows=n_row)
for i in range(axes.shape[0]):
for j in range(axes.shape[1]):
axes[i,j].set_xlabel("xlabel")
axes[i,j].set_ylabel("ylabel")
fig.tight_layout()
showStatsWindow = tk.Tk()
showStatsWindow_ = ScrollableWindow(showStatsWindow, fig)
showStatsWindow.mainloop()
Here's an example with empty plots of what it looks like. I want to have 3 or 4 subplots per row, but it is all squeezed together. As you can see, I have more space down in my window and it changes with the figsize parameter but it's all blank.
Upvotes: 5
Views: 2796
Reputation: 21
more optional customization if you need call this function by tkinter grid.
import tkinter as tk
from tkinter import *
from tkinter import Scrollbar
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class ScrollableTkAggY(FigureCanvasTkAgg):
def __init__(self, figure, master, *args, **kwargs):
# --- create canvas with scrollbar ---
self.canvas = tk.Canvas(master)
self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
self.canvas.rowconfigure(0, weight=1)
self.canvas.columnconfigure(0, weight=1)
self.fig_wrapper = tk.Frame(self.canvas)
self.fig_wrapper.grid(row=0, column=0, sticky=tk.NSEW)
self.fig_wrapper.rowconfigure(0, weight=1)
self.fig_wrapper.columnconfigure(0, weight=1)
super(ScrollableTkAggY, self).__init__(figure, master=self.fig_wrapper, *args, **kwargs)
self.tkagg = self.get_tk_widget()
self.tkagg.grid(row=0, column=0, sticky=tk.NSEW)
self.vbar = Scrollbar(self.canvas, orient=tk.VERTICAL, command=self.canvas.yview)
self.vbar.grid(row=0, column=1, sticky=tk.NS)
self.canvas.configure(yscrollcommand=self.vbar.set, scrollregion=self.canvas.bbox(tk.ALL))
# when all widgets are in canvas
self.canvas.bind('<Configure>', self.on_configure)
# --- put frame in canvas ---
self.canvas_frame = self.canvas.create_window((0, 0), window=self.fig_wrapper, anchor=tk.NW)
ScrollableTkAggY_meths = vars(ScrollableTkAggY).keys()
methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
methods = methods.difference(ScrollableTkAggY_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.canvas, m))
def __str__(self):
return str(self.canvas)
# expand canvas_frame when canvas changes its size
def on_configure(self, event):
# update scrollregion after starting 'mainloop'
self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL))
# when all widgets are in canvas
canvas_width = event.width
self.canvas.itemconfig(self.canvas_frame, width=canvas_width - 20)
class ScrollableTkAggX(FigureCanvasTkAgg):
def __init__(self, figure, master, *args, **kwargs):
# --- create canvas with scrollbar ---
self.canvas = tk.Canvas(master)
self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
self.canvas.rowconfigure(0, weight=1)
self.canvas.columnconfigure(0, weight=1)
self.fig_wrapper = tk.Frame(self.canvas)
self.fig_wrapper.grid(row=0, column=0, sticky=tk.NSEW)
self.fig_wrapper.rowconfigure(0, weight=1)
self.fig_wrapper.columnconfigure(0, weight=1)
super(ScrollableTkAggX, self).__init__(figure, master=self.fig_wrapper, *args, **kwargs)
self.tkagg = self.get_tk_widget()
self.tkagg.grid(row=0, column=0, sticky=tk.NSEW)
self.hbar = Scrollbar(self.canvas, orient=tk.HORIZONTAL, command=self.canvas.xview)
self.hbar.grid(row=1, column=0, sticky=tk.EW)
self.canvas.configure(xscrollcommand=self.hbar.set, scrollregion=self.canvas.bbox(tk.ALL))
# when all widgets are in canvas
self.canvas.bind('<Configure>', self.on_configure)
# --- put frame in canvas ---
self.canvas_frame = self.canvas.create_window((0, 0), window=self.fig_wrapper, anchor=tk.NW)
ScrollableTkAggX_meths = vars(ScrollableTkAggX).keys()
methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
methods = methods.difference(ScrollableTkAggX_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.canvas, m))
def __str__(self):
return str(self.canvas)
# expand canvas_frame when canvas changes its size
def on_configure(self, event):
# update scrollregion after starting 'mainloop'
self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL))
# when all widgets are in canvas
canvas_height = event.height
self.canvas.itemconfig(self.canvas_frame, height=canvas_height - 20)
class mclass:
def __init__(self, window):
self.figY = Figure(figsize=(10, 30))
self.axesY = self.figY.subplots(ncols=3, nrows=10)
self.figX = Figure(figsize=(30, 10))
self.axesX = self.figX.subplots(ncols=10, nrows=3)
self.canvasY = ScrollableTkAggY(figure=self.figY, master=window)
self.canvasY.grid(row=0, column=0, sticky='nsew')
self.canvasY.rowconfigure(0, weight=1)
self.canvasY.columnconfigure(0, weight=1)
self.canvasX = ScrollableTkAggX(figure=self.figX, master=window)
self.canvasX.grid(row=1, column=0, sticky='nsew')
self.canvasX.rowconfigure(0, weight=1)
self.canvasX.columnconfigure(0, weight=1)
window.geometry("%dx%d+0+0" % (800, 600))
window.focus_set()
self.do_plot()
def do_plot(self):
# Color used in mpl online documentation.
mpl_grey_rvb = (51. / 255., 51. / 255., 51. / 255.)
self.figY.suptitle("Matplotlib's math rendering engine ScrollableTkAggY",
color=mpl_grey_rvb, fontsize=14, weight='bold')
self.figX.suptitle("Matplotlib's math rendering engine ScrollableTkAggX",
color=mpl_grey_rvb, fontsize=14, weight='bold')
# Plotting features demonstration formulae
for i in range(self.axesY.shape[0]):
for j in range(self.axesY.shape[1]):
self.axesY[i, j].set_xlabel("xlabel")
self.axesY[i, j].set_ylabel("ylabel")
self.figY.tight_layout()
self.canvasY.draw()
for i in range(self.axesX.shape[0]):
for j in range(self.axesX.shape[1]):
self.axesX[i, j].set_xlabel("xlabel")
self.axesX[i, j].set_ylabel("ylabel")
self.figX.tight_layout()
self.canvasX.draw()
if __name__ == '__main__':
window = Tk()
start = mclass(window)
window.rowconfigure(0, weight=1)
window.rowconfigure(1, weight=1)
window.columnconfigure(0, weight=1)
# window.columnconfigure(1, weight=1)
window.mainloop()
and this the result. enter image description here
Upvotes: 0
Reputation: 745
The problem was that your scrollbar wasn't set the correct way if you want to see how to set it correctly ckeckout this Vertical scrollbar for frame in Tkinter, Python and How to get frame in canvas window to expand to the size of the canvas?
The frame where you drawing your figure wasn't expanding ....
here is how I fixed it
import tkinter as tk
from tkinter import Scrollbar
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
class ScrollableWindow:
def __init__(self, master, fig, **options):
def on_configure(event):
# update scrollregion after starting 'mainloop'
# when all widgets are in canvas
canvas.configure(scrollregion=canvas.bbox('all'))
# expand canvas_frame when canvas changes its size
canvas_width = event.width
canvas.itemconfig(canvas_frame, width=canvas_width)
# --- create canvas with scrollbar ---
canvas = tk.Canvas(master, )
canvas.pack(side=tk.LEFT, fill='both', expand=True)
scrollbar = tk.Scrollbar(master, command=canvas.yview)
scrollbar.pack(side=tk.RIGHT, fill='both')
canvas.configure(yscrollcommand=scrollbar.set)
# update scrollregion after starting 'mainloop'
# when all widgets are in canvas
canvas.bind('<Configure>', on_configure)
# --- put frame in canvas ---
fig_wrapper = tk.Frame(canvas)
canvas_frame= canvas.create_window((0, 0), window=fig_wrapper,)
master.geometry("%dx%d+0+0" % (800, 500))
master.focus_set()
fig_canvas = FigureCanvasTkAgg(fig, master=fig_wrapper)
fig_canvas.get_tk_widget().pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
n_col, n_row = 3, 11
fig, axes = plt.subplots(figsize=(n_col*2,n_row*2), ncols=n_col, nrows=n_row,)
for i in range(axes.shape[0]):
for j in range(axes.shape[1]):
axes[i,j].set_xlabel("xlabel")
axes[i,j].set_ylabel("ylabel")
fig.tight_layout()
showStatsWindow = tk.Tk()
showStatsWindow_ = ScrollableWindow(showStatsWindow, fig)
showStatsWindow.mainloop()
here is the result I got
Upvotes: 1
Reputation: 1519
Let me first start with the size of tkinter-Window
which is showing matplotlib figure e.g subplots
. Actually, it the fixed window size master.geometry("%dx%d+0+0" % (800, 500))
which is bothering you.
When you simply use tkinter window, It will automatically
resize itself to the figure size. And inversely, if it is resized, the figure will resize as well. There is simple workaround for the above mentioned problem.
Replace the following line :
master.geometry("%dx%d+0+0" % (800, 500))
with:
master.resizable(width=False, height=False)
To provent tkinter window from being resized .
Now to make your size of subplots bigger or smaller, you can change it by changing the figsize=()
parameter accordingly.
fig, axes = plt.subplots(ncols=n_col, nrows=n_row, figsize=(7.5, 25))
I'll must refer you to use PyQT5
, which is more feasible in your given scenario. A working example is given below, where instead of calling tkinter
custom operations such as pack()
and config()
etc.
Qt5
puts the figure into a canvas with scrollbars, such that the figure retains it's original size and can be scrolled within the Qt window. You wouldn't have to deal with the details inside the class but only the call at the end of the script.
Visit reference: Developing GUIs in Python: Tkinter vs PyQt.
import matplotlib
from PyQt5 import QtWidgets
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
matplotlib.use('Qt5Agg')
matplotlib.use('TkAgg')
class ScrollableWindow(QtWidgets.QMainWindow):
def __init__(self, fig):
self.q_app = QtWidgets.QApplication([])
QtWidgets.QMainWindow.__init__(self)
# To set this size of the Display Window
self.setFixedSize(800, 500)
self.widget = QtWidgets.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtWidgets.QVBoxLayout())
self.widget.layout().setContentsMargins(0, 0, 0, 0)
self.widget.layout().setSpacing(10)
self.fig = fig
self.canvas = FigureCanvas(self.fig)
self.canvas.draw()
self.scroll = QtWidgets.QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
self.nav = NavigationToolbar(self.canvas, self.widget)
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.scroll)
self.show()
exit(self.q_app.exec_())
fig, axes = plt.subplots(ncols=3, nrows=11, figsize=(7.5, 25))
for i in range(axes.shape[0]):
for j in range(axes.shape[1]):
axes[i, j].set_xlabel("x-label")
axes[i, j].set_ylabel("y-label")
fig.tight_layout()
ScrollableWindow(fig)
Here is a result using PyQT5
:
Upvotes: 3