QWebEngineView:静音散景图例时“无法读取未定义的属性‘pageX’”

Posted

技术标签:

【中文标题】QWebEngineView:静音散景图例时“无法读取未定义的属性‘pageX’”【英文标题】:QWebEngineView: "cannot read property 'pageX' of undefined" when muting bokeh legend 【发布时间】:2017-08-29 17:05:07 【问题描述】:

我正在使用PyQt5 创建一个GUI,在这个GUI 中,我使用QWebEngineView 可视化Bokeh 图表。

它工作正常,但是当我尝试实现像 this 这样的“静音”图例时,我收到一个错误:

js: Uncaught TypeError: Cannot read property 'pageX' of undefined

如果我使用 show 方法,我会在浏览器中得到预期的结果。但是,如果我使用 save 并将其显示到 QWebEngineView 我会收到上述错误。

有什么想法吗?

Gui 类中要在 QWebEngineView 中绘制和显示的插槽:

注意:忽略 Bar 和 Pizza 的图,与此事相关的是散点和线

def plotGraph(self, df=None):
    # Get parameters to plot
    x = str(self.ui.comboBox_x_axis.currentText())
    y = str(self.ui.comboBox_y_axis.currentText())
    # Define axis types
    try:
        x_axis_type = str(
            self.ui.comboBox_plot_scale.currentText()).split('-')[0]
        y_axis_type = str(
            self.ui.comboBox_plot_scale.currentText()).split('-')[1]
    except:
        x_axis_type = 'auto'
        y_axis_type = 'auto'
    # Define kind of graph
    kind = str(self.ui.comboBox_plot_style.currentText())
    # For bar chart define groups
    group = str(self.ui.comboBox_group.currentText())
    # Prepare data for plot
    if (kind == 'bar' and group != "Don't group"):
        data = df[[x, y, group]]
    else:
        data = df[[x, y]]
        data = data.sort_values(x, axis=0)
    # Dynamically define plot size
    width = round(self.ui.webViewer.frameGeometry().width())
    height = round(self.ui.webViewer.frameGeometry().height())
    # Plot and save html
    self.plot = self.graph.plot(
        data, kind, x_axis_type, y_axis_type, width, height)
    self.plot_num = 1
    # Display it at QWebEngineView
    self.ui.webViewer.setUrl(QtCore.QUrl(
        "file:///C:/Users/eandrade_brp/Documents/git/tl-data-viewer/plot.html"))

这是处理所有散景图的 Graph 类(我省略了一些不必要的代码)

class Graph(object):
    """docstring for ClassName"""

    def __init__(self, file_name="plot.html"):
        super(Graph, self).__init__()
        output_file(file_name)

    def plot(self, data, kind, x_axis_type, y_axis_type, width, height):
        p = None
        if kind == 'scatter' or kind == 'line':
            layout, p = self.createFigure(
                data, kind, x_axis_type, y_axis_type, width, height)
        elif kind == 'bar':
            layout = self.plot_Bar(data, width, height)
        elif kind == 'pizza':
            layout = self.plot_Pizza(
                data, width, height)
        # Show/save
        save(layout)
        return p

    def createFigure(self, data, kind, x_axis_type, y_axis_type, width, height):
        source, xdata, ydata, xvalues, yvalues = self.prepare_data(data)
        # Define tool
        tools = "pan, box_zoom, lasso_select, undo, redo"
        wheel_zoom = WheelZoomTool()
        hover = HoverTool(
            tooltips=[
                (data.columns[0],          '$x'),
                (data.columns[1],          '$y')],
            mode='mouse')
        # Create first figure and customize
        fig1 = figure(title=" vs " .format(ydata, xdata), tools=tools,
                      x_axis_type=x_axis_type, y_axis_type=y_axis_type,
                      toolbar_location="right", plot_width=round(0.9 * width),
                      plot_height=round(0.75 * height))
        fig1.add_tools(wheel_zoom)
        fig1.add_tools(hover)
        fig1.toolbar.active_scroll = wheel_zoom
        fig1.background_fill_color = "beige"
        fig1.background_fill_alpha = 0.4

        # Create second figure and customize
        fig2 = figure(title='Overview', title_location="left",
                      x_axis_type=x_axis_type, y_axis_type=y_axis_type,
                      tools='', plot_width=round(0.9 * width), plot_height=round(0.25 * height))
        fig2.xaxis.major_tick_line_color = None
        fig2.xaxis.minor_tick_line_color = None
        fig2.yaxis.major_tick_line_color = None
        fig2.yaxis.minor_tick_line_color = None
        fig2.xaxis.major_label_text_color = None
        fig2.yaxis.major_label_text_color = None

        # Add View box to second figure
        rect = Rect(x='x', y='y', width='width', height='height', fill_alpha=0.1,
                    line_color='black', fill_color='black')
        fig2.add_glyph(source, rect)

        # Add JS callBacks
        self.JS_linkPlots(fig1, source)

        # Plots
        plots = self.plot_continuous(source, xvalues, yvalues, fig1, kind)
        self.plot_continuous(source, xvalues, yvalues, fig2, kind)
        s2 = ColumnDataSource(data=dict(ym=[0.5, 0.5]))
        fig1.line(x=[0, 1], y='ym', color="orange",
                  line_width=5, alpha=0.6, source=s2)

        # Add legends
        legend = Legend(items=[
            (ydata, plots)],
            location=(0, 0),
            click_policy="mute")
        # Add legend to fig layout
        fig1.add_layout(legend, 'below')
        # Layout
        layout = col(fig1, fig2)
        return layout, fig1

    def plot_continuous(self, source, xvalues, yvalues, fig, kind, color=0):
        if kind == 'scatter':
            s = fig.scatter(
                xvalues, yvalues,
                fill_color='white', fill_alpha=0.6,
                line_color=Spectral10[color], size=8,
                selection_color="firebrick",
                nonselection_fill_alpha=0.2,
                nonselection_fill_color="blue",
                nonselection_line_color="firebrick",
                nonselection_line_alpha=1.0)
            return [s]

        elif kind == 'line':
            l = fig.line(
                xvalues, yvalues, line_width=2, color=Spectral10[color], alpha=0.8,
                muted_color=Spectral10[color], muted_alpha=0.2)

            s = fig.scatter(
                xvalues, yvalues,
                fill_color="white", fill_alpha=0.6,
                line_color=Spectral10[color], size=8,
                selection_color="firebrick",
                nonselection_fill_alpha=0.2,
                nonselection_fill_color="blue",
                nonselection_line_color="firebrick",
                nonselection_line_alpha=1.0)
            return [s, l]
        else:
            raise 'Wrong type of plot'

    def prepare_data(self, data):
        xdata = data.columns[0]
        xvalues = data[xdata]
        ydata = data.columns[1]
        yvalues = data[ydata]
        source = ColumnDataSource(data)
        return source, xdata, ydata, xvalues, yvalues

【问题讨论】:

【参考方案1】:

首先,免责声明: Bokeh 没有声称可以完全或部分使用 Qt 浏览器小部件。我们根本没有能力在持续测试下严格地维持这一主张,因此我们无法做到。如果有人愿意作为该功能的维护者介入,那么我们将来可能会提出更强有力的支持声明。


Bokeh 使用第三方库 Hammer.js 跨不同平台提供统一的低级事件处理。 Bokeh 期望生成的事件具有pageXpageY 属性。似乎 Qt 的浏览器小部件不满足这种期望,导致您看到的错误。更新 Bokeh 使用的 Hammer 版本可能会解决问题。可能会引入一种解决方法。无论如何,这需要对 BokehJS 本身进行新的工作。

简短的回答是:这个交互式图例可能不适用于 Qt。作为一种解决方法,使用 Bokeh 小部件或 Qt 小部件来显示字形,不要依赖交互式图例功能。

长期:Wo 可以研究上面提出的一些想法。但是我们需要帮助才能做。 我们没有足够的带宽、能力或经验自行构建 Qt 应用程序来测试潜在的修复。 如果您有能力与核心开发人员一起寻找解决方案,请随时issue tracker 上的一个问题。

【讨论】:

以上是关于QWebEngineView:静音散景图例时“无法读取未定义的属性‘pageX’”的主要内容,如果未能解决你的问题,请参考以下文章

如何为控制多个散景图形添加一个图例?

散景:多折线图中的图外图例

散景:如何在图例中添加图例和非固定颜色边界?

在 QWebEngineView 中观看视频时无法使用全屏

PyQt5 QWebEngineView 无法正常工作

QWebEngineView 无法运行