[Python] PyQt6 QWebChannel: Python is not receiving data from Javascript

Discussão em 'Python' iniciado por Stack, Maio 5, 2025.

  1. Stack

    Stack Membro Participativo

    I just can't figure out why this isn't working even after looking at multiple related issues such as here. I'm trying to get my javascript to interact with python, and I'm starting off by just sending a signal for a QObject to print an argument set in JavaScript, and I can't seem to get it working.

    The JavaScript for the D3 library seems to work as intended and is being displayed in the application window, but I am not seeing any print statement in my python console when executing the python object in JavaSCript. I also couldn't figure out how to view the browser dev console and tried searching online but could not find any answers. Any help would be greatly appreciated!

    Python:

    from PyQt6 import uic
    from PyQt6.QtWidgets import QPushButton, QSizePolicy
    from PyQt6.QtCore import QUrl, QObject, pyqtSlot
    from PyQt6.QtWebEngineWidgets import QWebEngineView
    from PyQt6.QtWebEngineCore import QWebEngineSettings
    from PyQt6.QtWebChannel import QWebChannel

    from ..views import View
    from ..helpers import Items, Actions, ViewState

    import os

    class PyObj(QObject):
    @pyqtSlot(str)
    def sendData(self, data):
    print(f"Received from JS: {data}")

    class StatsView(View):
    def __init__(self) -> None:
    super().__init__()
    self.view_state = ViewState.STATS
    self.window = uic.loadUi("app/frontend/qtdesigner/stats_design.ui")
    self.initialize_style()

    html_path = os.path.abspath("app/frontend/experiments/index.html")
    self.webEngineView = self.window.findChild(QWebEngineView, "graphTest")


    #self.webEngineView.settings().setAttribute(QWebEngineSettings.WebAttribute.WebAttribute.DeveloperExtrasEnabled, True)

    self.channel = QWebChannel(self)
    print(self.channel)
    self.obj = PyObj(self)
    self.channel.registerObject("pyObj", self.obj)
    self.webEngineView.page().setWebChannel(self.channel)

    self.webEngineView.setUrl(QUrl.fromLocalFile(html_path))




    # Make sure the QWebEngineView fills the layout
    size_policy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
    self.webEngineView.setSizePolicy(size_policy)


    HTML/JS:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>D3 Bar Chart Demo</title>
    <style>
    body {
    font-family: sans-serif;
    margin: 20px;
    }
    .bar:hover {
    opacity: 0.8;
    }
    .tooltip {
    position: absolute;
    pointer-events: none;
    background: rgba(0,0,0,0.7);
    color: white;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    }
    </style>
    </head>
    <body>
    <h2>D3.js Bar Chart (PyQt Integration)</h2>
    <button onclick="updateData()">Update Data</button>
    <svg id="chart" width="800" height="400"></svg>
    <div id="tooltip" class="tooltip" style="display: none;"></div>

    <script src="d3.v7.min.js"></script>
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <script>

    window.onload = function () {
    new QWebChannel(qt.webChannelTransport, function(channel) {
    window.pyObj = channel.objects.pyObj;

    // Now you can use pyObj directly within this function or in the global scope
    // For testing, you can just call a function here if you want
    pyObj.sendData('Test message from JS');
    });
    };
    const svg = d3.select("#chart");
    const width = +svg.attr("width");
    const height = +svg.attr("height");
    const margin = { top: 30, right: 30, bottom: 40, left: 50 };

    const xScale = d3.scaleBand().padding(0.1).range([margin.left, width - margin.right]);
    const yScale = d3.scaleLinear().range([height - margin.bottom, margin.top]);
    const colorScale = d3.scaleSequential(d3.interpolateBlues);

    const tooltip = d3.select("#tooltip");

    function generateDummyData() {
    return Array.from({ length: 24 }, (_, i) => ({
    hour: i,
    value: Math.floor(Math.random() * 100),
    }));
    }

    let data = generateDummyData();

    function render(data) {
    xScale.domain(data.map(d => d.hour));
    const maxVal = d3.max(data, d => d.value);
    yScale.domain([0, maxVal]);
    colorScale.domain([0, maxVal]);

    // Axes
    svg.selectAll(".x-axis").data([null]).join("g")
    .attr("class", "x-axis")
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .transition()
    .call(d3.axisBottom(xScale).tickFormat(d => `${d}:00`).tickSizeOuter(0));

    svg.selectAll(".y-axis").data([null]).join("g")
    .attr("class", "y-axis")
    .attr("transform", `translate(${margin.left},0)`)
    .transition()
    .call(d3.axisLeft(yScale));

    // Bars
    const bars = svg.selectAll(".bar").data(data, d => d.hour);

    bars.join(
    enter => enter.append("rect")
    .attr("class", "bar")
    .attr("x", d => xScale(d.hour))
    .attr("width", xScale.bandwidth())
    .attr("y", yScale(0))
    .attr("height", 0)
    .attr("fill", d => colorScale(d.value))
    .on("mousemove", (event, d) => {
    tooltip
    .style("display", "block")
    .style("left", (event.pageX + 10) + "px")
    .style("top", (event.pageY - 20) + "px")
    .html(`<strong>${d.hour}:00</strong><br>${d.value} reviews`);
    })
    .on("mouseleave", () => tooltip.style("display", "none"))
    .transition()
    .duration(800)
    .attr("y", d => yScale(d.value))
    .attr("height", d => yScale(0) - yScale(d.value)),

    update => update
    .transition()
    .duration(800)
    .attr("x", d => xScale(d.hour))
    .attr("width", xScale.bandwidth())
    .attr("y", d => yScale(d.value))
    .attr("height", d => yScale(0) - yScale(d.value))
    .attr("fill", d => colorScale(d.value)),

    exit => exit
    .transition()
    .duration(400)
    .attr("y", yScale(0))
    .attr("height", 0)
    .remove()
    );
    }

    function updateData() {
    // Generate new data and update the chart
    // Generate new data and update the chart
    data = generateDummyData();
    render(data);

    // Send the new data to Python directly without error handling
    const dataString = JSON.stringify(data);
    pyObj.sendData(`Updated Data from JS: ${dataString}`);
    }

    render(data);
    </script>
    </body>
    </html>

    Continue reading...

Compartilhe esta Página