Reputation: 514
Is it possible to have Altair or Vega(-Lite) render to a PyQt widget, similar to Matplotlib supporting multiple backends? I know I can use a Qt WebView widget to render a web page with Vega-embed, but I want to prevent the overhead of having to serve this, even if locally.
Upvotes: 3
Views: 1099
Reputation: 8699
Building on the great answer by eyllanesc, this is the version adapted for PyQt6:
from io import StringIO
from typing import Optional
from PyQt6 import QtCore
from PyQt6 import QtWidgets
from PyQt6.QtWebEngineCore import QWebEngineDownloadRequest
from PyQt6.QtWebEngineCore import QWebEnginePage
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWidgets import QWidget
class WebEngineView(QWebEngineView):
def __init__(self, parent: Optional[QWidget] = None):
super().__init__(parent)
self.page().profile().downloadRequested.connect(self.onDownloadRequested)
self.windows = []
@QtCore.pyqtSlot(QWebEngineDownloadRequest)
def onDownloadRequested(self, download: QWebEngineDownloadRequest) -> None:
if (
download.state()
== QWebEngineDownloadRequest.DownloadState.DownloadRequested
):
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, self.tr("Save as"), download.downloadFileName()
)
if path:
download.setDownloadFileName(path)
download.accept()
def createWindow(
self, web_window_type: QWebEnginePage.WebWindowType
) -> Optional[QWebEngineView]:
if web_window_type == QWebEnginePage.WebWindowType.WebBrowserTab:
window = QtWidgets.QMainWindow(self)
view = QWebEngineView(window)
window.resize(640, 480)
window.setCentralWidget(view)
window.show()
return view
def set_chart(self, chart, **kwargs) -> None:
output = StringIO()
chart.save(output, "html", **kwargs)
self.setHtml(output.getvalue())
Upvotes: 0
Reputation: 243887
The best option to visualize a plot with Altair is to use QWebEngineView since altair what is to create javascript code based on the instructions you set. IMHO the best solution is to obtain the html of the chart and set it in a QWebEngineView. In the following example I show how to do the above, in addition to enabling the characteristics of saving the image as svg or png, etc.
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
from io import StringIO
class WebEngineView(QtWebEngineWidgets.QWebEngineView):
def __init__(self, parent=None):
super().__init__(parent)
self.page().profile().downloadRequested.connect(self.onDownloadRequested)
self.windows = []
@QtCore.pyqtSlot(QtWebEngineWidgets.QWebEngineDownloadItem)
def onDownloadRequested(self, download):
if (
download.state()
== QtWebEngineWidgets.QWebEngineDownloadItem.DownloadRequested
):
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, self.tr("Save as"), download.path()
)
if path:
download.setPath(path)
download.accept()
def createWindow(self, type_):
if type_ == QtWebEngineWidgets.QWebEnginePage.WebBrowserTab:
window = QtWidgets.QMainWindow(self)
view = QtWebEngineWidgets.QWebEngineView(window)
window.resize(640, 480)
window.setCentralWidget(view)
window.show()
return view
def updateChart(self, chart, **kwargs):
output = StringIO()
chart.save(output, "html", **kwargs)
self.setHtml(output.getvalue())
if __name__ == "__main__":
import sys
import altair as alt
from vega_datasets import data
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QMainWindow()
cars = data.cars()
chart = (
alt.Chart(cars)
.mark_bar()
.encode(x=alt.X("Miles_per_Gallon", bin=True), y="count()",)
.properties(title="A bar chart")
.configure_title(anchor="start")
)
view = WebEngineView()
view.updateChart(chart)
w.setCentralWidget(view)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Upvotes: 2
Reputation: 86310
Currently the only rendering backend for Altair is Vega-Embed, so rendering it in a PyQt widget will require running some Javascript engine. I suspect that Qt WebView is probably the best option.
If you're OK losing interactivity of charts, you could also use altair_saver as a backend to store a static PNG, SVG, or PDF of the chart and show it within a QtWidget.
Upvotes: 0