Reputation: 163
I am trying to embed a plotly graph into a PyQt5 webengine view. I was able to do so using the following:
open plotly in qwebview in interactive mode
If you read it, the article explains that you can't directly include the javascript in the HTML when using the webengine view (it has issues loading files above 2 MB). However, I'm trying to make it so that the source for the javascript is a local copy of plotly-min.js (saved in the project folder) so that anyone using the program doesn't need an internet connection to view the graphs it generates. I've tried to follow a few examples on html, but to no avail.
The original code that does work with an internet connection is:
raw_html = '<html><head><meta charset="utf-8" />'
raw_html += '<script src="https://cdn.plot.ly/plotly-latest.min.js"></script></head>'
raw_html += '<body>'
raw_html += plot(fig, include_plotlyjs=False, output_type='div')
raw_html += '</body></html>'
temp_view = QWebEngineView()
temp_view.setHtml(graph)
If I understand it correctly, I need to change this part:
<script src="https://cdn.plot.ly/plotly-latest.min.js">
I've already tried:
<script type="text/javascript" src="file:///C:/Users/UserName/PycharmProjects/ProjectFolder/plotly-latest.min.js"></script>
I honestly do not know HTML. This is the only HTML in the code (the rest is in Python 3). Does anyone know why the above might not work and what I need to change? I have a suspicion I might somehow be running into the 2 MB limit the above question referenced, as different variations I've found online for how to reference a local file in HTML don't seem to change anything.
Upvotes: 6
Views: 10385
Reputation: 421
In case it's helpful to anybody, I went a different direction with a solution. Instead of relying on the QWebEngineView.setHTML
method, I decided to use a named temporary file to write the html to the disk, then load that into a QUrl
to load into the QWebEngineView
using the QUrl.fromLocalFile
and QWebEngineView.setUrl
methods.
Altogether I applied this into a custom PlotlyWidget
class, so here's the full thing in case it's helpful to anyone.
Some special care had to be taken to ensure the temporary files get deleted when the PlotlyWiget
goes out of scope and gets garbage collected (i.e. the __del__
override).
import os
import tempfile
import plotly
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QUrl
class PlotlyWidget(QWebEngineView):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.plotly_fig = None
self.html_file = None
def load_fig(self, plotly_fig):
self.plotly_fig = plotly_fig
self.plotly_fig.update_layout(font={'size':18})
self.update_fig()
def update_fig(self):
html = '<html><body>'
html += plotly.offline.plot(self.plotly_fig, output_type='div', include_plotlyjs=True)
html += '</body></html>'
if self.html_file is not None: # close previous tempfiles so that they delete from disk properly
self.html_file.close()
os.unlink(self.html_file.name)
self.html_file = tempfile.NamedTemporaryFile(suffix='.html', delete=False)
with open(self.html_file.name, 'w') as file:
file.write(html)
url = QUrl.fromLocalFile(self.html_file.name)
self.setUrl(url)
def closeEvent(self, event):
"""Delete the temp file on close event."""
super().closeEvent(event)
if self.html_file is not None:
self.html_file.close()
os.unlink(self.html_file.name)
self.html_file = None
def __del__(self):
"""Handle temp file close (and deletion) if QWebEngineView is garbage collected instead of closed."""
if self.html_file is not None:
self.html_file.close()
os.unlink(self.html_file.name)
Typical usage is then to generate a plotly figure and then send it to the load_fig
method like:
# from within QMainWindow or other QWidget instance
fig = go.Figure()
fig.add_scatter(y=ydata, x=xdata)
self.ploty_widget = PlotlyWidget()
self.ploty_widget.load_fig(fig)
self.ploty_widget.show()
Upvotes: 2
Reputation: 1100
I had problems running the example of @eyllanesc under Ubuntu 16.04 / 18.04 with NVIDIA driver.
First I had to downgrade PyQt5
5.10.1
to 5.10.0
to avoid:
[0525/114545.216138:WARNING:stack_trace_posix.cc(648)] Failed to open file: /home/tmp/.gl5Qb0Tf (deleted)
Error: Datei oder Verzeichnis nicht gefunden
Could not find QtWebEngineProcess
[3032:3032:0525/114545.495351:FATAL:zygote_host_impl_linux.cc(182)] Check failed: ReceiveFixedMessage(fds[0], kZygoteBootMessage, sizeof(kZygoteBootMessage), &boot_pid).
...
But this should be fixed for the future in PyQt5 5.11
The second problem was the linking of the shader (ubuntu 16.04
):
QOpenGLShaderProgram::uniformLocation(qt_Matrix): shader program is not linked
QOpenGLShaderProgram: could not create shader program
QOpenGLShader: could not create shader
QOpenGLShader: could not create shader
shader compilation failed:
Under ubuntu 18.04
in VirtualBox
:
Received signal 11 SEGV_MAPERR 000000000000
#0 0x7faa83f029a5 <unknown>
#1 0x7faa82c42501 <unknown>
#2 0x7faa83f02d3d <unknown>
#3 0x7faa9228a890 <unknown>
r8: 0000000000000001 r9: 0000000002d5d3d0 r10: 0000000000000002 r11: 00007faa8ec4a000
r12: 0000000002d386b0 r13: 0000000002d59b50 r14: 0000000000000000 r15: 00007faa8ed3b280
di: 0000000000001f01 si: 00007faa8ed3d210 bp: 0000000002d52e40 bx: 0000000000000001
dx: 0000000002e52220 ax: 0000000002e52220 cx: 0000000000000000 sp: 00007ffce38c2b78
ip: 0000000000000000 efl: 0000000000010202 cgf: 002b000000000033 erf: 0000000000000014
trp: 000000000000000e msk: 0000000000000000 cr2: 0000000000000000
[end of stack trace]
Calling _exit(1). Core file will not be generated.
that can be solved by following the advise of dcortesi fixing an 4 year old Ubuntu bug:
if sys.platform.startswith( 'linux' ) :
from OpenGL import GL
Putting it all togehter:
requirements.txt
PyQt5 == 5.10.0
PyOpenGL
plotly
webview.py
import sys
if sys.platform.startswith( 'linux' ) :
from OpenGL import GL
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QDir, QUrl
import plotly
import plotly.graph_objs as go
sys.argv.append("--disable-web-security")
app = QApplication(sys.argv)
x1 = [10, 3, 4, 5, 20, 4, 3]
trace1 = go.Box(x = x1)
layout = go.Layout(showlegend = True)
data = [trace1]
fig = go.Figure(data=data, layout = layout)
path = QDir.current().filePath('plotly-latest.min.js')
local = QUrl.fromLocalFile(path).toString()
raw_html = '<html><head><meta charset="utf-8" />'
raw_html += '<script src="{}"></script></head>'.format(local)
raw_html += '<body>'
raw_html += plotly.offline.plot(fig, include_plotlyjs=False, output_type='div')
raw_html += '</body></html>'
view = QWebEngineView()
view.setHtml(raw_html)
view.show()
sys.exit(app.exec_())
One can download the the latest plotly.js here. I used plotly.js v1.38.0
for this example.
Upvotes: 2
Reputation: 244003
Many browsers for security reason disable the loading of local files, but as noted in the following forum you can enable that capability with:
sys.argv.append("--disable-web-security")
Assuming the .js is next to your .py file:
.
├── main.py
└── plotly-latest.min.js
you can use the following example:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import QDir, QUrl
import plotly
import plotly.graph_objs as go
sys.argv.append("--disable-web-security")
app = QApplication(sys.argv)
x1 = [10, 3, 4, 5, 20, 4, 3]
trace1 = go.Box(x = x1)
layout = go.Layout(showlegend = True)
data = [trace1]
fig = go.Figure(data=data, layout = layout)
path = QDir.current().filePath('plotly-latest.min.js')
local = QUrl.fromLocalFile(path).toString()
raw_html = '<html><head><meta charset="utf-8" />'
raw_html += '<script src="{}"></script></head>'.format(local)
raw_html += '<body>'
raw_html += plotly.offline.plot(fig, include_plotlyjs=False, output_type='div')
raw_html += '</body></html>'
view = QWebEngineView()
view.setHtml(raw_html)
view.show()
sys.exit(app.exec_())
Upvotes: 5