I'm using PyQt5
to create a GUI
and in this GUI
I visualize Bokeh
graphs using QWebEngineView
.
It works fine but when I tried to implement the "muting" legend like this I get an error:
js: Uncaught TypeError: Cannot read property 'pageX' of undefined
If I use the show method, I get the expecting result in my browser. However if I use save and display it to the QWebEngineView
I get the mentioned error.
Any ideas?
The slot in my Gui
Class to plot and show in the QWebEngineView
:
Notes: Ignore the Bar and Pizza plots, it is the scatter and line that is relevant to this matter
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"))
Here is the Graph class that handles all the bokeh plots (I omitted some non necessary code)
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
First, a disclaimer: Bokeh makes no claim to function, either fully or partially, with Qt browser widgets. We are simply not equipped to be able to maintain that claim rigorously under continuous testing, therefore we cannot make it. If anyone would ever like to step in as a maintainer of that functionality, it's possible in the future that we can make stronger support claims.
Bokeh uses a third party library Hammer.js to provide uniform low-level event handling across different platforms. Bokeh expects that the events that are generated have a
pageX
andpageY
attributes. It appears that Qt's browser widget does not satisfy this expectation, leading to the error you are seeing. It's possible that updating the version of Hammer used by Bokeh might fix the problem. It's possible that a workaround could be introduced. In any case, it would require new work on BokehJS itself.The short answer is: this interacive legend probably just is not going to work on Qt. As a workaround, use Bokeh widgets or Qt Widgets to high and show glyphs, and do not rely on the interactive legend capability.
Longer term: Wo could look into some of the ideas suggested above. But we would need assistance to do do. We do not have the bandwidth, ability, or experience to build Qt apps ourselves to test potential fixes. If you have the ability to work together with a core dev on finding a solution, please feel free to make an issue on the issue tracker.