Search code examples
pyqtvegavega-litealtair

How to render Altair / Vega in a PyQt widget


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.


Solution

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