'JavaSript callback for month slider in bokeh map

I am running into some issues with a bokeh map I am creating, I need to update the map with a javascript callback. I've searched stackoverflow far and wide, but nothing I try works. The callback needs to change the month I specified on the slider in the existing source. Can I just call the json_data(a) function with an updated value for a with the slider or do I tackle the problem some other way? Code is as follows:

shapefile = '/content/ne_110m_admin_0_countries.shp'
codes = '/content/country-codes_csv.csv'
gdf = gpd.read_file(shapefile)[['ADMIN', 'ADM0_A3', 'geometry']]
countries = pd.read_csv(codes)[['ISO3166-1-Alpha-3', 'ISO3166-1-Alpha-2']]
gdf.columns = ['country', 'country_code', 'geometry']
gdf.head()
sales_df['Month'] = sales_df['Transaction Date'].apply(lambda x: x.month)
sum_df = sales_df.groupby(['Month','Buyer Country']).sum().reset_index()

merged_gdf = gdf.merge(countries, left_on='country_code', right_on='ISO3166-1-Alpha-3', how = 'inner')


def json_data(a):
  month_df = sum_df[sum_df['Month'] == a]
  merged = merged_gdf.merge(month_df, left_on = 'ISO3166-1-Alpha-2', right_on = 'Buyer Country', how = 'left')
  merged.fillna('No data', inplace = True)
  merged_json = json.loads(merged.to_json())
  json_data = json.dumps(merged_json)
  return json_data

geosource = GeoJSONDataSource(geojson = json_data(7))
palette = brewer['YlGnBu'][8]
palette = palette[::-1]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = 40, nan_color = '#d9d9d9')
tick_labels = {'0': '0%', '5': '5%', '10':'10%', '15':'15%', '20':'20%', '25':'25%', '30':'30%','35':'35%', '40': '>40%'}
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
                     border_line_color=None,location = (0,0), orientation = 'horizontal', major_label_overrides = tick_labels)

p = figure(title = 'Amount per country', plot_height = 600 , plot_width = 950, toolbar_location = None)
p.patches('xs','ys', source = geosource,fill_color = {'field' :'Amount', 'transform' : color_mapper},
          line_color = 'black', line_width = 0.25, fill_alpha = 1)
p.add_tools(HoverTool(tooltips=[('Country/region','@country'),('Amount bought', '@Amount')]))
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.add_layout(color_bar, 'below')

slider = Slider(title = 'Month',start = 6, end = 12, step = 1, value = 6)

callback = CustomJS(args=dict(slider = slider, source=geosource), code="""
    const a = slider.value;
    json_data(a);
    ??????????
    source.change.emit();
""")
slider.js_on_change('value', callback)



layout = column(p, widgetbox(slider))
show(layout)

Result of the code is not interactive with only month data I specified for the original geosource



Solution 1:[1]

solution

# bokeh generates a lot of shapely deprecation warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")

    month_gdf.plot_bokeh(
        slider=[c for c in month_gdf.columns if c[0] == "m"],
        slider_name="month ",
        colorbar_tick_format="0.0",
        colormap="YlGnBu",
        hovertool_string="<h3>@country</h3><pre>@Colormap{0.0}</pre>",
    )

enter image description here

data sourcing, generation and preparation

from pathlib import Path
import requests
import geopandas as gpd
import pandas as pd
import numpy as np
import pandas_bokeh

pandas_bokeh.output_notebook()
import warnings

# get geometry from github
f = Path.cwd().joinpath("content")
if not f.is_dir():
    f.mkdir()
f = f.joinpath("ne_110m_admin_0_countries.shp")
if not f.is_file():
    for ext in [".cpg", ".dbf", ".prj", ".shp", ".shx"]:
        r = requests.get(
            f"https://github.com/nvkelso/natural-earth-vector/raw/master/110m_cultural/ne_110m_admin_0_countries{ext}",
            stream=True,
        )
        with open(f.parent.joinpath(f"{f.stem}{ext}"), "wb") as fd:
            for chunk in r.iter_content(chunk_size=128):
                fd.write(chunk)
shapefile = f
codes = "https://raw.githubusercontent.com/datasets/country-codes/master/data/country-codes.csv"

gdf = gpd.read_file(shapefile)[["ADMIN", "ADM0_A3", "SUBREGION", "geometry"]]
countries = pd.read_csv(codes)[["ISO3166-1-Alpha-3", "ISO3166-1-Alpha-2"]]
gdf.columns = ["country", "country_code", "subregion", "geometry"]


merged_gdf = gdf.merge(
    countries, left_on="country_code", right_on="ISO3166-1-Alpha-3", how="inner"
)

# synthesize sales dataframe
sales_df = pd.DataFrame(
    {
        "Transaction Date": np.repeat(
            pd.date_range("1-mar-2021", freq="W-MON", periods=100), 100
        ),
        "Buyer Country": np.random.choice(
            merged_gdf.loc[
                lambda d: d["subregion"].isin(
                    ["Northern Europe", "Western Europe", "Southern Europe"]
                ),
                "ISO3166-1-Alpha-2",
            ],
            10000,
        ),
        "Amount": np.random.uniform(20, 100, 10000),
    }
)

sales_df["Month"] = sales_df["Transaction Date"].apply(lambda x: x.month)
sum_df = sales_df.groupby(["Month", "Buyer Country"]).sum().reset_index()

# restructure so months are columns
sum_df = (
    sum_df.set_index(["Month", "Buyer Country"])
    .unstack("Month")
    .droplevel(0, 1)
    .reset_index()
)

month_gdf = merged_gdf.merge(
    sum_df, right_on="Buyer Country", left_on="ISO3166-1-Alpha-2"
)

# geojson and numeric column names don't work well together...
month_gdf = month_gdf.rename(
    columns={c: f"m{c}" for c in month_gdf.columns if isinstance(c, int)}
)


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rob Raymond