'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]
- there is no need to code this all yourself. Higher level API https://github.com/PatrikHlobil/Pandas-Bokeh
- code below shows this in action
- have assumed how your data frames look and geometry sourcing. This code follows
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>",
)
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 |

