Search code examples
pythonpyqt5plotlyplotly-dashqtwebengine

change dynamically data dash in pyqt5


So I made an App that shows dash graphs inside a pyqt5 which takes data from a table widget. the App fonctions well at the first data but after if I change the data in the table i can't see changes in the graph as well, so can where is the problem occurring?

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets
import sys
import dash
import dash_core_components as dcc
import dash_html_components as html
import threading
import plotly.figure_factory as ff


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(956, 701)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.webEngineView = QtWebEngineWidgets.QWebEngineView(self.centralwidget)
        self.webEngineView.setUrl(QtCore.QUrl("http://127.0.0.1:8050"))
        self.webEngineView.setObjectName("webEngineView")
        self.verticalLayout.addWidget(self.webEngineView)
        self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(0)
        self.tableWidget.setRowCount(0)
        self.verticalLayout.addWidget(self.tableWidget)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setObjectName("pushButton")
        self.verticalLayout.addWidget(self.pushButton)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 956, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.tableWidget.setColumnCount(3)
        self.tableWidget.setRowCount(3)
        self.tableWidget.setHorizontalHeaderLabels(('X', 'Y1','Y2'))
        header = self.tableWidget.horizontalHeader()       
        header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
        header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
        header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
        self.pushButton.clicked.connect(self.Clicked)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
            
    def Clicked(self):

        df=[]
        data = []
        for c in range(self.tableWidget.columnCount()):
            data.append([])
            for r in range(self.tableWidget.rowCount()):
                data[c].append(self.tableWidget.item(r,c).text())
        
        for r in range(self.tableWidget.rowCount()):
            df.append(dict(Task=data[0][r], Start=data[1][r], Finish=data[2][r]))
        print(df)           
        threading.Thread(target=self.run_dash, args=([df]), daemon=True).start()  
        
    def run_dash(self,df):

        fig = ff.create_gantt(df)        
        app = dash.Dash()
        app.layout = html.Div([
            dcc.Graph(figure=fig)
        ])
        
        app.run_server(debug=True, use_reloader=False)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))

from PyQt5 import QtWebEngineWidgets

if __name__ == "__main__":
    
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle(QtWidgets.QStyleFactory.create('Fusion'))
    Optimizer = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(Optimizer)
    Optimizer.show()

    sys.exit(app.exec_())

Note : the seconde and third columns are date columns to show a gantt chart. so data entry in those two columns shauld be like: 2020-07-27 | 2020-07-30


Solution

  • Dash launches a server that updates the information but this is rendered by the browser, this means that although the server has updated information on the client side, it must request that updated information and repaint it. This can be done by reloading the page:

    import sys
    import threading
    
    from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
    
    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    import plotly.figure_factory as ff
    
    
    class QDash(QtCore.QObject):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self._app = dash.Dash()
            self.app.layout = html.Div()
    
        @property
        def app(self):
            return self._app
    
        def update_graph(self, df):
            fig = ff.create_gantt(df)
            self.app.layout = html.Div([dcc.Graph(figure=fig)])
    
        def run(self, **kwargs):
            threading.Thread(target=self.app.run_server, kwargs=kwargs, daemon=True).start()
    
    
    class Mainwindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.browser = QtWebEngineWidgets.QWebEngineView()
            self.table = QtWidgets.QTableWidget()
            self.button = QtWidgets.QPushButton("Press me")
    
            central_widget = QtWidgets.QWidget()
            self.setCentralWidget(central_widget)
            lay = QtWidgets.QVBoxLayout(central_widget)
            lay.addWidget(self.browser, stretch=1)
            lay.addWidget(self.table, stretch=1)
            lay.addWidget(self.button)
    
            self.resize(640, 480)
    
            self.table.setColumnCount(3)
            self.table.setHorizontalHeaderLabels(("X", "Y1", "Y2"))
            header = self.table.horizontalHeader()
            for i in range(self.table.columnCount()):
                header.setSectionResizeMode(i, QtWidgets.QHeaderView.Stretch)
    
            self.qdask = QDash()
            self.qdask.run(debug=True, use_reloader=False)
            self.browser.load(QtCore.QUrl("http://127.0.0.1:8050"))
    
            self.button.clicked.connect(self.update_figure)
    
            current_date = QtCore.QDateTime.currentDateTime()
    
            for i in range(3):
                self.append_row(
                    task="Task{}".format(i),
                    start=current_date,
                    finish=current_date.addDays(i + 1),
                )
    
        @QtCore.pyqtSlot()
        def update_figure(self):
    
            df = []
    
            for i in range(self.table.rowCount()):
                task = self.table.item(i, 0).data(QtCore.Qt.DisplayRole)
                start = (
                    self.table.item(i, 1)
                    .data(QtCore.Qt.DisplayRole)
                    .toString(QtCore.Qt.ISODateWithMs)
                )
                finish = (
                    self.table.item(i, 2)
                    .data(QtCore.Qt.DisplayRole)
                    .toString(QtCore.Qt.ISODateWithMs)
                )
    
                d = dict(Task=task, Start=start, Finish=finish)
                df.append(d)
    
            print(df)
    
            self.qdask.update_graph(df)
            self.browser.reload()
    
        def append_row(
            self,
            task="",
            start=QtCore.QDateTime.currentDateTime(),
            finish=QtCore.QDateTime.currentDateTime(),
        ):
            row = self.table.rowCount()
            self.table.insertRow(row)
            for column, value in enumerate((task, start, finish)):
                it = QtWidgets.QTableWidgetItem()
                it.setData(QtCore.Qt.DisplayRole, value)
                self.table.setItem(row, column, it)
    
    
    if __name__ == "__main__":
    
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
    
        w = Mainwindow()
        w.show()
    
        sys.exit(app.exec_())