Tengo un gráfico potly en PyQt5 QWebEngine y me gustaría poder importar los puntos de clic en python.
Este no es el código completo sino las piezas importantes. Ya puedo interceptar las coordenadas de los puntos a través de código javascript e insertar en una alerta al hacer clic, pero me gustaría poder usarlos en Python.
self.browser = QtWebEngineWidgets.QWebEngineView(self) vbox.addWidget(self.browser) vbox.setSpacing(0) vbox.setContentsMargins(0, 0, 0, 0) self.show_graph(input_dict) def show_graph(self, input_dict): # Initialize figure with 3 3D subplots ... #details of graph ... fig = go.FigureWidget(fig.to_dict()) self.browser.setHtml(fig.to_html(include_plotlyjs='cdn', post_script="document.getElementsByClassName('plotly-graph-div')[0].on('plotly_click', function(data){alert(data.points[0].x + '-' + data.points[0].y)});"))Entonces, la parte javascript del código funciona, pero me gustaría que los datos estén disponibles en python en lugar de la alerta. ¿Cómo podría hacer?
Una posible solución es utilizar QWebChannel. La lógica es exportar un QObject que sirva como puente.
import json import plotly.graph_objects as go from PyQt5.QtCore import pyqtSlot, QFile, QIODevice, QObject from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget from PyQt5.QtWebEngineWidgets import QWebEngineScript, QWebEngineView from PyQt5.QtWebChannel import QWebChannel def get_webchannel_source(): file = QFile(":/qtwebchannel/qwebchannel.js") if not file.open(QIODevice.ReadOnly): return "" content = file.readAll() file.close() return content.data().decode() class Backend(QObject): @pyqtSlot(str, name="handleClicked") def handle_clicked(self, o): py_obj = json.loads(o) print(py_obj) class Widget(QWidget): def __init__(self, parent=None): super().__init__(parent) self.backend = Backend() self.browser = QWebEngineView() script = QWebEngineScript() script.setName("create_connection") script.setSourceCode(get_webchannel_source()) script.setInjectionPoint(QWebEngineScript.DocumentReady) script.setWorldId(QWebEngineScript.MainWorld) script.setRunsOnSubFrames(False) self.browser.page().profile().scripts().insert(script) channel = QWebChannel(self) channel.registerObject("backend", self.backend) self.browser.page().setWebChannel(channel) vbox = QVBoxLayout(self) vbox.addWidget(self.browser) vbox.setSpacing(0) vbox.setContentsMargins(0, 0, 0, 0) self.browser.loadFinished.connect(print) self.build_plot() def build_plot(self): trace = go.Heatmap( z=[[1, 20, 30, 50, 1], [20, 1, 60, 80, 30], [30, 60, 1, -10, 20]], x=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], y=["Morning", "Afternoon", "Evening"], ) data = [trace] layout = go.Layout(title="Activity Heatmap") fig = go.Figure(data=data, layout=layout) fig = go.FigureWidget(fig.to_dict()) script = """ window.backend = null; window.onload = function(){ new QWebChannel(qt.webChannelTransport, function(channel) { window.backend = channel.objects.backend; }); } var elements = document.getElementsByClassName('plotly-graph-div'); if(elements.length > 0){ var element = elements[0]; element.on("plotly_click", function(data){ var point = data.points[0]; if(window.backend != null){ var json_str = JSON.stringify({x: point.x, y: point.y}) window.backend.handleClicked(json_str); } }) } """ html = fig.to_html(include_plotlyjs="cdn", post_script=script) self.browser.setHtml(html) def main(): app = QApplication([]) w = Widget() w.resize(640, 480) w.show() app.exec_() if __name__ == "__main__": main()