Search code examples
pythonpandasmatplotlibpyqt5traceback

PyQT5 live updating plots


I need help with adding live plotting to each of these graphs. I tried many solutions but every single one kept crashing my window. Is there a simple fix that I'm missing?

Down below is code without any updating method:

import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import pandas as pd


class Window(QDialog):

    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        self.figure = plt.figure(tight_layout=True)
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)

        self.button5 = QPushButton('Lane 5')
        self.button4 = QPushButton('Lane 4')
        self.button3 = QPushButton('Lane 3')
        self.button2 = QPushButton('Lane 2')
        self.button1 = QPushButton('Lane 1')

        self.button1.clicked.connect(self.plot1)
        self.button2.clicked.connect(self.plot2)
        self.button3.clicked.connect(self.plot3)
        self.button4.clicked.connect(self.plot4)
        self.button5.clicked.connect(self.plot5)

        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)
        layout.addWidget(self.button3)
        layout.addWidget(self.button4)
        layout.addWidget(self.button5)
        self.setLayout(layout)

    def plot1(self):
        data = pd.read_csv("2021-08-13.csv", parse_dates=['time'], infer_datetime_format=True)
        datafilter = data[data.lane == "Lane 1 Op2"]
        datafilter['time'] = pd.to_datetime(datafiltr['time'], errors='coerce')
        df = datafilter['time'].groupby(datafilter.godzina.dt.to_period("H")).agg('count')
        y = [df.index[i].to_timestamp() for i in range(len(df))]
        self.figure.clear()
        ax = self.figure.add_subplot(111)
        ax.bar(y, df, width=0.035)
        self.canvas.draw()

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

Here is csv file sample:

lane,detail,time,potentiometer,piece,tooltime
Lane 3 Op3,3584,00:00:03,100%,873,0:00:58
Lane 3 Op2,00:00:20,150,,,
Lane 4 Op3,00:01:40,811,,,
Lane 3 Op1,1584,00:00:20,100%,149,0:01:32
Lane 2 Op2,2508,00:00:40,110%,151,0:01:49
Lane 3 Op1,00:00:34,149,,,
Lane 2 Op3,3508,00:00:56,100%,551,0:01:05
Lane 2 Op2,00:00:25,151,,,
Lane 3 Op3,00:01:07,873,,,
Lane 4 Op2,2858,00:01:31,100%,104,0:02:34
Lane 4 Op3,3858,00:01:32,100%,812,0:01:20
Lane 2 Op1,1508,00:01:33,100%,152,0:01:35
Lane 1 Op1,1141,00:01:38,100%,125,0:01:49
Lane 1 Op2,1141,00:01:38,100%,125,0:01:49
Lane 3 Op2,2584,00:01:51,100%,151,0:01:45

Solution

  • There are some issues in your code. I suggest you to add this to your code:

    import traceback
    
    def handle_exception(exc_type, exc_value, exc_traceback):
        print("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
        sys.exit(1)
    
    if __name__ == '__main__':
        sys.excepthook = handle_exception
        app = QApplication(sys.argv)
        main = Window()
        main.show()
        sys.exit(app.exec_())
    

    it this way, each error which will crash your window will be printed, so you can see what is going on.


    The first error I get is:

    Traceback (most recent call last):
      File "C:/Users/main.py", line 60, in <module>
        main = Window()
      File "C:/Users/main.py", line 26, in __init__
        self.button2.clicked.connect(self.plot2)
    AttributeError: 'Window' object has no attribute 'plot2'
    

    In the __init__ method you connect some signal to not defined functions self.plot2, self.plot3 and so on.


    If I comment those line, then I get:

    Traceback (most recent call last):
      File "C:/Users/main.py", line 44, in plot1
        datafilter['time'] = pd.to_datetime(datafiltr['time'], errors='coerce')
    NameError: name 'datafiltr' is not defined
    

    this is a typo in datafilter.


    Fixed this typo, I get:

    Traceback (most recent call last):
      File "C:/Users/main.py", line 45, in plot1
        df = datafilter['time'].groupby(datafilter.godzina.dt.to_period("H")).agg('count')
      File "C:\Users\VENV\stack\lib\site-packages\pandas\core\generic.py", line 5141, in __getattr__
        return object.__getattribute__(self, name)
    AttributeError: 'DataFrame' object has no attribute 'godzina'
    

    This is do to a translation problem, as you already pointed out: godzina --> time.


    Finally, fixed all those issues, I get the working window:

    enter image description here

    Working code

    import sys
    from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
    import matplotlib.pyplot as plt
    import pandas as pd
    
    import traceback
    
    
    class Window(QDialog):
    
        def __init__(self, parent=None):
            super(Window, self).__init__(parent)
            self.figure = plt.figure(tight_layout=True)
            self.canvas = FigureCanvas(self.figure)
            self.toolbar = NavigationToolbar(self.canvas, self)
    
            self.button5 = QPushButton('Lane 5')
            self.button4 = QPushButton('Lane 4')
            self.button3 = QPushButton('Lane 3')
            self.button2 = QPushButton('Lane 2')
            self.button1 = QPushButton('Lane 1')
    
            self.button1.clicked.connect(self.plot1)
            # self.button2.clicked.connect(self.plot2)
            # self.button3.clicked.connect(self.plot3)
            # self.button4.clicked.connect(self.plot4)
            # self.button5.clicked.connect(self.plot5)
    
            layout = QVBoxLayout()
            layout.addWidget(self.toolbar)
            layout.addWidget(self.canvas)
            layout.addWidget(self.button1)
            layout.addWidget(self.button2)
            layout.addWidget(self.button3)
            layout.addWidget(self.button4)
            layout.addWidget(self.button5)
            self.setLayout(layout)
    
        def plot1(self):
            data = pd.read_csv("2021-08-13.csv", parse_dates=['time'], infer_datetime_format=True)
            datafilter = data[data.lane == "Lane 1 Op2"]
            datafilter['time'] = pd.to_datetime(datafilter['time'], errors='coerce')
            df = datafilter['time'].groupby(datafilter.time.dt.to_period("H")).agg('count')
            y = [df.index[i].to_timestamp() for i in range(len(df))]
            self.figure.clear()
            ax = self.figure.add_subplot(111)
            ax.bar(y, df, width=0.035)
            self.canvas.draw()
    
    def handle_exception(exc_type, exc_value, exc_traceback):
        print("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
        sys.exit(1)
    
    
    if __name__ == '__main__':
        sys.excepthook = handle_exception
        app = QApplication(sys.argv)
        main = Window()
        main.show()
        sys.exit(app.exec_())
        
    

    However I get the warning message:

    SettingWithCopyWarning: 
    A value is trying to be set on a copy of a slice from a DataFrame.
    Try using .loc[row_indexer,col_indexer] = value instead
    
    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      datafilter['time'] = pd.to_datetime(datafilter['time'], errors='coerce')