Timberghost_
Timberghost_

Reputation: 175

Update QT elements over course of for loop PyQT5

-| Summary |-

Hello, Recently I have been working on creating a graphical representation of a sorting algorithm. Currently however I am stuck trying to find a way to update the gui elements overtime compared to all the elements being sorted at once when the for loop is completely finished. To fix this issue I tried using pythons threading library to run the method(loop) in its own thread where it should be able to run over the course of multiple frames instead of just one, however this lead to the same result or sometimes all the gui elements would dissapear (would still exist just not visible on screen)

-| Goal |-

Have The ability to watch the gui elements update over the course of x amount of time to show the progress of the sorting algorithm

-| Expected/Actual |-

Actual : The for loop waits till completion before updating all gui elements Expected: Update gui elements overtime (x amount of seconds)

-| MRE (Minimal Reproducible Example) |-

Around Line 45 is where I run the algorithm, The Method I tried for threading is shown just above that at line 40

from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout
import sys
import random

class MRE(QWidget):

    def __init__(self):
        super().__init__()
        #initialized Values
        self.n = 250 # n is the Number Of Values In the array to be sorted
        self.values = [] #Holds all the Rectangle Objects that are in the Graphics Scene

        # Set Layout
        grid = QGridLayout()
        self.setLayout(grid)

        # Set Size of Window
        self.resize(600, 400)

        # Set Widgets
        self.sortingGraphic = QtWidgets.QGraphicsView(self)
        self.sortingGraphic.resize(600,400)

        # Setup Scene View
        self.scene = QtWidgets.QGraphicsScene()
        self.sortingGraphic.setScene(self.scene)

        # Open Application
        self.show()

    def main(self):
        self.scene.setSceneRect(0, 0, self.sortingGraphic.width() - 5, self.sortingGraphic.height() - 5)
        self.value_gen() #Generates Random Values to Array Change n (in __init__) to change amount


        #---- Help! ----
        # Also Tried the Following
        #import threading
        #threading.Thread(self.bubbleSort()).start()

        # Runs Bubble Sort Algorithm that will sort the Values in order
        self.bubbleSort()

    def bubbleSort(self):
        n = len(self.values)

        for i in range(n - 1):
            for j in range(0, n - i - 1):
                if self.values[j].scenePos().y() > self.values[j + 1].scenePos().y():
                    # Update Graphic Positions
                    self.switch(self.values[j], self.values[j + 1])

                    # Update Position in List
                    self.values[j], self.values[j + 1] = self.values[j + 1], self.values[j]

    def switch(self, a: QtWidgets.QGraphicsRectItem, b: QtWidgets.QGraphicsRectItem):
        #Holds temporary values of each item
        temp_a = a.scenePos()
        temp_b = b.scenePos()

        # Updates Graphics View and swaps positions
        a.setPos(temp_b.x(), temp_a.y())
        b.setPos(temp_a.x(), temp_b.y())

    def value_gen(self):
        # Grab the Size of the View and calculate width of each line
        width = (self.sortingGraphic.geometry().width() - 5) / self.n

        for value in range(self.n):
            # Randomizes Height Between 0 and Height of Screen
            height = random.randrange(0, self.sortingGraphic.geometry().height() - 5)

            # Create Rect Item
            rect_item = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, 0, width, height))
            rect_item.setPos(width * value, self.sortingGraphic.height() - height)

            # Adds Item to Values List
            self.values.append(rect_item)

            # Adds Rect item to Screen
            self.scene.addItem(rect_item)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mre = MRE()
    mre.main()
    sys.exit(app.exec_())

Final Thoughts

If anyone has any ideas on what I could try it would be greatly appreciated, thank you!

Upvotes: 0

Views: 214

Answers (1)

eyllanesc
eyllanesc

Reputation: 243897

The objective of visualizing an algorithm is not to visualize its speed but the behavior of the data, so in this case, a timer that executes each pass in t seconds is enough as shown below:

import random
import sys

from PyQt5 import QtWidgets, QtCore, QtGui


def bubbleSort(elements, switch, compare):
    n = len(elements)
    for i in range(n - 1):
        for j in range(0, n - i - 1):
            if compare(elements[j], elements[j + 1]):
                switch(elements[j], elements[j + 1])
                elements[j], elements[j + 1] = (
                    elements[j + 1],
                    elements[j],
                )
                yield


class MRE(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.n = 100
        self.items = []
        self.resize(600, 400)
        self.view = QtWidgets.QGraphicsView()
        self.view.setRenderHints(QtGui.QPainter.Antialiasing)
        self.view.scale(1, -1)
        self.scene = QtWidgets.QGraphicsScene()
        self.view.setScene(self.scene)
        grid = QtWidgets.QGridLayout(self)
        grid.addWidget(self.view)

        self.timer = QtCore.QTimer(interval=10, timeout=self.next_step)

    def main(self):
        self.value_gen()
        self.start_algorithm()
        self.show()

    def value_gen(self):
        height = 100
        width = 2

        for value in range(self.n):
            h = random.randrange(0, height)
            item = self.scene.addRect(QtCore.QRectF(0, 0, width, h))
            item.setPos(2 * value * width, 0)
            item.setPen(QtGui.QPen(QtCore.Qt.NoPen))
            item.setBrush(QtGui.QColor("black"))
            self.items.append(item)

        self.fix_size()

    def start_algorithm(self):
        self.algorithm = bubbleSort(self.items, self.switch, self.compare)
        self.timer.start()

    def next_step(self):
        try:
            next(self.algorithm)
        except StopIteration:
            self.timer.stop()

    def switch(self, item_a, item_b):
        pos_a = item_a.scenePos()
        pos_b = item_b.scenePos()
        item_a.setPos(pos_b)
        item_b.setPos(pos_a)
        self.fix_size()

    def compare(self, item_a, item_b):
        return item_a.rect().height() < item_b.rect().height()

    def fix_size(self):
        self.view.fitInView(self.scene.itemsBoundingRect())

    def resizeEvent(self, event):
        self.fix_size()
        super().resizeEvent(event)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    mre = MRE()
    mre.main()
    sys.exit(app.exec_())

Upvotes: 2

Related Questions