Search code examples
pythonpyqt5plotly-dashsslhandshakeexceptionqwebengineview

Embedding a Plotly-Dash plot in a PyQt5 GUI: Handshake Failed, SSL Error


I am trying to build a GUI to help my team rapidly compare large amounts of test data in the form of signals. I envision highly modular, interactive plots in a format that looks similar to Audacity. I'm building the GUI in PyQt5 and am currently trying to decide what to use for the plots; I was planning on matplotlib but it will be pretty clunky for the intended use. Plotly and/or Dash seem much more promising; however, they only display interactive plots in browser, so I need to use some sort of browser display embedded in the GUI. Hence, QWebEngineView.

My GUI itself will be housing plots in tabs in a QTabWidget, which I've developed, and other tabs are populated with placeholder matplotlib plots, which I want to replace because they lack easy interactivity. I am able to successfully display a webpage (google.com) in the browser.

Successfully displaying a webpage in a QWebEngineView object in a QTabWidget in a PyQt5 GUI

enter image description here

However, when I point it at my Dash plot's address (which, for development purposes, I made in a separate program running in another kernel), it gives an error: This site can't provide a secure connection:

Dash Plot not displaying

enter image description here

 On the GUI's Kernel, the error is "[29477:29512:0716/121826.555372:ERROR:ssl_client_socket_impl.cc(1050)] handshake failed; returned -1, SSL error code 1, net_error -107".

In the kernel running the dash plot, the message is: 
127.0.0.1 - - [16/Jul/2019 12:18:26] code 400, message Bad request version ('**À+À/À,À0̨̩À\x13À\x14\x00\x9c\x00\x9d\x00/\x005\x00')

127.0.0.1 - - [16/Jul/2019 12:18:26] "µ±ª~ÜÎÌDñB¤¦jËfM½;*÷hå¸GÛ¼i©Tè**À+À/À,À0̨̩ÀÀ/5" HTTPStatus.BAD_REQUEST -

This is only in the QTabsWidget. The Dash plot displays fine in a separate, stand-alone app (working code for as placeholder Dash plot provided below).

Dash plot successfully displays in a simple PyQt5 GUI

enter image description here

I googled the error, but nothing relevant turned up. I don't understand why this worked fine with the first url, but failed when I pointed it to my Dash plots, especially given that I was able to view the dash plots in a QWebEngineView in my stripped-down, prototype code.

Dash Plot Working Code:

import sys
import threading
from PyQt5 import QtWidgets
import dash
import dash_core_components as dcc
import dash_html_components as html


def run_dash(data, layout):
    app = dash.Dash()
    app.layout = html.Div(children=[
        html.H1(children='Hello Dash'),
        html.Div(children='''
            Dash: A web application framework for Python.
        '''),
        dcc.Graph(
            id='example-graph',
            figure={
                'data': data,
                'layout': layout
            })
        ])
    app.run_server(debug=False)


class MainWindow(QtWidgets.QMainWindow):
    pass

if __name__ == '__main__':
    data = [
        {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
        {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
    layout = {
        'title': 'Dash Data Visualization'
    }

    threading.Thread(target=run_dash, args=(data, layout), daemon=True).start()
    app = QtWidgets.QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit(app.exec_())

Upon running this code, the kernel provides the address for the plot. On my machine, it's http://127.0.0.1:8050.

Prototype GUI embedding code:


import sys
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWidgets import QApplication

app = QApplication(sys.argv)

web = QWebEngineView()
#web.load(QUrl("https://www.google.com"))
web.load(QUrl("http://127.0.0.1:8050"))
web.show()

sys.exit(app.exec ())

Snippet of code from my actual PyQt5 GUI:

web = QWebEngineView()
# web.load(QUrl("https://www.google.com")) web.load(QUrl("https://127.0.0.1:8050/"))
web.show()
self.ui.TabsContainer.addTab(web, "QWebEngineView Object") 
# TabsContainer is a QTabsWidget

How do I get around the error preventing me from embedding the plot in my GUI?

EDIT: I thought perhaps it was related to the QTabWidget, so I wrote the following simple code to test that idea, and my Dash plot displayed fine. However, it does not display in the main multithreaded GUI where I need it, despite being constructed the same way.


import sys
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *


class CustomMainWindow(QMainWindow):  # MainWindow is a subclass of QMainWindow
    def __init__(self, *args, **kwargs):
        super(CustomMainWindow, self).__init__(*args, **kwargs)
#        a = 1

        self.setWindowTitle("Window Title")

        label = QLabel("Label")
        label.setAlignment(Qt.AlignCenter)
#        
        layout = QVBoxLayout()
#        layout.addWidget(Color('red'))
#        layout.addWidget(Color('green'))
        layout.addWidget(Color('blue'))

        TW = QTabWidget()


        web1 = QWebEngineView()
        web1.load(QUrl("http://www.google.com"))
        #web1.load(QUrl("http://127.0.0.1:8050"))

        web2 = QWebEngineView()
        web2.load(QUrl("http://127.0.0.1:8050"))

        TW.addTab(web1, 'web1')
        TW.addTab(web2, 'web2')
        layout.addWidget(TW)


        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)        

class Color(QWidget):

    def __init__(self, color, *args, **kwargs):
        super(Color, self).__init__(*args, **kwargs)
        self.setAutoFillBackground(True)

        palette = self.palette()
        palette.setColor(QPalette.Window, QColor(color))
        self.setPalette(palette)


app = QApplication(sys.argv)

CMWindow = CustomMainWindow()  # Instead of using QMainWindow, we now use our custom window subclassed from QMainWindow
CMWindow.show()
sys.exit(app.exec ())

Solution

  • Well, the answer turned out to be simple:

    In the working examples I posted, I load QUrl("http://127.0.0.1:8050"). In the code that doesn't work, I used https:// . Apparently the Dash plot doesn't use https.