Resizing a matplotlib plot in a tkinter Toplevel

Question:

I have some code that opens a plot in a tkinter toplevel widget. When I grab the corner of the toplevel to resize it, I would like the plot to resize along with the window.

import Tkinter
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg
from matplotlib.figure import Figure

class grapher_gui(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.graph()

    def graph(self):
        x_vals = [0,3,10,15]
        y_vals = [232,120,45,23]

        toplevel = Tkinter.Toplevel(width=2000)
        figure = Figure(figsize=(10,5))
        ax = figure.add_subplot(111)
        plot = ax.plot(x_vals, y_vals, 'k-')
        ax.set_xlabel('Time')
        ax.set_ylabel('Numbers')
        ax.set_title('Title')

        canvas = FigureCanvasTkAgg(figure, master=toplevel)
        canvas.show()
        canvas.get_tk_widget().grid(row=0)            
        toolbar = NavigationToolbar2TkAgg(canvas, toplevel)
        toolbar.grid(row=1, sticky=Tkinter.W) 
        toolbar.update() 
        toplevel.mainloop()

if __name__ == "__main__":
    app = grapher_gui(None)
    app.title('Grapher')
    app.mainloop()

The only thing I could think of to try was adding sticky='NSEW' to canvas.get_tk_widget().grid(row=0), but that doesn’t work.

Asked By: El Confuso

||

Answers:

You have to bind an event to the window resize – in other words, instead of having the program watch for something like a mouse click and do something when it happens, have the program watch for the window being resized and do something when that happens.

In graph(), add:

self.toplevel.bind("<Configure>", resize)

Then make the variables instance variables (e.g. self.toplevel instead of toplevel) so you can refer to them from other methods.

Then add an instance method def resize(event): that does whatever you would need to do to redraw the plot with a new size. You can base the size off of the Toplevel window’s dimensions by accessing toplevel.winfo_height() and toplevel.winfo_width() (add self if you’ve made that an instance variable).

When the Toplevel window is resized, that event will be “seen” and routed to the given callback (the method that’s bound to that event).

Here’s a small example that prints the size of the root window whenever you resize it:

>>> import tkinter as tk
>>> root = tk.Tk()
>>> def onsize(event):
...     print(root.winfo_width(), root.winfo_height())
...
>>> root.bind("<Configure>", onsize)

By the way, you only need to call mainloop() once, in the main area. Don’t call it for every Toplevel window.

Answered By: TigerhawkT3

Try using pack with expand=True instead of grid with sticky. This simplified example works for me and the chart resizes with the window:

import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure

import Tkinter as tk
import ttk

class My_GUI:

    def __init__(self,master):
        self.master=master
        master.title("Dashboard")
        f = Figure(figsize=(5,5), dpi=100)
        a = f.add_subplot(111)
        a.scatter([1,2,3,4,5,6,7,8],[5,6,1,3,8,9,3,5])
        canvas1=FigureCanvasTkAgg(f,master)
        canvas1.show()
        canvas1.get_tk_widget().pack(side="top",fill='both',expand=True)
        canvas1.pack(side="top",fill='both',expand=True)

root=tk.Tk()
gui=My_GUI(root)
root.mainloop()

I know it may be difficult depending on how far along your gui is to redesign using pack… but may be worthwhile instead of making a custom resize event handler. I do sympathize though, would be great if grid and sticky worked for the charts :/

Answered By: Vlox

In my experience if you use grid you need to use rowconfigure and columnconfigure on the parent widgets and then also the child widgets in order to make them resize properly.

import tkinter
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

class grapher_gui(tkinter.Tk):
    def __init__(self, parent):
        tkinter.Tk.__init__(self, parent)
        # Add col, row configure to the parent of toplevel
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.parent = parent
        self.graph()

    def graph(self):
        x_vals = [0,3,10,15]
        y_vals = [232,120,45,23]

        toplevel = tkinter.Toplevel(width=2000)
        # Add col, row configure to the toplevel itself
        toplevel.columnconfigure(0, weight=1)
        toplevel.rowconfigure(0, weight=1)
        figure = Figure(figsize=(10,5))
        ax = figure.add_subplot(111)
        plot = ax.plot(x_vals, y_vals, 'k-')
        ax.set_xlabel('Time')
        ax.set_ylabel('Numbers')
        ax.set_title('Title')

        canvas = FigureCanvasTkAgg(figure, master=toplevel)
        canvas.draw()
        canvas.get_tk_widget().grid(row=0)            
        toplevel.mainloop()

if __name__ == "__main__":
    app = grapher_gui(None)
    app.title('Grapher')
    app.mainloop()
Answered By: chris
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.