I’m trying to include a Bokeh based visualisation canvas in a Qt application written in Python (using PySide6
). So I create the visualisation, save it in an HTML file, and read it in a QWebEngineView
widget. It renders fine, but is extremely sluggish to interact with it; e.g. try selecting the zoom tool and zooming in, or panning. The surprising part is, when I open the same HTML in Firefox, it is pretty snappy, as I would expect.
There are 2 time series, with 8760 data points each in the plot. What is going on here?
Minimal example
<code>from argparse import ArgumentParser
from PySide6.QtCore import QUrl, QSize
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtWebEngineWidgets import QWebEngineView
class MainWindow(QMainWindow):
def __init__(self, path: str = "", *args, **kwargs):
super().__init__(*args, **kwargs)
self.browser = QWebEngineView()
self.browser.setUrl(QUrl.fromLocalFile(Path(path).absolute()))
self.setCentralWidget(self.browser)
self.resize(QSize(800, 500))
if __name__ == "__main__":
# HTML example produced using Bokeh: dump-viz.html
parser = ArgumentParser()
parser.add_argument("file")
opts = parser.parse_args()
window = MainWindow(opts.file)
<code>from argparse import ArgumentParser
from pathlib import Path
from PySide6.QtCore import QUrl, QSize
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtWebEngineWidgets import QWebEngineView
class MainWindow(QMainWindow):
def __init__(self, path: str = "", *args, **kwargs):
super().__init__(*args, **kwargs)
if path:
self.browser = QWebEngineView()
self.browser.setUrl(QUrl.fromLocalFile(Path(path).absolute()))
self.setCentralWidget(self.browser)
self.resize(QSize(800, 500))
if __name__ == "__main__":
# HTML example produced using Bokeh: dump-viz.html
parser = ArgumentParser()
parser.add_argument("file")
opts = parser.parse_args()
app = QApplication()
window = MainWindow(opts.file)
window.show()
app.exec()
</code>
from argparse import ArgumentParser
from pathlib import Path
from PySide6.QtCore import QUrl, QSize
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtWebEngineWidgets import QWebEngineView
class MainWindow(QMainWindow):
def __init__(self, path: str = "", *args, **kwargs):
super().__init__(*args, **kwargs)
if path:
self.browser = QWebEngineView()
self.browser.setUrl(QUrl.fromLocalFile(Path(path).absolute()))
self.setCentralWidget(self.browser)
self.resize(QSize(800, 500))
if __name__ == "__main__":
# HTML example produced using Bokeh: dump-viz.html
parser = ArgumentParser()
parser.add_argument("file")
opts = parser.parse_args()
app = QApplication()
window = MainWindow(opts.file)
window.show()
app.exec()
The above script, and an example HTML is available in this gist.
You can run it with hatch run pyside-qwebengine-slow.py qwebengine-ex-viz.html
, or create a virtual env, then pip install PySide6
, and run normally.
Additional info
The code that I use to generate that plot, is more or less equivalent to:
<code>def make_plot(data_list)
"""`data_list`: list of dataclasses representing a time series;
data.y, data.labels, etc.
fig = figure(x_axis_label="x_label", y_axis_label="y_label", title="title", height=400, width=800)
for idx, data in enumerate(data_list):
df = pd.DataFrame({y_label: data.y})
line = fig.line("index", "y_label", source=df, color=palette[idx])
point = fig.scatter("index", "y_label", source=df, color=palette[idx], size=7)
legend_items[data.labels[0]] = [line, point]
legend = Legend(items=list(legend_items.items()))
fig.add_layout(legend, "right")
fig.legend.click_policy = "hide"
fig = make_plot(data_list)
html = file_html(fig, INLINE, title)
<code>def make_plot(data_list)
"""`data_list`: list of dataclasses representing a time series;
data.y, data.labels, etc.
"""
palette = TolRainbow[10]
legend_items = {}
fig = figure(x_axis_label="x_label", y_axis_label="y_label", title="title", height=400, width=800)
for idx, data in enumerate(data_list):
df = pd.DataFrame({y_label: data.y})
line = fig.line("index", "y_label", source=df, color=palette[idx])
point = fig.scatter("index", "y_label", source=df, color=palette[idx], size=7)
legend_items[data.labels[0]] = [line, point]
legend = Legend(items=list(legend_items.items()))
fig.add_layout(legend, "right")
fig.legend.click_policy = "hide"
return fig
fig = make_plot(data_list)
html = file_html(fig, INLINE, title)
</code>
def make_plot(data_list)
"""`data_list`: list of dataclasses representing a time series;
data.y, data.labels, etc.
"""
palette = TolRainbow[10]
legend_items = {}
fig = figure(x_axis_label="x_label", y_axis_label="y_label", title="title", height=400, width=800)
for idx, data in enumerate(data_list):
df = pd.DataFrame({y_label: data.y})
line = fig.line("index", "y_label", source=df, color=palette[idx])
point = fig.scatter("index", "y_label", source=df, color=palette[idx], size=7)
legend_items[data.labels[0]] = [line, point]
legend = Legend(items=list(legend_items.items()))
fig.add_layout(legend, "right")
fig.legend.click_policy = "hide"
return fig
fig = make_plot(data_list)
html = file_html(fig, INLINE, title)