1

I have been mainly working in VS code to create a bokeh dashboard and I now need to get it to run within a Jupyter notebook. I know that some transformations in the code are required to push the code to a Jupyter notebook and for it to update interactively with widgets.

I have referred to this documentation:-

https://docs.bokeh.org/en/latest/docs/user_guide/jupyter.html#userguide-jupyter-notebook

But it is either too simplistic for my code, or that I have not used the push_notebook commands properly (or both).

Here is the code that I am trying to run in the notebook:-

################################### Code chunk 1##########################

from ipywidgets import interact
import pandas as pd
import numpy as np
import math
from bokeh.models import HoverTool
from bokeh.io import curdoc, output_notebook, push_notebook
from bokeh.plotting import figure, ColumnDataSource
from bokeh.layouts import layout, row, column, gridplot
from bokeh.models.widgets import RangeSlider
#https://discourse.bokeh.org/t/interactive-histograms-not-updating-with-sliders/3779/25

#clustering packages
from operator import index
from bokeh.models.widgets.markups import Div
import numpy as np
from numpy.lib import source
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column, row, gridplot, Column, Row
from bokeh.models import ColumnDataSource, Select, Slider, BoxSelectTool, LassoSelectTool, Tabs, Panel, LinearColorMapper, ColorBar, BasicTicker, PrintfTickFormatter, MultiSelect, DataTable, TableColumn
from bokeh.plotting import figure, curdoc, show
from bokeh.palettes import viridis, gray, cividis, Category20
from bokeh.transform import factor_cmap
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import classification_report, confusion_matrix, mean_squared_error, r2_score, recall_score, f1_score
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.cluster import KMeans
from sklearn.svm import SVC
from sklearn.decomposition import PCA


#tables
from time import time

output_notebook()

################################# Code chunk 2 ###########################################

#define the categorical variable
category_a = ['A','B','C']
category_b = ['X','Y','Z']
print("step 2")
df = pd.DataFrame({
   'id': np.arange(0, 100),
   'date': pd.date_range(start='1/1/2021', periods=100, freq='D'),
   'month':np.random.randint(1, 12, 100),
   'sensor_1': np.random.uniform(0, 1,100),
   'sensor_2': np.random.uniform(10, 150, 100),
   'sensor_3': np.random.randint(0, 90, 100),
   'sensor_4': np.random.randint(0, 450, 100),
   'sensor_5': np.random.randint(0, 352, 100),
   'categorya': np.random.choice(category_a, 100, p=[0.2, 0.4, 0.4]),
   'categoryb': np.random.choice(category_b, 100, p=[0.6, 0.2, 0.2]),
})

source = ColumnDataSource(data=df)

################################### Code chunk 3##############################

class hist_data:
    def __init__(self, df, col, n_bins, bin_range):
        self.sensor1_lwr = min(df['sensor_1'])#duration millisecond
        self.sensor1_upr = max(df['sensor_1'])#duration millisecond
        self.sensor2_lwr = min(df['sensor_2'])#count watch
        self.sensor2_upr = max(df['sensor_2'])#count_watch
        self.sensor3_lwr = min(df['sensor_3'])#count idle
        self.sensor3_upr = max(df['sensor_3'])#count idle
        self.sensor4_lwr = min(df['sensor_4'])#count inter and watch
        self.sensor4_upr = max(df['sensor_4'])#count inter and watch
        self.sensor5_lwr = min(df['sensor_5'])#count inter
        self.sensor5_upr = max(df['sensor_5'])#count inter
        self.col = col
        self.n_bins = n_bins
        self.bin_range = bin_range
        self.original_df = df
        self.source = ColumnDataSource(self.create_hist_data(df))
    
    def filt_df(self):
        filt = (pd.DataFrame(self.original_df[(self.original_df.sensor_1 >=self.sensor1_lwr) & 
                (self.original_df.sensor_1 <= self.sensor1_upr) & 
                (self.original_df.sensor_2 >= self.sensor2_lwr) & 
                (self.original_df.sensor_2 <= self.sensor2_upr) &
                (self.original_df.sensor_3 >= self.sensor3_lwr) & 
                (self.original_df.sensor_3 <= self.sensor3_upr) &
                (self.original_df.sensor_4 >= self.sensor4_lwr) &
                (self.original_df.sensor_4 <= self.sensor4_upr) &
                (self.original_df.sensor_5 >= self.sensor5_lwr) &
                (self.original_df.sensor_5 <= self.sensor5_upr)]))        
        print(f'{self.sensor1_lwr} {self.sensor1_upr} {self.sensor2_lwr} {self.sensor2_upr} {self.sensor3_lwr} {self.sensor3_upr} {self.sensor4_lwr} {self.sensor4_upr} {self.sensor5_lwr} {self.sensor5_upr}')
        filt.shape
        return ColumnDataSource(self.create_hist_data(filt))

    def create_hist_data(self,df):
        arr_hist, edges = np.histogram(df[self.col],bins=self.n_bins, range=self.bin_range)
        arr_df = pd.DataFrame({'count': arr_hist, 'left': edges[:-1], 'right': edges[1:]})
        arr_df['f_count'] = ['%d' % count for count in arr_df['count']]
        arr_df['f_interval'] = ['%d to %d ' % (left, right) for left, right in zip(arr_df['left'], arr_df['right'])]        
        return (arr_df)
        

df = df

########################histograms
hist_data_A = hist_data(df,'sensor_1',math.floor(math.sqrt(df['sensor_1'].nunique())),[min(df['sensor_1']),max(df['sensor_1'])])
hist_data_B = hist_data(df,'sensor_2',math.floor(math.sqrt(df['sensor_2'].nunique())),[min(df['sensor_2']),max(df['sensor_2'])])
hist_data_C = hist_data(df,'sensor_3',math.floor(math.sqrt(df['sensor_3'].nunique())),[min(df['sensor_3']),max(df['sensor_3'])])
hist_data_D = hist_data(df,'sensor_4',math.floor(math.sqrt(df['sensor_4'].nunique())),[min(df['sensor_4']),max(df['sensor_4'])])
hist_data_E = hist_data(df,'sensor_5',math.floor(math.sqrt(df['sensor_5'].nunique())),[min(df['sensor_5']),max(df['sensor_5'])])

############################slider
A_Slider= RangeSlider(start=min(df['sensor_1']), end=max(df['sensor_1']), value=(min(df['sensor_1']),max(df['sensor_1'])), step=1, title='sensor_1')
B_Slider = RangeSlider(start=min(df['sensor_2']), end=max(df['sensor_2']), value=(min(df['sensor_2']),max(df['sensor_2'])), step=1, title='sensor_2')
C_Slider = RangeSlider(start=min(df['sensor_3']), end=max(df['sensor_3']), value=(min(df['sensor_3']),max(df['sensor_3'])), step=1, title='sensor_3')
D_Slider = RangeSlider(start=min(df['sensor_4']), end=max(df['sensor_4']), value=(min(df['sensor_4']),max(df['sensor_4'])), step=1, title='sensor_4')
E_Slider = RangeSlider(start=min(df['sensor_5']), end=max(df['sensor_5']), value=(min(df['sensor_5']),max(df['sensor_5'])), step=1, title='sensor_5')

def callback_A(attr,new,old):
    hist_data_A.sensor1_lwr = new[0]
    hist_data_A.sensor1_upr = new[1]
    hist_data_A.source = hist_data_A.filt_df()
    Graphs1.children[0] = plot_data_A()
    push_notebook(handle=grid)

def callback_B(attr,new,old):
    hist_data_B.sensor2_lwr = new[0]
    hist_data_B.sensor2_upr = new[1]
    hist_data_B.source = hist_data_B.filt_df()
    Graphs1.children[1] = plot_data_B()
    push_notebook(handle=grid)

def callback_C(attr,new,old):
    hist_data_C.sensor3_lwr = new[0]
    hist_data_C.sensor3_upr = new[1]
    hist_data_C.source = hist_data_C.filt_df()
    Graphs1.children[2] = plot_data_C()
    push_notebook(handle=grid)

def callback_D(attr,new,old):
    hist_data_D.sensor4_lwr = new[0]
    hist_data_D.sensor4_upr = new[1]
    hist_data_D.source = hist_data_D.filt_df()
    Graphs2.children[0] = plot_data_D()
    push_notebook(handle=grid)


def callback_E(attr,new,old):
    hist_data_E.sensor5_lwr = new[0]
    hist_data_E.sensor5_upr = new[1]
    hist_data_E.source = hist_data_E.filt_df()
    Graphs2.children[1] = plot_data_E()
    push_notebook(handle=grid)

A_Slider.on_change("value",callback_A)
B_Slider.on_change("value",callback_B)
C_Slider.on_change("value",callback_C)
D_Slider.on_change("value",callback_D)
E_Slider.on_change("value",callback_E)

(df,'sensor_1',df['sensor_1'].nunique(),[min(df['sensor_1']),max(df['sensor_1'])])
(df,'sensor_2',df['sensor_2'].nunique(),[min(df['sensor_2']),max(df['sensor_2'])])
(df,'sensor_3',df['sensor_3'].nunique(),[min(df['sensor_3']),max(df['sensor_3'])])
(df,'sensor_4',df['sensor_4'].nunique(),[min(df['sensor_4']),max(df['sensor_4'])])
(df,'sensor_5',df['sensor_5'].nunique(),[min(df['sensor_5']),max(df['sensor_5'])])

# Histogram
def interactive_histogram( hist_data, title,x_axis_label,x_tooltip):
     source = hist_data
     # Set up the figure same as before
     toollist = ['lasso_select', 'tap', 'reset', 'save','crosshair','wheel_zoom','pan','hover','box_select']
     p = figure(plot_width = 400, 
                plot_height = 400,
                title = title,
                x_axis_label = x_axis_label, 
                y_axis_label = 'Count',tools=toollist)
     
     # Add a quad glyph with source this time
     p.quad(bottom=0, 
            top='count', 
            left='left', 
            right='right', 
            source=source,
            fill_color='red',
            hover_fill_alpha=0.7,
            hover_fill_color='blue',
            line_color='black')
     
     # Add style to the plot
     p.title.align = 'center'
     p.title.text_font_size = '18pt'
     p.xaxis.axis_label_text_font_size = '12pt'
     p.xaxis.major_label_text_font_size = '12pt'
     p.yaxis.axis_label_text_font_size = '12pt'
     p.yaxis.major_label_text_font_size = '12pt'
     
     # Add a hover tool referring to the formatted columns
     hover = HoverTool(tooltips = [(x_tooltip, '@f_interval'),
                                   ('Count', '@f_count')])
     
     # Add the hover tool to the graph
     p.add_tools(hover)
     return p
     push_notebook(handle=grid)
 
#binsize = 10
binzise = 100
def plot_data_A():
    A_hist = interactive_histogram(hist_data_A.source, 'sensor_1','sensor_1','sensor_1')
    return A_hist

def plot_data_B():
    B_hist = interactive_histogram(hist_data_B.source, 'sensor_2','sensor_2','sensor_2')
    return B_hist
     #

def plot_data_C():
    C_hist = interactive_histogram(hist_data_C.source, 'sensor_3','sensor_3','sensor_3')
    return C_hist
     #

def plot_data_D():
    D_hist = interactive_histogram(hist_data_D.source, 'sensor_4','sensor_4','sensor_4')
    return D_hist
     #

def plot_data_E():
    E_hist = interactive_histogram(hist_data_E.source, 'sensor_5','sensor_5','sensor_5')
    return E_hist
     #

Graphs1 = row([plot_data_A(), plot_data_B(), plot_data_C()])
Graphs2 = row([plot_data_D(), plot_data_E()])

Controls1= column([A_Slider,B_Slider,C_Slider,D_Slider,E_Slider])
#grid = gridplot([[Graphs1],
#                 [Controls1]])
grid = gridplot([[Controls1,Graphs1],[None,Graphs2]])

show(grid)

Now, it brings up the plots:- enter image description here

But the widgets do not update the plots. Can someone kindly show me what I am missing?

metaltoaster
  • 380
  • 2
  • 15
  • Do you use CustomJS? As far as I know you need to work in TypeScript to select new data from the ColumnDataSource. Please read [this post](https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-model-property-events) and try the example in your notebook. – mosc9575 Oct 04 '21 at 09:49
  • 1
    As far as I know bokeh itself does not support full bidirectional communication in Jupyter, but you can easily embed it in a Panel.holoviz.org layout, and Panel does support that. – James A. Bednar Oct 04 '21 at 13:37
  • 2
    It certainly does, but only if you embed an actual Bokeh server application, which requires structuring the notebook code in a specific way. See https://github.com/bokeh/bokeh/blob/branch-3.0/examples/howto/server_embed/notebook_embed.ipynb – bigreddot Oct 04 '21 at 17:03
  • @bigreddot many thanks for this. I can see the logic behind embedding now. I was wondering if there was a way to deploy to a browser window from a jupyter notebook? If this is possible, could you recommend any relevant materials that would illustrate this? Thanks again! – metaltoaster Oct 05 '21 at 11:03
  • I think @bigreddot and I have different expectations of Jupyter notebooks. :-) Embedding the server lets you put a functioning app in a notebook cell, but with Panel I'm used to being able to integrate the app functionality entirely into Jupyter, with widgets and plots across different cells as needed while still having full bidirectional coms. Hopefully embedding the app is all you need here! – James A. Bednar Oct 15 '21 at 14:46

0 Answers0