Search code examples
javascriptpythonpyqtplotlyqwebview

Get js console.log as python variable in QWebView pyqt


I'm trying to build an application that creates some html + js code with python and loads it into a QWebView.

The final html + js code is the following (raw code available here):

<head><meta charset="utf-8" /><script src="https://cdn.plot.ly/plotly-latest.min.js"></script></head><div id="myDiv" style="height: 100%; width: 100%;" class="plotly-graph-div"></div><script type="text/javascript">window.PLOTLYENV=window.PLOTLYENV || {};window.PLOTLYENV.BASE_URL="https://plot.ly";Plotly.newPlot("myDiv", [{"type": "scatter", "x": [6.81, 6.78, 6.49, 6.72, 6.88, 7.68, 7.69, 7.38, 6.76, 6.84, 6.91, 6.74, 6.77, 6.73, 6.68, 6.94, 6.72], "y": [1046.67, 1315.0, 1418.0, 997.33, 2972.3, 9700.0, 6726.0, 6002.5, 2096.0, 2470.0, 867.0, 2201.7, 1685.6, 2416.7, 1618.3, 2410.0, 2962.0], "mode": "markers", "ids": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}], {"showlegend": true}, {"linkText": "Export to plot.ly", "showLink": false})</script>
<script>    
    var plotly_div = document.getElementById('myDiv')
    var plotly_data = plotly_div.data
    plotly_div.on('plotly_selected', function(data){
    featureId = plotly_data[0].ids[data.points[0].pointNumber]
    featureIds = []
    data.points.forEach(function(pt) {
    featureIds.push(parseInt(pt.id))
        })
    console.log(featureIds)
    })
</script>

This code creates a plot thanks to plotly python API. By saving the plot in a local file (e.g. /home/matteo/plot.html) I'm able to load it in the QWebView with the following piece of code:

from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import *
from PyQt5.QtCore import QUrl
import json

plot_view = QWebView()
plot_view.page().setNetworkAccessManager(QgsNetworkAccessManager.instance())
plot_view_settings = plot_view.settings()
plot_view_settings.setAttribute(QWebSettings.WebGLEnabled, True)
plot_view_settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
plot_view_settings.setAttribute(QWebSettings.Accelerated2dCanvasEnabled, True)

plot_path = '/home/matteo/plot.html'
plot_url = QUrl.fromLocalFile(plot_path)
plot_view.load(plot_url)
plot_view.show()

The javascript function of above simply return the ids of the selected points of the plot in the javascript console when using the lasso selection tool of the plot.

Checking with the inspect tool, it works fine also in the QWebView: each time an element is within the selection, in the console.log I can see the corresponding id of the point.

This is a static image taken from jsfiddle (lasso selection, point selected and console.log output):

enter image description here

the running code is available here, just enable the debugging console and select some point with the lasso selection tool.

What I'd really like to achieve is to take the output of the console (so the ids of the selected points), and re-inject them into the application in order to have an usable python object (list, dictionary, whatever) every time I make a selection.

Is there a way to link the javascript console of the QWebPage in order to obtain python objects?


Solution

  • To get the console.log() output, it is only necessary to overwrite the method javaScriptConsoleMessage().

    Plus: I have added 2 ways to access the list of points, the first is through the attribute obj, and the second through a signal.

    class _LoggedPage(QWebPage):
        obj = [] # synchronous
        newData = pyqtSignal(list) #asynchronous
        def javaScriptConsoleMessage(self, msg, line, source):
            l = msg.split(",")
            self.obj = l
            self.newData.emit(l)
            print ('JS: %s line %d: %s' % (source, line, msg))
    

    complete example:

    class _LoggedPage(QWebPage):
        obj = [] # synchronous
        newData = pyqtSignal(list) #asynchronous
        def javaScriptConsoleMessage(self, msg, line, source):
            l = msg.split(",")
            self.obj = l
            self.newData.emit(l)
            print ('JS: %s line %d: %s' % (source, line, msg))
    
    def onNewData(data):
        print("asynchronous", data)
    
    app = QApplication(sys.argv)
    plot_view = QWebView()
    page = _LoggedPage()
    page.newData.connect(onNewData)
    
    plot_view.setPage(page)
    plot_path = '/home/qhipa/plot.html'
    plot_url = QUrl.fromLocalFile(plot_path)
    plot_view.load(plot_url)
    plot_view_settings = plot_view.settings()
    plot_view_settings.setAttribute(QWebSettings.WebGLEnabled, True)
    plot_view_settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
    plot_view_settings.setAttribute(QWebSettings.Accelerated2dCanvasEnabled, True)
    
    # synchronous request simulation
    timer = QTimer(plot_view)
    timer.timeout.connect(lambda: print("synchronous", page.obj))
    timer.start(1000)
    
    plot_view.show()
    sys.exit(app.exec_())