使用 QWebEngineView 显示大于 2MB 的内容? [英] Use QWebEngineView to Display Something Larger Than 2MB?

查看:103
本文介绍了使用 QWebEngineView 显示大于 2MB 的内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 QWebEngineView 在 PyQt5 GUI 中显示一些 Plot.ly 或 Plot.ly Dash 图(我还没有决定使用其中一个,所以我现在正在试验两者).由于某些 Chromium 级硬编码限制,这不适用于任何大于 2MB 的图.

我发现了一个

I'm trying to display some Plot.ly or Plot.ly Dash plots ( I haven't settled on using one or the other, so I'm experimenting with both right now) in a PyQt5 GUI using QWebEngineView. This doesn't work for any plots larger than 2MB due to some Chromium-level hardcoded restriction.

I found one similar question that is pretty much identical in terms of our needs. It looks like the OP actually found an answer, but unfortunately for me, they didn't post an example of working code or explain what they did to make it work. I do not understand enough of the underlying theory to piece together an answer with the resources linked in this other question, and my Stack reputation isn't high enough to comment and ask the OP what exactly worked.

Here is a minimum reproducible example that displays a plot embedded in the GUI. It's a modification of an answer to a question about embedding Plotly plots in PyQt5 GUIs here:

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_())

Here is a modified version that demonstrates how a large data set cannot be displayed the same way:

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_())

Lastly, here is something I tried to get the large plot to display with QUrl pointing to a local html plot on the disk:

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_())

The plot was generated with:

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)

解决方案

As this answer indicates a possible solution is to use a QWebEngineUrlSchemeHandler, in the next section I have created a class that allows you to register functions that are invoked through custom urls:

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_())

Structure:

├── main.py
└── qtplotly.py

Output:

这篇关于使用 QWebEngineView 显示大于 2MB 的内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆