0

Here is a tkinter program, boiled down from a GUI I am working on:

import tkinter as tk
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg, NavigationToolbar2Tk)

class App(tk.Tk):
    def __init__(self):
        super(App, self).__init__()
        self.main_frame = tk.Frame(self,)
        self.main_frame.pack(fill=tk.BOTH, expand=1)
        
        self.plot1_button = tk.Button(self.main_frame, text='Plot 1',
                                      command=self.draw_plot1)
        self.plot1_button.pack(fill=tk.X,expand=1)
        
        self.plot2_button = tk.Button(self.main_frame, text='Plot 2',
                                      command=self.draw_plot2)
        self.plot2_button.pack(fill=tk.X,expand=1)

        self.FIG, self.AX = plt.subplots()
        self.canvas = FigureCanvasTkAgg(self.FIG, master=self.main_frame)   
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        self.toolbar = NavigationToolbar2Tk(self.canvas, self.main_frame)
        self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        
    def draw_plot1(self):
        self.clear_axes()
        fig = self.AX.plot(np.random.rand(10),np.random.rand(10), color='red')
        self.canvas.draw_idle()
        self.toolbar.update()      
        
    def draw_plot2(self):
        self.clear_axes()
        im = self.AX.matshow(np.random.rand(100,100))
        self.canvas.draw_idle()
        self.toolbar.update()
        cb = plt.colorbar(im, ax=self.AX)
    
    def clear_axes(self):
        for ax in self.FIG.axes:
            ax.clear()
            if ax != self.AX:
                ax.remove()
root = App()
root.resizable(False, False)
root.mainloop()

The Plot 1 button draws a random line plot, while the Plot 2 button draws a random heatmap with a colorbar. The Plot 1 button can be clicked repeatedly, creating new random line plots as expected. After 10 clicks, the display looks fine:

Plot 1 after 10 clicks:

But the Plot 2 button causes the figure to shrink each time it is clicked. After 10 clicks, the graph is uninterpretable:

enter image description here

Additionally, the figure size persists when clicking Plot 1 again:

enter image description here

These are the .png files saved from the application's toolbar, but the same can be seen in the GUI window. I have tried add updates to the GUI/canvas (e.g. self.update(), self.canvas.draw_idle()) at different locations but haven't found anything that affects the issue. I added the clear_axes() function because in the real GUI I have some figures with multiple axes and this removes them, but apparently it does not help here.

I have found that if the color bar is removed, the problem disappears (i.e. comment out cb = plt.colorbar(im, ax=self.AX)), but I would like to have this as part of the figure. Can anyone shed light on what is going on, or can anyone suggest a fix? I'm on matplotlib 3.2.1.

Community
  • 1
  • 1
Tom
  • 8,310
  • 2
  • 16
  • 36

1 Answers1

2

The problem is you are not clearing the colorbar when you clear the axes.

class App(tk.Tk):
    def __init__(self):
        super(App, self).__init__()
        self.main_frame = tk.Frame(self,)
        ...
        self.cb = None

    ...

    def draw_plot2(self):
        self.clear_axes()
        im = self.AX.matshow(np.random.rand(100,100))
        self.canvas.draw_idle()
        self.toolbar.update()
        self.cb = plt.colorbar(im, ax=self.AX)

    def clear_axes(self):
        if self.cb:
            self.cb.remove()
            self.cb = None
        for ax in self.FIG.axes:
            ax.clear()
            if ax != self.AX:
                ax.remove()

Also note that you should use matplotlib.figure.Figure instead of pyplot when working with tkinter. See this for the official sample.

Henry Yik
  • 22,275
  • 4
  • 18
  • 40
  • Thanks very much, that helps a lot! – Tom Jun 10 '20 at 15:12
  • You say you *shouldn't* not use `pyplot` with `tkinter`, I haven't heard this before. What's the downside? – Tom Jun 10 '20 at 15:13
  • 2
    If you have your GUI and pyplot managing a figure, they might interfere and cause all problems. For starters, when you quit your GUI, your script might not exit properly due to `pyplot` running on a different thread. The `Figure` class is created for safely embedding `matplotlib` graphs with `tkinter` interface. – Henry Yik Jun 10 '20 at 15:37
  • Nice answer. I met the similar question; however, I still cannot find a better way for it. https://stackoverflow.com/questions/70530615/python-tkinter-figures-with-colorbar-overlap-when-using-slider Would you mind giving me some advices? Thanks! – Denny Dec 31 '21 at 17:17