Use QWebEngineView to Display Something Larger Than 2MB?

时光总嘲笑我的痴心妄想 提交于 2021-01-28 06:51:40

问题


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)


回答1:


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:



来源:https://stackoverflow.com/questions/57260271/use-qwebengineview-to-display-something-larger-than-2mb

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!