Search code examples
pythonbokeh

Bokeh: Why do I need to call the source.change.emit() to update the location of a Span?


I want to highlight the sample over which I´m hovering. I did managed to solve this with a custom JSCallback. See the minimal working example below.
My question is, why do I need to call source.change.emit() in order to update the location of my Span? If I remove the source.change.emit() in my callback, the location of the horizontal line does not change at all (visually). So far I thought, I only need to call this command, if I actually change the data inside the source object (like source.data['Value1'] = <new_value>.

import pandas as pd
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, HoverTool, CustomJS, Span
from bokeh.layouts import layout

plot1 = figure(plot_width=1000, plot_height=250)

df = pd.DataFrame({"ID":[0, 1, 2, 3, 4, 5, 6, 7], 
                   "Value1":[0, 100, 200, 300, 400, 500, 600, 700], 
                   "Value2":[0, 1, 2, 4,8 , 16, 32, 64]})
source = ColumnDataSource(df)

line = plot1.line(x='ID', y='Value1', source=source)
circle = plot1.circle(x='ID', y='Value1', source=source)

v_line = Span(location=2,  dimension='height', line_color='green',
                              line_dash='dashed', line_width=3)
plot1.add_layout(v_line)

callback = CustomJS(args=dict(source=source, v_line=v_line), 
                        code="""
                        v_line.location = cb_data['index'].line_indices[0];
                        source.change.emit();
                        """)

hover_tool_plot = HoverTool(mode='vline', line_policy='nearest', callback=callback, renderers=[line])

plot1.add_tools(hover_tool_plot)

layout_ = layout([[plot1]])
curdoc().add_root(layout_)

In my application, I deal with a lot of data in multiple plots having the same source object. So when I include the source.change.emit() part in the callback the Span loacation changes, but the visual hover annoation gets noticeably slower and also skips some samples because of this non-smoothness. Is there a way to update the location of the Span without calling source.change.emit()?
Thanks in advance.

Edit:
I'm running on Bokeh 2.3.0.


Solution

  • If you move outside the begin/end of the line, the location then line_indices is empty, and setting from line_indices[0] is undefined. In this case things stop updating because undefined is not a valid thing to set location to. Perhaps this is what you are seeing?

    This version of the code updates and keeps the tooltip and span in sync without an emit call with Bokeh 2.3 (note: please always, always specify relevant software versions in every question)

    callback = CustomJS(args=dict(source=source, v_line=v_line),
                            code="""
                            const loc = cb_data['index'].line_indices[0]
                            if (loc === undefined) {
                                 v_line.visible = false
                            } else {
                                v_line.visible = true
                                v_line.location = loc;
                            }
                            """)
    
    hover_tool_plot = HoverTool(mode='vline', line_policy='prev', 
                                callback=callback, renderers=[line])
    

    Note I have also changed line_policy to prev because I believe that is what line_indices will always report, regardless. It's possible this could be made more flexible, but no one has ever asked about it before. Feel free to propose a feature request on github.