pythonbokehbokehjs

Update text dynamically in Bokeh with js_on_change


Is it possible to use js_on_change with bokeh to dynamically update text that is placed next to a plot?

For example, using this code snippet from a different question


from random import random
from bokeh.models import CustomJS, ColumnDataSource, Span
from bokeh.plotting import figure, output_file, show

output_file("callback.html")

x = [random() for x in range(500)]
y = [random() for y in range(500)]
color = ["navy"] * len(x)

s = ColumnDataSource(data=dict(x=x, y=y, color=color))
p = figure(plot_width=400,
           plot_height=400,
           tools="lasso_select",
           title="Select Here")
p.circle(x='x', y='y', color='color', size=8, source=s, alpha=0.4)

slope = Span(location=.5,
             dimension="width",
             line_alpha=.6,
             line_width=5)
p.add_layout(slope)

s.selected.js_on_change(
    'indices',
    CustomJS(args=dict(s=s, slope=slope),
             code="""       
        var inds = cb_obj.indices;
        
        if (inds.length == 0) {
            slope.location = 0.5
            return 
        }
        
        var total = 0;
        for (var i = 0; i < inds.length; i++) {
            total += s.data["y"][inds[i]]
        }
        var avg = total / inds.length;
        slope.location = avg;
    """))

show(p)

I'd like to include a text right of the figure that shows the value of the computed slope.location and updates whenever I select new points.


Solution

  • You can Use a Text Widget like PreText, place it on the right side of the plot e.g via layout and update the .text property of that widget in your JSCallback. Make sure you use the .toString() methode to assign the value.

    from random import random
    from bokeh.models import CustomJS, ColumnDataSource, Span, PreText
    from bokeh.plotting import figure, output_file, show
    from bokeh.layouts import layout
    
    output_file("callback.html")
    
    x = [random() for x in range(500)]
    y = [random() for y in range(500)]
    color = ["navy"] * len(x)
    
    s = ColumnDataSource(data=dict(x=x, y=y, color=color))
    p = figure(plot_width=400,
               plot_height=400,
               tools="lasso_select",
               title="Select Here")
    p.circle(x='x', y='y', color='color', size=8, source=s, alpha=0.4)
    
    slope = Span(location=.5,
                 dimension="width",
                 line_alpha=.6,
                 line_width=5)
    p.add_layout(slope)
    
    slope_text = PreText(text='Slope_Text')
    
    s.selected.js_on_change(
        'indices',
        CustomJS(args=dict(s=s, slope=slope, slope_text=slope_text),
                 code="""       
            var inds = cb_obj.indices;
            
            if (inds.length == 0) {
                slope.location = 0.5
                return 
            }
            
            var total = 0;
            for (var i = 0; i < inds.length; i++) {
                total += s.data["y"][inds[i]]
            }
            var avg = total / inds.length;
            slope.location = avg;
            slope_text.text = avg.toString();
        """))
    
    layout_ = layout([[p,slope_text]])
    show(layout_)