Embedding a figure plotted with user input, in a Python PySimpleGUI

Question:

I am making a GUI using PySimpleGui for a project. Below is the code replicating the key structure – it consists of two columns, and the LHS column has two tabs. In one of these tabs, I ask for input from the GUI user, in order to plot a graph, that is to be embedded within the same tab, iteratively being updated if different inputs are submitted. So I asked for coefficients b and a for the equation b*x^2 + a*x. I am using matplotlib.use('TkAgg) and FigureCanvas TkAgg, but at the line of code where I wish to draw the plot, and update the GUI

figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)

I get the error: AttributeError: 'Canvas' object has no attribute 'set_canvas'. Please could I get some help on how to fix? I provide below the code for the GUI, and then a plotter class, where the the error is in the class method draw_plot. This class was on a separate script and imported into the main GUI script, thank you!!

##### MAIN GUI Script ####

from GUI_helpers import * # this was where the Plotter class was stored
import PySimpleGUI as sg

profile = Plotter()


def refresh(window):
            window.refresh()

sg.theme("Black")
sg.set_options(font=("Avenir Next", 16))

tab_1 = [  

        [sg.Text("PLOTTER FOR bx^2 + ax",expand_x=True,justification="center",font =("Avenir Next", 16,'bold'))],
    [sg.Text("Coefficeint for x (a)",font =("Avenir Next", 14)),sg.Push(),
        sg.Input(key='-x-',  size=(3, 1)),
        sg.Button("Submit",key = 'x',font =("Avenir Next", 14))],
    [sg.Text("Coefficient for x^2 (b) ",font =("Avenir Next", 14)), sg.Push(),
        sg.Input(key='-x_squared-', size=(3, 1),),
        sg.Button("Submit",key = 'x_squared', font =("Avenir Next", 14))],
   
    [sg.Canvas(size=(20,20), key='plot-canvas')]]


tab_2 = [
    [sg.Text("RANDOM TEXT",expand_x=True,justification="center",font =("Avenir Next", 16,'bold'))],
    [sg.T(' '*5),sg.Button("RANDOM BUTTON", key='random_1',size=(20,1))],
    [sg.T(' '*5),sg.Button("RANDOM BUTTON", key='random_2',size=(20,1))],
    
]
side_1 = [[sg.TabGroup([[sg.Tab('Tab 1', tab_1),
                    sg.Tab('Tab 2', tab_2)]], tab_location='centertop', border_width=5,size= (300,780),enable_events=True)]]  
        
side_2 = [[sg.Text("SIDE 2 HERE:", expand_x=True, expand_y=True)]]

layout = [[sg.Column(side_1, element_justification='top', size= (320,900),justification='center'),
        sg.VSeperator(),
        sg.Column(side_2, expand_x=True, expand_y=True,size= (1100,900),), 
    ]]

window = sg.Window("Test GUI", layout, resizable=True, finalize=True, use_default_focus=False, return_keyboard_events=True)


refresh_thread = None
fig = None
fig_agg = None


while True:
    
    event, values = window.read()
    
    if event  == 'x': 
        if fig_agg is not None:
                    profile.delete_plot(fig_agg)
        profile.update_x(values['-x-'])
        fig = profile.update_plot(window)
        fig_agg = profile.draw_plot(window['plot-canvas'].TKCanvas, fig)
        window.refresh()

    if event  == 'x_squared': 
        if fig_agg is not None:
                profile.delete_plot(fig_agg)
        profile.update_xsquared(values['-x_squared-'])
        fig = profile.update_plot(window)
        fig_agg = profile.draw_plot(window['plot-canvas'].TKCanvas, fig)
        window.refresh()



window.close()

print('Transfer ended')


###### PLOTTER CLASS (separate script) #####


import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


class Plotter:

    def __init__(self): 

        self.a = 0
        self.b = 0

    def update_plot(self,window):

        x_axis =  np.linspace(-10,10)
        x_comp = [self.a * x for x in x_axis]
        x2_comp = [self.b*(x**2) for x in x_axis]
        y_axis = np.add(x_comp,x2_comp)

        plt.ioff()
        plt.plot(x_axis,y_axis)
        plt.xlabel('x')
        plt.ylabel('bx^2 + ax')
        return plt.gcf()


    def draw_plot(canvas, figure, loc=(0, 0)):

        figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
        figure_canvas_agg.draw()
        figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
        return figure_canvas_agg

    def delete_plot(fig_agg):
        
        fig_agg.get_tk_widget().forget()
        plt.close('all')


    def update_x(self, state):
        self.a= float(state)

    def update_xsquared(self,state):
        self.b = float(state)


   

I am also open to alternative ways to embedding the plot in the GUI!

Asked By: Amy23

||

Answers:

It is caused by wrong arguments when passed to a method of an instance.

    def draw_plot(canvas, figure, loc=(0, 0)):

        figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
        figure_canvas_agg.draw()
        figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
        return figure_canvas_agg

    def delete_plot(fig_agg):
        
        fig_agg.get_tk_widget().forget()
        plt.close('all')

Often, the first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python.

Arguments in Method of an instance should be started from self, so above code revised to

    def draw_plot(self, canvas, figure, loc=(0, 0)):

        figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
        figure_canvas_agg.draw()
        figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
        return figure_canvas_agg

    def delete_plot(self, fig_agg):

        fig_agg.get_tk_widget().forget()
        plt.close('all')
Answered By: Jason Yang