title: Slicing data in Bokeh callback to produce dashboard-like interactions
body: <p>I am trying to produce a dashboard like interactions for my bar chart using callback function without using <code>bokeh serve</code> functionality. Ultimately, I would like to be able to change the plot if any of the two drop-down menus is changed. So far this only works when threshold value is hard-coded. I only know how to extract <code>cb_obj</code> value but not from dropdown that is not actually called.  I have looked at <a href="https://stackoverflow.com/questions/43231896/changing-source-of-plot-in-bokeh-with-a-callback">this</a> and <a href="https://stackoverflow.com/questions/44064031/bokeh-widget-slicing-data">this</a> answer to formulate first attempt.</p>
  
  <p>Here is my code:</p>
  
  <pre><code>from bokeh.io import show, output_notebook, output_file
  from bokeh.models import ColumnDataSource, Whisker
  from bokeh.plotting import figure
  from bokeh.transform import factor_cmap
  from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
  from bokeh.layouts import column
  
  import numpy as np
  import pandas as pd
  
  
  def generate_data(factor=10):
      rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
      idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
                                 [20,999]],names=["Experiment","Threshold"])
      rawdata.index = idx
  
      return rawdata.reset_index()
  
  # Generate data
  output_notebook()
  count_data = generate_data()
  error_data = generate_data(factor=2)
  groups = ["A","B","C","D"]
  initial_counts = count_data[(count_data.Experiment == "Exp 0") 
                              &amp; (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
  initial_errors = error_data[(error_data.Experiment == "Exp 0") 
                              &amp; (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
  
  # Create primary sources of data
  count_source = ColumnDataSource(data=count_data)
  error_source = ColumnDataSource(data=error_data)
  
  # Create plotting source of data
  source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts, 
                                      upper=initial_counts+initial_errors, 
                                      lower=initial_counts-initial_errors))
  
  # Bar chart and figure
  p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
  p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
         line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
                                                    factors=groups))
  
  # Error bars 
  p.add_layout(
      Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
  )
  
  def callback(source=source, count_source = count_source, error_source=error_source, window=None):
  
      def slicer(data_source, experiment, threshold, dummy_col, columns):
          """ Helper function to enable lookup of data."""
          count = 0
          for row in data_source[dummy_col]:
              if (data_source["Experiment"][count] == experiment) &amp; (data_source["Threshold"][count] == threshold):
                  result = [data_source[col][count] for col in columns]
  
              count+=1
  
          return result
  
      # Initialise data sources
      data = source.data
      count_data = count_source.data
      error_data = error_source.data
  
      # Initialise values
      experiment = cb_obj.value
      threshold = 20
      counts, upper, lower = data["counts"], data["upper"], data["lower"]
  
      tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
      temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
  
      # Select values and emit changes
      for i in range(len(counts)):
          counts[i] = tempdata[i]
  
      for i in range(len(counts)):
          upper[i] = counts[i]+temperror[i]
          lower[i] = counts[i]-temperror[i]
  
      source.change.emit()
  
  
  exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))    
  thr_dropdown = Select(title="Select:", value="12", options=list(count_data.Threshold.astype(str).unique()))    
  
  exp_dropdown.callback = CustomJS.from_py_func(callback)
  
  
  p.xgrid.grid_line_color = None
  p.legend.orientation = "horizontal"
  p.legend.location = "top_center"
  
  layout = column(exp_dropdown,thr_dropdown, p)
  
  show(layout)
  </code></pre>