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.

Bokeh Docs

Adding Interactions

Linking Plots

It’s often useful to link plots to add connected interactivity between plots. This section shows an easy way to do it using the bokeh.plotting interface.

Linked Panning

It’s often desired to link pan or zooming actions across many plots. All that is needed to enable this feature is to share range objects between figure() calls.

from bokeh.io import output_file, show
from bokeh.layouts import gridplot
from bokeh.plotting import figure

output_file("panning.html")

x = list(range(11))
y0 = x
y1 = [10-xx for xx in x]
y2 = [abs(xx-5) for xx in x]

# create a new plot
s1 = figure(width=250, plot_height=250, title=None)
s1.circle(x, y0, size=10, color="navy", alpha=0.5)

# create a new plot and share both ranges
s2 = figure(width=250, height=250, x_range=s1.x_range, y_range=s1.y_range, title=None)
s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)

# create a new plot and share only one range
s3 = figure(width=250, height=250, x_range=s1.x_range, title=None)
s3.square(x, y2, size=10, color="olive", alpha=0.5)

p = gridplot([[s1, s2, s3]], toolbar_location=None)

# show the results
show(p)

Now you have learned how to link panning between multiple plots with the bokeh.plotting interface.

Linked Brushing

Linked brushing in Bokeh is expressed by sharing data sources between glyph renderers. This is all Bokeh needs to understand that selections acted on one glyph must pass to all other glyphs that share that same source.

The following code shows an example of linked brushing between circle glyphs on two different figure() calls.

from bokeh.io import output_file, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure

output_file("brushing.html")

x = list(range(-20, 21))
y0 = [abs(xx) for xx in x]
y1 = [xx**2 for xx in x]

# create a column data source for the plots to share
source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1))

TOOLS = "box_select,lasso_select,help"

# create a new plot and add a renderer
left = figure(tools=TOOLS, width=300, height=300, title=None)
left.circle('x', 'y0', source=source)

# create another new plot and add a renderer
right = figure(tools=TOOLS, width=300, height=300, title=None)
right.circle('x', 'y1', source=source)

p = gridplot([[left, right]])

show(p)

              Now you have learned how to link brushing between plots.

              Adding Widgets

              Widgets are interactive controls that can be added to Bokeh applications to provide a front end user interface to a visualization. They can drive new computations, update plots, and connect to other programmatic functionality. When used with the Bokeh server, widgets can run arbitrary Python code, enabling complex applications. Widgets can also be used without the Bokeh server in standalone HTML documents through the browser’s Javascript runtime.

              To use widgets, you must add them to your document and define their functionality. Widgets can be added directly to the document root or nested inside a layout. There are two ways to program a widget’s functionality:

              • Use the CustomJS callback (see CustomJS for Widgets). This will work in standalone HTML documents.
              • Use bokeh serve to start the Bokeh server and set up event handlers with .on_change (or for some widgets, .on_click).

              Event handlers are user-defined Python functions that can be attached to widgets. These functions are then called when certain attributes on the widget are changed. The necessary function signature of event handlers is determined by how they are attached to widgets (whether they are passed through .on_change or .on_click).

              All widgets have an .on_change method that takes an attribute name and one or more event handlers as parameters. These handlers are expected to have the function signature, (attr, old, new), where attr refers to the changed attribute’s name, and old and new refer to the previous and updated values of the attribute. .on_change must be used when you need the previous value of an attribute.

              def my_text_input_handler(attr, old, new):
                  print("Previous label: " + old)
                  print("Updated label: " + new)
              
              text_input = TextInput(value="default", title="Label:")
              text_input.on_change("value", my_text_input_handler)
              

              Additionally, some widgets, including the button, dropdown, and checkbox, have an .on_click method that takes an event handler as its only parameter. For the Button, this handler is called without parameters. For the other widgets with .on_click, the handler is passed the new attribute value.

              def my_radio_handler(new):
                  print 'Radio button option ' + str(new) + ' selected.'
              
              radio_group = RadioGroup(
                  labels=["Option 1", "Option 2", "Option 3"], active=0)
              radio_group.on_click(my_radio_handler)
              

              Bokeh provides a simple default set of widgets, largely based off the Bootstrap JavaScript library. In the future, it will be possible for users to wrap and use other widget libraries, or their own custom widgets.

              For more information about the attributes to watch using .on_change or whether .on_click is available, go to the Reference Guide. Widgets can be found under bokeh.models.

              Button

              Bokeh provides a simple Button:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import Button
              
              output_file("button.html")
              
              button = Button(label="Foo", button_type="success")
              
              show(widgetbox(button))
              

              Checkbox Button Group

              Bokeh also provides a checkbox button group, that can have multiple options selected simultaneously:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import CheckboxButtonGroup
              
              output_file("checkbox_button_group.html")
              
              checkbox_button_group = CheckboxButtonGroup(
                      labels=["Option 1", "Option 2", "Option 3"], active=[0, 1])
              
              show(widgetbox(checkbox_button_group))
              

              Checkbox Group

              A standard checkbox:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import CheckboxGroup
              
              output_file("checkbox_group.html")
              
              checkbox_group = CheckboxGroup(
                      labels=["Option 1", "Option 2", "Option 3"], active=[0, 1])
              
              show(widgetbox(checkbox_group))
              

              Data Table

              Bokeh provides a sophisticated data table widget based on SlickGrid. Note that since the table is configured with a data source object, any plots that share this data source will automatically have selections linked between the plot and the table (even in static HTML documents).

              Date
              Downloads
              2014 Mar 1
              67
              2014 Mar 2
              41
              2014 Mar 3
              79
              2014 Mar 4
              79
              2014 Mar 5
              33
              2014 Mar 6
              82
              2014 Mar 7
              70
              2014 Mar 8
              80
              2014 Mar 9
              79
              2014 Mar 10
              72
              from datetime import date
              from random import randint
              
              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models import ColumnDataSource
              from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
              
              output_file("data_table.html")
              
              data = dict(
                      dates=[date(2014, 3, i+1) for i in range(10)],
                      downloads=[randint(0, 100) for i in range(10)],
                  )
              source = ColumnDataSource(data)
              
              columns = [
                      TableColumn(field="dates", title="Date", formatter=DateFormatter()),
                      TableColumn(field="downloads", title="Downloads"),
                  ]
              data_table = DataTable(source=source, columns=columns, width=400, height=280)
              
              show(widgetbox(data_table))
              

              MultiSelect

              A multi-select widget to present multiple available options:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import MultiSelect
              
              output_file("multi_select.html")
              
              multi_select = MultiSelect(title="Option:", value=["foo", "quux"],
                                         options=["foo", "bar", "baz", "quux"])
              
              show(widgetbox(multi_select))
              

              Radio Button Group

              A radio button group can have at most one selected button at at time:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import RadioButtonGroup
              
              output_file("radio_button_group.html")
              
              radio_button_group = RadioButtonGroup(
                      labels=["Option 1", "Option 2", "Option 3"], active=0)
              
              show(widgetbox(radio_button_group))
              

              Radio Group

              A radio group uses standard radio button appearance:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import RadioGroup
              
              output_file("radio_group.html")
              
              radio_group = RadioGroup(
                      labels=["Option 1", "Option 2", "Option 3"], active=0)
              
              show(widgetbox(radio_group))
              

              Select

              A single selection widget:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import Select
              
              output_file("select.html")
              
              select = Select(title="Option:", value="foo", options=["foo", "bar", "baz", "quux"])
              
              show(widgetbox(select))
              

              Slider

              The Bokeh slider can be configured with start and end values, a step size, an initial value and a title:

              from bokeh.io import output_file, show
              from bokeh.layouts import widgetbox
              from bokeh.models.widgets import Slider
              
              output_file("slider.html")
              
              slider = Slider(start=0, end=10, value=1, step=.1, title="Stuff")
              
              show(widgetbox(slider))
              

              Tab Panes

              Tab panes allow multiple plots or layouts to be show in selectable tabs:

                              from bokeh.models.widgets import Panel, Tabs
                              from bokeh.io import output_file, show
                              from bokeh.plotting import figure
                              
                              output_file("slider.html")
                              
                              p1 = figure(plot_width=300, plot_height=300)
                              p1.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
                              tab1 = Panel(child=p1, title="circle")
                              
                              p2 = figure(plot_width=300, plot_height=300)
                              p2.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=3, color="navy", alpha=0.5)
                              tab2 = Panel(child=p2, title="line")
                              
                              tabs = Tabs(tabs=[ tab1, tab2 ])
                              
                              show(tabs)
                              

                              TextInput

                              A widget for collecting a line of text from a user:

                              from bokeh.io import output_file, show
                              from bokeh.layouts import widgetbox
                              from bokeh.models.widgets import TextInput
                              
                              output_file("text_input.html")
                              
                              text_input = TextInput(value="default", title="Label:")
                              
                              show(widgetbox(text_input))
                              

                              Toggle Button

                              The toggle button holds an on/off state:

                              from bokeh.io import output_file, show
                              from bokeh.layouts import widgetbox
                              from bokeh.models.widgets import Toggle
                              
                              output_file("toggle.html")
                              
                              toggle = Toggle(label="Foo", button_type="success")
                              
                              show(widgetbox(toggle))
                              

                              Div

                              A widget for displaying text that can support HTML in a <div> tag:

                              Your HTML-supported text is initialized with the text argument. The remaining div arguments are width and height. For this example, those values are 200 and 100 respectively.
                              from bokeh.io import output_file, show
                              from bokeh.layouts import widgetbox
                              from bokeh.models.widgets import Div
                              
                              output_file("div.html")
                              
                              div = Div(text="""Your <a href="https://en.wikipedia.org/wiki/HTML">HTML</a>-supported text is initialized with the <b>text</b> argument.  The
                              remaining div arguments are <b>width</b> and <b>height</b>. For this example, those values
                              are <i>200</i> and <i>100</i> respectively.""",
                              width=200, height=100)
                              
                              show(widgetbox(div))
                              

                              Paragraph

                              A widget for displaying a block of text in an HTML <p> tag:

                              Your text is initialized with the 'text' argument. The remaining Paragraph arguments are 'width' and 'height'. For this example, those values are 200 and 100 respectively.

                              from bokeh.io import output_file, show
                              from bokeh.layouts import widgetbox
                              from bokeh.models.widgets import Paragraph
                              
                              output_file("div.html")
                              
                              p = Paragraph(text="""Your text is initialized with the 'text' argument.  The
                              remaining Paragraph arguments are 'width' and 'height'. For this example, those values
                              are 200 and 100 respectively.""",
                              width=200, height=100)
                              
                              show(widgetbox(p))
                              

                              PreText

                              A widget for displaying a block of pre-formatted text in an HTML <pre> tag:

                              Your text is initialized with the 'text' argument.
                              
                              The remaining Paragraph arguments are 'width' and 'height'. For this example,
                              those values are 500 and 100 respectively.
                              from bokeh.io import output_file, show
                              from bokeh.layouts import widgetbox
                              from bokeh.models.widgets import PreText
                              
                              output_file("div.html")
                              
                              pre = PreText(text="""Your text is initialized with the 'text' argument.
                              
                              The remaining Paragraph arguments are 'width' and 'height'. For this example,
                              those values are 500 and 100 respectively.""",
                              width=500, height=100)
                              
                              show(widgetbox(pre))
                              

                              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)