Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why subplots are not taking all space allowed by figure in Matplotlib embedded with Tkinter?

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.

like image 680
Jérémy Talbot-Pâquet Avatar asked Sep 13 '19 19:09

Jérémy Talbot-Pâquet


People also ask

How do I make more space between subplots in Matplotlib?

We can use the plt. subplots_adjust() method to change the space between Matplotlib subplots. The parameters wspace and hspace specify the space reserved between Matplotlib subplots. They are the fractions of axis width and height, respectively.

How do I stop subplots from overlapping in Matplotlib?

Often you may use subplots to display multiple plots alongside each other in Matplotlib. Unfortunately, these subplots tend to overlap each other by default. The easiest way to resolve this issue is by using the Matplotlib tight_layout() function.

Which of the following function adjusts the subplot params so that subplots fits into figure area?

tight_layout automatically adjusts subplot params so that the subplot(s) fits in to the figure area.


2 Answers

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:

Simple Result with PyQT5

like image 73
Muhammad Usman Bashir Avatar answered Oct 20 '22 11:10

Muhammad Usman Bashir


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

enter image description here

like image 35
Ali Avatar answered Oct 20 '22 09:10

Ali