Using CustomJS in Django

Hello,
I am trying to run Bokeh visualizations inside a Django framework.
The goal is for the user to import a csv file, which, when uploaded, renders an html with bokeh plots that I have specified.
I have successfully built the views and connected the urls properly, and my plot renders after the csv is uploaded. The problem is, my python code includes Select Tool and a Slider callbacks, which do not modify my plots as the plots render on standalone HTML/JS.

Bokeh documentation recommends using the CustomJS model and gives a few examples: JavaScript callbacks — Bokeh 2.4.2 Documentation

but I am fairly new to Bokeh and Django and am unsure how my specific callback code needs to be modified with CustomJS. Here is the relevant code snippet:

def csv(request):
if request.method == ‘POST’:
excel_file = request.FILES[‘excel_file’]
excel = pd.read_csv(excel_file)
excel_df = pd.DataFrame(excel)
excel_df_avg = excel_df.groupby(‘Year’).agg(‘mean’)
excel_df_avg = excel_df_avg.reset_index()

    source1 = ColumnDataSource(data = {
        'x' : excel_df_avg['Year'],
        'top' : excel_df_avg['Residential_Rate'],
        })

    ymin1,ymax1 = min(excel_df_avg.Residential_Rate), max(excel_df_avg.Residential_Rate)
    xmin1,xmax1 = min(excel_df_avg.Year),max(excel_df_avg.Year)

    plot1 = figure(title='Galena Rate Data',plot_height=400, plot_width=700)

    plot1.vbar(x='x',top='top',width=0.5,source=source1)

    plot1.xaxis.axis_label = 'Year'
    plot1.yaxis.axis_label = 'Residential Rate ($/kWh)'

    def update_plot1(attr,old,new):
        y1 = y1_select.value
        new1_data = {
         'x' : excel_df_avg['Year'],
         'top' : excel_df_avg[y1]
        }
        source1.data = new1_data
        plot1.yaxis.axis_label = y1

    y1_select = Select(
        options=['Residential_Rate','PCE Rate ($/kWh)','Effective Rate ($/kWh)','Fuel Price ($/gal)','Diesel Efficiency (kWh/gal)'],
        value='Residential_Rate',
        title='Y-Axis Options'
    )

    y1_select.on_change('value', update_plot1)

@ikturchaninov

I am not sure about the Django aspect of your issue or if any others exist given the excerpt of code provided. However, the following points might be relevant.

Your function update_plot1() is a Python function not Javascript code. And the following line is registering it as a Python callback

So while it is consistent to register a Python function as a Python callback, an implication is that you need to be running bokeh as a server for it to work.

If you’re trying to use JavaScript and its callbacks, see the similar callback registration method js_on_change().

I do not have any issues with the django aspect of this project. The problem is as you mentioned: python callbacks are not registered in a standalone HTML file. This requires a JS callback which is achieved using the CustomJS() function imported from bokeh.models.callbacks.

My only issue is I have never coded in JS, and thus am unaware of how to convert the python callback I created in update_plot1() into a JS callback that is readable by CustomJS().

Creating a CustomJS() object is required to use the registration method js_on_change().

I am aware that another solution to this issue is to run a bokeh server within my django app, but creating a dynamic html file using CustomJS() seems like a much simpler option.

@ikturchaninov

I see. I misread that you were wondering why the code excerpt provided in the example did not work and perhaps you were thinking you were running with a JS callback when that is not the case.

The following is a basic example to help illustrate. It covers the functional requirements you need but leaves the implementation details to how you want to modify the data in your specific application to you.

Two key points, use the js_on_change() method to register a callback for your Select widget.

The CustomJS() function that defines your callback takes two arguments of interest, an args argument which is a Python dictionary of things that need to be accessible in the code to implement whatever changes need to happen and the Javascript code.

EXAMPLE

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""
from bokeh.plotting import figure, show
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, Select

x = [0, 1, 2]
y = [0, 0, 0]

data = dict(x=x, y=y)
source = ColumnDataSource(data=data)

p = figure(width=400, height=300, y_range=(-10,100))
r = p.circle(x='x', y='y', source=source)

cb = CustomJS(args=dict(source=source), code="""
    var data = source.data;
    var y = data['y'];
    var f = cb_obj.value;
    
    // Do something... here just replace last element with cb_obj.value
    y[y.length-1] = parseInt(f, 10);

    // Emit data changes
    source.change.emit()
""")

s = Select(options=['0','10','20','30','40','50','60','70'],
           value='0',
           title='Replace Last Point')

s.js_on_change('value', cb)

show(row(p,s))