我试图在使用QWebEngineView的PyQt5 GUI中显示一些Plot.ly或Plot.ly Dash绘图(我还没有决定使用一个或另一个,所以我现在正在尝试两者)。由于某些Chromium级别的硬编码限制,因此对于大于2MB的任何地块均无法使用。
我发现了一个similar question,在我们的需求上几乎相同。看起来OP确实找到了答案,但是对我来说不幸的是,他们没有发布工作代码示例,也没有解释如何使代码工作。我对基本理论还不够了解,无法将答案与另一个问题中链接的资源拼凑在一起,而且我的Stack信誉还不足以评论和询问OP到底有什么用。
这是一个最小的可重现示例,它显示了嵌入在GUI中的绘图。这是对在PyQt5 GUI here中嵌入Plotly图的问题的答案的修改:
import numpy as np
import plotly.offline as po
import plotly.graph_objs as go
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
def show_qt(fig):
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 += po.plot(fig, include_plotlyjs=False, output_type='div')
raw_html += '</body></html>'
fig_view = QWebEngineView()
# setHtml has a 2MB size limit, need to switch to setUrl on tmp file
# for large figures.
fig_view.setHtml(raw_html)
# fig_view.setUrl(QUrl('temp-plot.html'))
fig_view.show()
fig_view.raise_()
return fig_view
if __name__ == '__main__':
app = QApplication(sys.argv)
# Working small plot:
fig = go.Figure(data=[{'type': 'scattergl', 'y': [2, 1, 3, 1]}])
# Not working large plot:
# t = np.arange(0, 200000, 1)
# y = np.sin(t/20000)
fig = go.Figure(data=[{'type': 'scattergl', 'y': y}])
# po.plot(fig)
fig_view = show_qt(fig)
sys.exit(app.exec_())
以下是修改后的版本,演示了如何无法以相同方式显示大数据集:
import numpy as np
import plotly.offline as po
import plotly.graph_objs as go
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
def show_qt(fig):
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 += po.plot(fig, include_plotlyjs=False, output_type='div')
raw_html += '</body></html>'
fig_view = QWebEngineView()
# setHtml has a 2MB size limit, need to switch to setUrl on tmp file
# for large figures.
fig_view.setHtml(raw_html)
# fig_view.setUrl(QUrl('temp-plot.html'))
fig_view.show()
fig_view.raise_()
return fig_view
if __name__ == '__main__':
app = QApplication(sys.argv)
# Working small plot:
# fig = go.Figure(data=[{'type': 'scattergl', 'y': [2, 1, 3, 1]}])
# Not working large plot:
t = np.arange(0, 200000, 1)
y = np.sin(t/20000)
fig = go.Figure(data=[{'type': 'scattergl', 'y': y}])
# po.plot(fig)
fig_view = show_qt(fig)
sys.exit(app.exec_())
最后,这是我尝试获取的大图以QUrl指向磁盘上本地html图的方式显示的内容:
import numpy as np
import plotly.offline as po
import plotly.graph_objs as go
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
def show_qt(fig):
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 += po.plot(fig, include_plotlyjs=False, output_type='div')
raw_html += '</body></html>'
fig_view = QWebEngineView()
# setHtml has a 2MB size limit, need to switch to setUrl on tmp file
# for large figures.
# fig_view.setHtml(raw_html)
fig_view.setUrl(QUrl('temp-plot.html'))
fig_view.show()
fig_view.raise_()
return fig_view
if __name__ == '__main__':
app = QApplication(sys.argv)
# Working small plot:
# fig = go.Figure(data=[{'type': 'scattergl', 'y': [2, 1, 3, 1]}])
# Not working large plot:
t = np.arange(0, 200000, 1)
y = np.sin(t/20000)
fig = go.Figure(data=[{'type': 'scattergl', 'y': y}])
# po.plot(fig)
fig_view = show_qt(fig)
sys.exit(app.exec_())
该图是通过以下方式生成的:
import numpy as np
import plotly.offline as po
import plotly.graph_objs as go
t = np.arange(0, 200000, 1)
y = np.sin(t/20000)
fig = go.Figure(data=[{'type': 'scattergl', 'y': y}])
po.plot(fig)
答案 0 :(得分:2)
this answer表示可能的解决方案是使用QWebEngineUrlSchemeHandler
,在下一节中,我创建了一个类,该类允许您注册通过自定义网址调用的函数:
qtplotly.py
from PyQt5 import QtCore, QtWebEngineCore, QtWebEngineWidgets
import plotly.offline as po
import plotly.graph_objs as go
class PlotlySchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler):
def __init__(self, app):
super().__init__(app)
self.m_app = app
def requestStarted(self, request):
url = request.requestUrl()
name = url.host()
if self.m_app.verify_name(name):
fig = self.m_app.fig_by_name(name)
if isinstance(fig, go.Figure):
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 += po.plot(fig, include_plotlyjs=False, output_type="div")
raw_html += "</body></html>"
buf = QtCore.QBuffer(parent=self)
request.destroyed.connect(buf.deleteLater)
buf.open(QtCore.QIODevice.WriteOnly)
buf.write(raw_html.encode())
buf.seek(0)
buf.close()
request.reply(b"text/html", buf)
return
request.fail(QtWebEngineCore.QWebEngineUrlRequestJob.UrlNotFound)
class PlotlyApplication(QtCore.QObject):
scheme = b"plotly"
def __init__(self, parent=None):
super().__init__(parent)
scheme = QtWebEngineCore.QWebEngineUrlScheme(PlotlyApplication.scheme)
QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme)
self.m_functions = dict()
def init_handler(self, profile=None):
if profile is None:
profile = QtWebEngineWidgets.QWebEngineProfile.defaultProfile()
handler = profile.urlSchemeHandler(PlotlyApplication.scheme)
if handler is not None:
profile.removeUrlSchemeHandler(handler)
self.m_handler = PlotlySchemeHandler(self)
profile.installUrlSchemeHandler(PlotlyApplication.scheme, self.m_handler)
def verify_name(self, name):
return name in self.m_functions
def fig_by_name(self, name):
return self.m_functions.get(name, lambda: None)()
def register(self, name):
def decorator(f):
self.m_functions[name] = f
return f
return decorator
def create_url(self, name):
url = QtCore.QUrl()
url.setScheme(PlotlyApplication.scheme.decode())
url.setHost(name)
return url
main.py
import numpy as np
import plotly.graph_objs as go
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
from qtplotly import PlotlyApplication
# PlotlyApplication must be created before the creation
# of QGuiApplication or QApplication
plotly_app = PlotlyApplication()
@plotly_app.register("scatter")
def scatter():
t = np.arange(0, 200000, 1)
y = np.sin(t / 20000)
fig = go.Figure(data=[{"type": "scattergl", "y": y}])
return fig
@plotly_app.register("scatter2")
def scatter2():
N = 100000
r = np.random.uniform(0, 1, N)
theta = np.random.uniform(0, 2 * np.pi, N)
fig = go.Figure(
data=[
{
"type": "scattergl",
"x": r * np.cos(theta),
"y": r * np.sin(theta),
"marker": dict(color=np.random.randn(N), colorscale="Viridis"),
}
]
)
return fig
@plotly_app.register("scatter3")
def scatter3():
x0 = np.random.normal(2, 0.45, 30000)
y0 = np.random.normal(2, 0.45, 30000)
x1 = np.random.normal(6, 0.4, 20000)
y1 = np.random.normal(6, 0.4, 20000)
x2 = np.random.normal(4, 0.3, 20000)
y2 = np.random.normal(4, 0.3, 20000)
traces = []
for x, y in ((x0, y0), (x1, y1), (x2, y2)):
trace = go.Scatter(x=x, y=y, mode="markers")
traces.append(trace)
fig = go.Figure(data=traces)
return fig
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.m_view = QtWebEngineWidgets.QWebEngineView()
combobox = QtWidgets.QComboBox()
combobox.currentIndexChanged[str].connect(self.onCurrentIndexChanged)
combobox.addItems(["scatter", "scatter2", "scatter3"])
vlay = QtWidgets.QVBoxLayout(self)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(QtWidgets.QLabel("Select:"))
hlay.addWidget(combobox)
vlay.addLayout(hlay)
vlay.addWidget(self.m_view)
self.resize(640, 480)
@QtCore.pyqtSlot(str)
def onCurrentIndexChanged(self, name):
self.m_view.load(plotly_app.create_url(name))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
# Init_handler must be invoked before after the creation
# of QGuiApplication or QApplication
plotly_app.init_handler()
w = Widget()
w.show()
sys.exit(app.exec_())
结构:
├── main.py
└── qtplotly.py
输出: