Please do not open another topic for the same issue.
Unfortunately your code still cannot be easily tested.
A complete code starts with the import section and contains all that is needed to run it (ideally with self contained reduced data), only keep what is necessary to reproduce the error.
The code usually ends with show(layout)
, or curdoc().add_root(layout)
if there are Python callbacks.
That way, the reader can run the code quickly and the probability you get an answer is higher.
Also, I would recommend to use more specific title to your topic, for instance:
“CustomJS callbacks to modify plot source”.
If I had to solve your problem, I would write the following code:
import pandas as pd
from math import pi
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import CustomJS, Slider, HoverTool, ColumnDataSource
from bokeh.layouts import column
from bokeh.transform import cumsum
import colorcet
# Provide some data (is not the same as in your example):
data = {"Year": [1990, 1991, 1992],
"Meningitis": [2159, 1592, 3914],
"Alzheimer": [1116, 842, 534],
"Parkinson": [371, 2376, 1675],
"Malaria": [93, 189, 231]
}
df = pd.DataFrame(data).set_index("Year")
df_transposed = df.transpose()
# Create one data frame per year and save them to a list of data frames.
# I find it easier to keep data frames at first, because operations can
# be applied at a higher level, so with less code:
year_dfs = []
for year in df_transposed.columns:
year_df = df_transposed[year].to_frame(name="Deaths")
year_df.index.name = "Disease"
year_df["Percent"] = 100 * year_df["Deaths"]/year_df["Deaths"].sum()
year_df["angle"] = year_df["Deaths"]/year_df["Deaths"].sum() * 2 * pi
# Compute start and end angle for each disease, so that the first disease starts
# at the top of the pie and the next ones appear in clockwise direction:
year_df["start_angle"] = pi/2 - (year_df["angle"].cumsum() - year_df["angle"])
year_df["end_angle"] = pi/2 - year_df["angle"].cumsum()
# Set the color column. Feel free to use other palettes:
year_df['color'] = list(map(lambda x: colorcet.b_glasbey_category10[x], range(len(year_df))))
year_dfs.append(year_df)
# Convert the dataframes to dictionaries in the ColumnDataSource data format to hand over to Javascript:
year_dicts = []
for df in year_dfs:
year_dicts.append(dict(ColumnDataSource(df).data))
# Setup pie chart:
height = 550
width = 650
plot = figure(height=height, width=width, title="Diseases", x_range=(-0.6, 1.0))
plot_source = ColumnDataSource(year_dicts[0])
plot.wedge(x=0, y=1, radius=0.6, source=plot_source,
start_angle="start_angle", end_angle="end_angle", direction="clock",
line_color="white", legend_field='Disease', fill_color='color')
# Setup hover inspection:
hover = HoverTool()
hover.tooltips = [("Disease", "@{Disease}"),
("Deaths", "@Deaths"),
("Percent", "@Percent{f0.0} %")]
plot.add_tools(hover)
plot.axis.axis_label = None
plot.axis.visible = False
plot.grid.grid_line_color = None
plot.outline_line_color = None
plot.title.text_font_size = '16pt'
plot.toolbar.active_drag = None
plot.toolbar_location = None
# Setup slider and callback:
slider = Slider(start=1990, end=1992, value=1990, step=1, title="Select year")
callback = CustomJS(args=dict(plot_source=plot_source, sources=year_dicts), code="""
const year_select = cb_obj.value
const source_list = sources
plot_source.data = source_list[year_select - 1990]
""")
slider.js_on_change('value', callback)
layout = column(slider, plot)
show(layout)
In this code, I hand over a list of all the ColumnDataSource
data dictionaries to Javascript, so Javascript just has to pick the correct one and assign it to the source of the plot.
There are surely other ways to obtain the same behavior.
You could also have look here:
How to change data columns in CustomJS callback
Also, keep in mind that with CustomJS, all the data is contained in the html file.
With my code, all disease deaths for all years, together with the percents, angles and colors. Here, lightweighting could start with letting Javascript compute percents, angles and colors only for the selected year.
But if you have huge data, CustomJS ist not the best solution. An alternative could be the use of Python callbacks, which require the use of a Bokeh server to update the changes to the app.