This docs on this page refers to a PREVIOUS VERSION. For the latest stable release, go to https://docs.bokeh.org/

Archived docs for versions <= 1.0.4 have had to be modified from their original published configuration, and may be missing some features (e.g. source listing)

All users are encourage to update to version 1.1 or later, as soon as they are able.

JavaScript Callbacks — Bokeh 0.12.1 documentation

JavaScript Callbacks

Bokeh exposes various callbacks that can be specified from Python that trigger actions inside the browser’s JavaScript runtime. This kind of JavaScript callback can be used to add interesting interactions to Bokeh documents without the need to use a Bokeh server (but can also be used in conjuction with a Bokeh server).

OpenURL

Opening an URL when users click on a glyph (for instance a circle marker) is a very popular feature. Bokeh lets users enable this feature by exposing an OpenURL callback object that can be passed to a Tap tool in order to have that action called whenever the users clicks on the glyph.

The following code shows how to use the OpenURL action combined with a TapTool to open an URL whenever the user clicks on a circle.

from bokeh.models import ColumnDataSource, OpenURL, TapTool
from bokeh.plotting import figure, output_file, show

output_file("openurl.html")

p = figure(plot_width=400, plot_height=400,
           tools="tap", title="Click the Dots")

source = ColumnDataSource(data=dict(
    x=[1, 2, 3, 4, 5],
    y=[2, 5, 8, 2, 7],
    color=["navy", "orange", "olive", "firebrick", "gold"]
    ))

p.circle('x', 'y', color='color', size=20, source=source)

url = "http://www.colors.commutercreative.com/@color/"
taptool = p.select(type=TapTool)
taptool.callback = OpenURL(url=url)

show(p)

                Now you have learned how to open an URL when the user clicks on a glyph.

                CustomJS for Widgets

                Bokeh lets you express even more advanced callbacks that must be called on the Javascript side in order to add custom logic and interactivity when a widget is used. For instance, we may want to change the data of a plot when a user clicks on a button or changes a slider Widget.

                Custom callbacks like these can be set using a CustomJS object and passing it as the callback argument to a Widget object.

                The code below shows an example of CustomJS set on a slider Widget that changes the source of a plot when the slider is used.

                from bokeh.layouts import column
                from bokeh.models import CustomJS, ColumnDataSource, Slider
                from bokeh.plotting import Figure, output_file, show
                
                output_file("callback.html")
                
                x = [x*0.005 for x in range(0, 200)]
                y = x
                
                source = ColumnDataSource(data=dict(x=x, y=y))
                
                plot = Figure(plot_width=400, plot_height=400)
                plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
                
                callback = CustomJS(args=dict(source=source), code="""
                        var data = source.get('data');
                        var f = cb_obj.get('value')
                        x = data['x']
                        y = data['y']
                        for (i = 0; i < x.length; i++) {
                            y[i] = Math.pow(x[i], f)
                        }
                        source.trigger('change');
                    """)
                
                slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=callback)
                
                layout = column(slider, plot)
                
                show(layout)
                

                        CustomJS for Tools

                        Bokeh allows for some tool events to trigger custom Javascript callbacks that have access to the tool’s attributes. Below, a callback on the BoxSelectTool uses the selection box dimensions (accessed in the geometry field of the cb_data object that is injected into the Callback code attribute), in order to add a Rect glyph to the plot with identical dimensions.

                        from bokeh.models import CustomJS, ColumnDataSource, BoxSelectTool, Range1d, Rect
                        from bokeh.plotting import figure, output_file, show
                        
                        output_file("boxselecttool_callback.html")
                        
                        source = ColumnDataSource(data=dict(x=[], y=[], width=[], height=[]))
                        
                        callback = CustomJS(args=dict(source=source), code="""
                                // get data source from Callback args
                                var data = source.get('data');
                        
                                /// get BoxSelectTool dimensions from cb_data parameter of Callback
                                var geometry = cb_data['geometry'];
                        
                                /// calculate Rect attributes
                                var width = geometry['x1'] - geometry['x0'];
                                var height = geometry['y1'] - geometry['y0'];
                                var x = geometry['x0'] + width/2;
                                var y = geometry['y0'] + height/2;
                        
                                /// update data source with new Rect attributes
                                data['x'].push(x);
                                data['y'].push(y);
                                data['width'].push(width);
                                data['height'].push(height);
                        
                                // trigger update of data source
                                source.trigger('change');
                            """)
                        
                        box_select = BoxSelectTool(callback=callback)
                        
                        p = figure(plot_width=400,
                                   plot_height=400,
                                   tools=[box_select],
                                   title="Select Below",
                                   x_range=Range1d(start=0.0, end=1.0),
                                   y_range=Range1d(start=0.0, end=1.0))
                        
                        rect = Rect(x='x',
                                    y='y',
                                    width='width',
                                    height='height',
                                    fill_alpha=0.3,
                                    fill_color='#009933')
                        
                        p.add_glyph(source, rect, selection_glyph=rect, nonselection_glyph=rect)
                        show(p)
                        

                                      CustomJS for Selections

                                      Bokeh also provides the means to specify the same kind of callback to be executed whenever a selection changes. As a simple demonstration, the example below simply copies selected points on the first plot to the second. However, more sophisticated actions and computations are easily constructed in a similar way.

                                      from random import random
                                      
                                      from bokeh.layouts import row
                                      from bokeh.models import CustomJS, ColumnDataSource
                                      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)]
                                      
                                      s1 = ColumnDataSource(data=dict(x=x, y=y))
                                      p1 = figure(plot_width=400, plot_height=400, tools="lasso_select", title="Select Here")
                                      p1.circle('x', 'y', source=s1, alpha=0.6)
                                      
                                      s2 = ColumnDataSource(data=dict(x=[], y=[]))
                                      p2 = figure(plot_width=400, plot_height=400, x_range=(0, 1), y_range=(0, 1),
                                                  tools="", title="Watch Here")
                                      p2.circle('x', 'y', source=s2, alpha=0.6)
                                      
                                      s1.callback = CustomJS(args=dict(s2=s2), code="""
                                              var inds = cb_obj.get('selected')['1d'].indices;
                                              var d1 = cb_obj.get('data');
                                              var d2 = s2.get('data');
                                              d2['x'] = []
                                              d2['y'] = []
                                              for (i = 0; i < inds.length; i++) {
                                                  d2['x'].push(d1['x'][inds[i]])
                                                  d2['y'].push(d1['y'][inds[i]])
                                              }
                                              s2.trigger('change');
                                          """)
                                      
                                      layout = row(p1, p2)
                                      
                                      show(layout)
                                      

                                                                    Another more sophisticated example is shown below. It computes the average y value of any selected points (including multiple disjoint selections), and draws a line through that value.

                                                                    from random import random
                                                                    from bokeh.models import CustomJS, ColumnDataSource
                                                                    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', 'y', color='color', size=8, source=s, alpha=0.4)
                                                                    
                                                                    s2 = ColumnDataSource(data=dict(ym=[0.5, 0.5]))
                                                                    p.line(x=[0, 1], y='ym', color="orange", line_width=5, alpha=0.6, source=s2)
                                                                    
                                                                    s.callback = CustomJS(args=dict(s2=s2), code="""
                                                                            var inds = cb_obj.get('selected')['1d'].indices;
                                                                            var d = cb_obj.get('data');
                                                                            var ym = 0
                                                                    
                                                                            if (inds.length == 0) { return; }
                                                                    
                                                                            for (i = 0; i < d['color'].length; i++) {
                                                                                d['color'][i] = "navy"
                                                                            }
                                                                            for (i = 0; i < inds.length; i++) {
                                                                                d['color'][inds[i]] = "firebrick"
                                                                                ym += d['y'][inds[i]]
                                                                            }
                                                                    
                                                                            ym /= inds.length
                                                                            s2.get('data')['ym'] = [ym, ym]
                                                                    
                                                                            cb_obj.trigger('change');
                                                                            s2.trigger('change');
                                                                        """)
                                                                    
                                                                    show(p)
                                                                    

                                                                                  CustomJS for Hover

                                                                                  The HoverTool has a callback which comes with two pieces of built-in data: the index, and the geometry. The index is the indices of any points that the hover tool is over.

                                                                                  from bokeh.plotting import figure, output_file, show
                                                                                  from bokeh.models import ColumnDataSource, HoverTool, CustomJS
                                                                                  
                                                                                  output_file("hover_callback.html")
                                                                                  
                                                                                  # define some points and a little graph between them
                                                                                  x = [2, 3, 5, 6, 8, 7]
                                                                                  y = [6, 4, 3, 8, 7, 5]
                                                                                  links = {
                                                                                      0: [1, 2],
                                                                                      1: [0, 3, 4],
                                                                                      2: [0, 5],
                                                                                      3: [1, 4],
                                                                                      4: [1, 3],
                                                                                      5: [2, 3, 4]
                                                                                  }
                                                                                  
                                                                                  p = figure(width=400, height=400, tools="", toolbar_location=None, title='Hover over points')
                                                                                  
                                                                                  source = ColumnDataSource({'x0': [], 'y0': [], 'x1': [], 'y1': []})
                                                                                  sr = p.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='olive', alpha=0.6, line_width=3, source=source, )
                                                                                  cr = p.circle(x, y, color='olive', size=30, alpha=0.4, hover_color='olive', hover_alpha=1.0)
                                                                                  
                                                                                  # Add a hover tool, that sets the link data for a hovered circle
                                                                                  code = """
                                                                                  var links = %s;
                                                                                  var data = {'x0': [], 'y0': [], 'x1': [], 'y1': []};
                                                                                  var cdata = circle.get('data');
                                                                                  var indices = cb_data.index['1d'].indices;
                                                                                  for (i=0; i < indices.length; i++) {
                                                                                      ind0 = indices[i]
                                                                                      for (j=0; j < links[ind0].length; j++) {
                                                                                          ind1 = links[ind0][j];
                                                                                          data['x0'].push(cdata.x[ind0]);
                                                                                          data['y0'].push(cdata.y[ind0]);
                                                                                          data['x1'].push(cdata.x[ind1]);
                                                                                          data['y1'].push(cdata.y[ind1]);
                                                                                      }
                                                                                  }
                                                                                  segment.set('data', data);
                                                                                  """ % links
                                                                                  
                                                                                  callback = CustomJS(args={'circle': cr.data_source, 'segment': sr.data_source}, code=code)
                                                                                  p.add_tools(HoverTool(tooltips=None, callback=callback, renderers=[cr]))
                                                                                  
                                                                                  show(p)
                                                                                  

                                                                                  CustomJS for Range Update

                                                                                  With Bokeh, ranges have a callback attribute that accept a Callback instance and execute javascript code on range updates that are triggered by tool interactions such as a box zoom, wheel scroll or pan.

                                                                                  import numpy as np
                                                                                  
                                                                                  from bokeh.layouts import row
                                                                                  from bokeh.models import ColumnDataSource, CustomJS, Rect
                                                                                  from bokeh.plotting import output_file, figure, show
                                                                                  
                                                                                  output_file('range_update_callback.html')
                                                                                  
                                                                                  N = 4000
                                                                                  
                                                                                  x = np.random.random(size=N) * 100
                                                                                  y = np.random.random(size=N) * 100
                                                                                  radii = np.random.random(size=N) * 1.5
                                                                                  colors = [
                                                                                      "#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y)
                                                                                  ]
                                                                                  
                                                                                  source = ColumnDataSource({'x': [], 'y': [], 'width': [], 'height': []})
                                                                                  
                                                                                  jscode="""
                                                                                      var data = source.get('data');
                                                                                      var start = cb_obj.get('start');
                                                                                      var end = cb_obj.get('end');
                                                                                      data['%s'] = [start + (end - start) / 2];
                                                                                      data['%s'] = [end - start];
                                                                                      source.trigger('change');
                                                                                  """
                                                                                  
                                                                                  p1 = figure(title='Pan and Zoom Here', x_range=(0, 100), y_range=(0, 100),
                                                                                              tools='box_zoom,wheel_zoom,pan,reset', plot_width=400, plot_height=400)
                                                                                  p1.scatter(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)
                                                                                  
                                                                                  p1.x_range.callback = CustomJS(
                                                                                          args=dict(source=source), code=jscode % ('x', 'width'))
                                                                                  p1.y_range.callback = CustomJS(
                                                                                          args=dict(source=source), code=jscode % ('y', 'height'))
                                                                                  
                                                                                  p2 = figure(title='See Zoom Window Here', x_range=(0, 100), y_range=(0, 100),
                                                                                              tools='', plot_width=400, plot_height=400)
                                                                                  p2.scatter(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)
                                                                                  rect = Rect(x='x', y='y', width='width', height='height', fill_alpha=0.1,
                                                                                              line_color='black', fill_color='black')
                                                                                  p2.add_glyph(source, rect)
                                                                                  
                                                                                  layout = row(p1, p2)
                                                                                  
                                                                                  show(layout)
                                                                                  

                                                                                                            CustomJS with a Python function

                                                                                                            A CustomJS callback can also be implemented as a Python function, which is then translated to JavaScript using PyScript. This makes it easier for users to define client-side interactions without having to learn JavaScript. To use this functionality you need the Flexx library (install with conda install -c bokeh flexx or pip install flexx).

                                                                                                            Warning

                                                                                                            It is critical to note that no python code is ever executed when a CustomJS callback is used. This is true even when the callback is supplied as python code to be translated to JavaScript as described in this section. A CustomJS callback is only executed inside a browser JavaScript interpreter, and can only directly interact JavaScript data and functions (e.g., BokehJS Backbone models).

                                                                                                            For more information about the subset of Python that is supported in callbacks, see the pyscript documentation.

                                                                                                            We recommend using window.x for variables specific to JavaScript to avoid confusion and help static code analysis tools. You can add window as an argument to the callback function to help readability (and pyflakes), as in the example below.

                                                                                                            from bokeh.layouts import column
                                                                                                            from bokeh.models import CustomJS, ColumnDataSource, Slider
                                                                                                            from bokeh.plotting import Figure, output_file, show
                                                                                                            
                                                                                                            output_file("callback.html")
                                                                                                            
                                                                                                            x = [x*0.005 for x in range(0, 200)]
                                                                                                            y = x
                                                                                                            
                                                                                                            source = ColumnDataSource(data=dict(x=x, y=y))
                                                                                                            
                                                                                                            plot = Figure(plot_width=400, plot_height=400)
                                                                                                            plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
                                                                                                            
                                                                                                            def callback(source=source, window=None):
                                                                                                                data = source.get('data')
                                                                                                                f = cb_obj.get('value')
                                                                                                                x, y = data['x'], data['y']
                                                                                                                for i in range(len(x)):
                                                                                                                    y[i] = window.Math.pow(x[i], f)
                                                                                                                source.trigger('change')
                                                                                                            
                                                                                                            slider = Slider(start=0.1, end=4, value=1, step=.1, title="power",
                                                                                                                            callback=CustomJS.from_py_func(callback))
                                                                                                            
                                                                                                            layout = column(slider, plot)
                                                                                                            
                                                                                                            show(layout)