Search code examples
pythonjupyter-notebookcallbackbokeh

Using bokeh 2.3.2 to plot interactive pie chart in Jupyter/Python


I am trying to create a pie chart in a jupyter notebook with Bokeh that can be updated with a slider. I have a custom function that creates data from a pre-existing dataframe. I would like the slider to manipulate input f to that function, such that data is different when displayed in the pie graph. Here is an example:

vals = [20, 100, 50]
names = ["Agnostic", "Competetive", "Loyalist"]

def data_generator(names, vals, f):
    data = pd.DataFrame([[name, val * f] for name, val in zip(names, vals)])
    return data

data = data_generator(names, vals, 10)

data['angle'] = data['value']/data['value'].sum() * 2*pi
data['color'] = Plasma[len(data)]


p = figure(plot_height=350, title="Loyalty Breakout", toolbar_location=None,
           tools="hover", tooltips="@segment: @value", x_range=(-0.5, 1.0))

p.wedge(x=0, y=1, radius=0.4,
        start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
        line_color="white", fill_color='color', legend_field='segment', source=data)

# This is where I need help
update = CustomJS(args=dict(xr=p.x_range), code="""

// I got this JS code from somewhere else. I need this to update the 'f' value in my input funciton.

// JavaScript code goes here

var a = 10;

// the model that triggered the callback is cb_obj:
var f = cb_obj.value;

// models passed as args are automagically available
xr.start = a;
xr.end = b;

""")

lookback_slider = Slider(start=0, end=180, value=30, step=1, title="Lookback (days)")
lookback_slider.js_on_change('value',update)

p.axis.axis_label=None
p.axis.visible=False
p.grid.grid_line_color = None

show(column(lookback_slider, p))

This creates the pie chart and slider, but the slider does not work. I found this: Using bokeh to plot interactive pie chart in Jupyter/Python but unfortunately, CustomJS.from_py_func(update) won't work because from_py_func has been deprecated in the newest version of Bokeh. Can anyone help me update a pie chart with Bokeh?


Solution

  • You need to implement your data_generator function as well as the angle calculation entirely in your JavaScript callback. It is not clear what you are trying to achieve with your code but here is some example JS callback implementation based on your code that changes the pie angle (tested with Bokeh v2.1.1):

    from re import I
    import pandas as pd
    from bokeh.plotting import show, figure, output_notebook
    from bokeh.models import CustomJS, Slider, Column
    from bokeh.palettes import Plasma
    from bokeh.transform import cumsum
    from math import pi
    output_notebook()
    
    vals = [20, 100, 50]
    names = ["Agnostic", "Competetive", "Loyalist"]
    
    def data_generator(names, vals, f):
        data = pd.DataFrame([[name, val * f] for name, val in zip(names, vals)], columns=['name', 'value'])
        return data
    
    f_start = 30
    data = data_generator(names, vals, f_start)
    
    data['angle'] = data['value']/data['value'].sum() * 2*pi # print(data.to_string())
    data['color'] = Plasma[len(data)]
    
    p = figure(plot_height=350, title="Loyalty Breakout", toolbar_location=None,
               tools="hover", tooltips="@segment: @value", x_range=(-0.5, 1.0))
    
    wedge = p.wedge(x=0, y=1, radius=0.4,
            start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
            line_color="white", fill_color='color', legend_field='name', source=data)
    
    update = CustomJS(args=dict(wedge=wedge, vals=vals, xr=p.x_range), code="""
        var f = cb_obj.value;
        //var scaled = Array.from(vals, (x) => x*f)
        var scaled = Array.from(vals, (x, i) => i==0?x*f/10:x)
        var sum = scaled.reduce((a, b) => a + b, 0)
        var angles =  Array.from(scaled, (x) => x/sum * 2 * Math.PI)
    
        wedge.data_source.data['angle'] = angles
        wedge.data_source.data['value'] = scaled
        wedge.data_source.change.emit()
    
        //xr.start = a;
        //xr.end = b;
    """)
    
    lookback_slider = Slider(start=0, end=180, value=f_start, step=1, title="Lookback (days)")
    lookback_slider.js_on_change('value',update)
    
    p.axis.axis_label=None
    p.axis.visible=False
    p.grid.grid_line_color = None
    
    show(Column(lookback_slider, p))