Home > Mobile >  Python ProgressBar & GUI frozen while calculation for plot is going on
Python ProgressBar & GUI frozen while calculation for plot is going on

Time:01-06

Can somebody help me out with threading in python and getting a progress bar working?

Even research gives quite a lot results, i cannot get it working.

I never did threading before and i cannot tell where to put things correctly.

For testing purposes i prepared a simple GUI with a button and a progress bar:

enter image description here

After clicking the button a simple 3d plot will pop-up.

Such plotting may take some computation time and while a user need to wait, id like to have the GUI not frozen and the progressbar animated.

At the moment the GUI freezes until the plot will show up. And after that the progress bar start the animation.

I have read a lot about threading and to put calculations and gui to different threads? But im just to noobish to get it working. Is somebody able to explain me more, direct me to similar problems or documentations? Or maybe, in case is quickly solved, overtake the simple code and correct it the way it should be?

Thanks in advance for any kind of help.

Python script so far:

from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt

def main():

    def plot_sample():

        sleep(5) # simulate calculation times
        x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
        y = x.copy().T # transpose
        z = np.cos(x ** 2   y ** 2)

        fig = plt.figure()
        ax = plt.axes(projection='3d')

        ax.plot_surface(x, y, z,cmap='viridis', edgecolor='none')
        ax.set_title('Surface plot')
        plt.show()

    def progressbar_start():
        progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
        progressbar.start(interval=10)

    def progressbar_stop():
        progressbar.stop()
        progressbar.grid_forget() # hide progressbar when not needed

    def button_clicked():
        progressbar_start()  # start progressbar before computation begins
        plot_sample() # plotting
        progressbar_stop()  # stop progressbar after plot is done



    # GUI
    # window size and settings
    root = ttk.Window()

    # Basic settings for the main window
    root.title('Test progressbar')
    root.minsize(300, 200)
    root.resizable(True, True)
    root.configure(bg='white')

    # grid settings for the main window in which LabelFrames are sitting in
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)
    root.rowconfigure(1, weight=1)

    # Button fto show 3d-plot
    button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
    button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)

    progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')


    # end of GUI
    root.mainloop()


if __name__ == "__main__":
    main()

CodePudding user response:

I have one solution. The main problem is, that You can't run the plt.show() function in a Thread. From this follows, that you can't run the window and the plt.show function at the same time.

from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
from threading import Thread

x, y, z = None, None, None


def main():
    def calculate_xyz():
        sleep(5)  # simulate calculation times
        global x, y, z
        x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
        y = x.copy().T  # transpose
        z = np.cos(x ** 2   y ** 2)

    def progressbar_start():
        progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10)  # let progressbar appear in GUI
    progressbar.start(interval=10)

    def progressbar_stop():
        progressbar.stop()
        progressbar.grid_forget()  # hide progressbar when not needed

    def button_clicked():
        progressbar_start()  # start progressbar before computation begins
        calculate_xyz_thread = Thread(target=calculate_xyz)
        calculate_xyz_thread.start()  # calculate x, y and z
        root.after(500, wait_for_plotting)  # wait for plotting and stop animation after finishing

    def wait_for_plotting():
        if x is None or y is None or z is None:
            root.after(500, wait_for_plotting)  # run self again
            return  # wait for calculation
        # calculation is finished
        progressbar_stop()  # stop progressbar "after" plot is done
        ax = plt.axes(projection='3d')
        ax.plot_surface(x, y, z, cmap='viridis', edgecolor='none')
        ax.set_title('Surface plot')
        root.after(500, plot_3d)

    def plot_3d():
        plt.show()

    # GUI
    # window size and settings
    root = ttk.Window()

    # Basic settings for the main window
    root.title('Test progressbar')
    root.minsize(300, 200)
    root.resizable(True, True)
    root.configure(bg='white')

    # grid settings for the main window in which LabelFrames are sitting in
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)
    root.rowconfigure(1, weight=1)

    # Button fto show 3d-plot
    button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
    button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)

    progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')

    # end of GUI
    root.mainloop()


if __name__ == "__main__":
    main()

CodePudding user response:

You should create a separate thread that will run the function plot_sample and update the progress bar. To do this, you will need to import the threading module.

import threading

def main():
    def plot_sample():
        # your code to create the plot
        ...

    def progressbar_start():
        progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10)
        progressbar.start(interval=10)

    def progressbar_stop():
        progressbar.stop()
        progressbar.grid_forget()

    def button_clicked():
        progressbar_start()
        # create a new thread to run the plot_sample function
        t = threading.Thread(target=plot_sample)
        # start the thread
        t.start()
        # stop the progress bar after the thread finishes
        t.join()
        progressbar_stop()

Now, when the button is clicked, the progress bar will start animating and the plot_sample function will be run in a separate thread. This will allow the GUI to remain responsive while the plot is being created.

  • Related