Search code examples
pythoncallbackbokeh

Python Bokeh JSCallback, filtering line graph based on Select


I am having some trouble with writing the code in the JSCallback function in Python Bokeh.

Specifically, I am trying to create a line graph that will show only the data that is stated in the dropdown box. I have to use the JSCallback function as server-app options are disabled at work.

For example, if I have a dataset with for different series (A, B, C, D). I would like to make it so that selecting "A" in a select box will show only that series in a line graph, "B" to show B etc. It is the equivalent of me filtering the dataset (for example, using a loc function in Pandas) before graphing the data. I have tried my best to extrapolate from the the documentation but to not avail. Here is a sample script. I know the "var z == f" is incorrect, but I don't know how to filter a datatable in the JS Callback code. Thank you in advance.

import pandas as pd
from bokeh.io import show
from bokeh.layouts import column, widgetbox
from bokeh.models import ColumnDataSource, CustomJS, Select
from bokeh.models.widgets import Dropdown
from bokeh.plotting import figure


df = pd.DataFrame(data = {"A": [1,2,3,4,5],
                          "B": [6,7,8,9,10],
                          "C": [11,12,13,14,15],
                          "D": [16,17,18,19,20],
                          "day":[100,101,102,103,104]})

df = df.melt(id_vars = ["day"], var_name = "var",  value_name = "val")
source = ColumnDataSource(dict (val = df["val"],
                                day = df["day"]))


dropdown = Select(title ="Variable", value = "A", options = ["A","B","C","D"])



p = figure(plot_height = 300, plot_width = 800)                 
p.line("day", "val", source= source)


callback = CustomJS(args = dict(source=  source), code = """

data = source.data;
var f = cb.obj_value
var z = data["variable"]
var z == f

source.change.emit()


""")


dropdown.js_on_change("value", callback)

combined= column(p, widgetbox(dropdown))

show(combined)

Solution

  • If you want to transform or filter something, don't change data sources. There are views, filters, and transforms in Bokeh for that.

    But since you're using the line glyph, using a filter will issue a warning because line is a connected glyph. What you can do is to avoid melting the dataframe and just switch the column:

    import pandas as pd
    from bokeh.layouts import column
    from bokeh.models import ColumnDataSource, CustomJS, Select
    from bokeh.plotting import figure, show
    
    df = pd.DataFrame(data={"A": [1, 2, 3, 4, 5],
                            "B": [6, 7, 8, 9, 10],
                            "C": [11, 12, 13, 14, 15],
                            "D": [16, 17, 18, 19, 20],
                            "day": [100, 101, 102, 103, 104]})
    
    source = ColumnDataSource({c: v.values for c, v in df.items()})
    
    initial_value = "A"
    dropdown = Select(title="Variable", value=initial_value, options=["A", "B", "C", "D"])
    
    p = figure(plot_height=300, plot_width=800)
    l = p.line("day", initial_value, source=source)
    
    dropdown.js_on_change("value", CustomJS(args=dict(line=l),
                                            code="line.glyph.y = {field: cb_obj.value};"))
    
    show(column(p, dropdown))