Alright, I’ve tried figuring out how to rebuild the data with JS from something @carolyn kindly helped me with a few months ago, and I think I’ve almost got it, but can’t quite figure out how to implement it.
In the for loop where we’re making the lines I’m unsure how to pass the source as subset_source
while also keeping the data=df[df.option==cause]
condition:
import pandas as pd
from bokeh.models import LinearColorMapper, ColumnDataSource, Slider, Select, CustomJSFilter, CDSView, CustomJS, GroupFilter
from bokeh.layouts import row, column, layout
from bokeh.plotting import figure, output_file, show
from bokeh.palettes import Viridis256, Category20_20
df = pd.read_excel('https://cjdixon.s3-ap-southeast-2.amazonaws.com/bokeh/heatmap_linegraph_datademo.xlsx', index_col=0)
df = df.reset_index()
df = df.rename(columns={n: str(n) for n in range(1,11)})
output_file('heatmap_linegraph.html', title='whatever', mode='inline')
df['period'] = df['period'].astype(str)
periods = df.period.unique().tolist()
causes = df.option.unique().tolist()
abilities = df.ability.unique().tolist()
df['active_column'] = df['5']
source = ColumnDataSource(data=df)
active = 5
color_mapper = LinearColorMapper(palette=Viridis256,
low=0,
high=0.02)
year_select = Slider(value=active, start=1, end=10, step=1)
ability_select = Select(value='noob', options=['l33t', 'noob'])
ability_filter = CustomJSFilter(args=dict(ability_select=ability_select), code='''
var indices = []
for (var i = 0; i < source.get_length(); i++){
if (source.data['ability'][i] == ability_select.value){
indices.push(true);
} else {
indices.push(false);
}
}
return indices;
''')
subset_source = ColumnDataSource(data=source.data.copy())
fields_to_update = list(df.columns.values)
ability_callback = CustomJS(args=dict(source=source, subset_source=subset_source, ability_select=ability_select, fields_to_update=fields_to_update), code="""
subset_source.clear();
for (i = 0; i < source.data.x.length; i++) {
if (source.data.ability[i] == ability_select.value) {
for (j=0; j < fields_to_update.length; j++) {
subset_source.data[fields_to_update[j]].push(source.data[fields_to_update[j]][i]);
}
}
}'
subset_source.change.emit();
""")
view = CDSView(source=source, filters=[ability_filter])
heatmap = figure(x_range=periods, y_range=causes,
x_axis_location="above", sizing_mode="stretch_both")
heatmap_renderer = heatmap.rect(x="period", y="option", width=1, height=0.95,
source=source, view=view,
fill_color={'field': str(active), 'transform': color_mapper},
line_color=None, name=str(active))
line_fig = figure(sizing_mode="stretch_both", y_range = (0, .03)
, x_range = (0, 60))
line_renderers = []
for cause, color in zip(causes, Category20_20):
r = line_fig.line(x='period'
,y=str(active)
,color=color
,line_width=3
,source= ColumnDataSource(data=df[df.option==cause])
,legend_label=str(cause))
line_renderers.append(r)
ability_select.js_on_change('value', ability_callback)
year_select.js_on_change('value', CustomJS(args=dict(heatmap_renderer=heatmap_renderer, p=heatmap, year_select=year_select, source=source, ability_select=ability_select, line_fig=line_fig, line_renderers=line_renderers), code="""\
const active = cb_obj.value;
const data = heatmap_renderer.data_source.data[active];
heatmap_renderer.name = String(active);
const {transform} = heatmap_renderer.glyph.fill_color;
heatmap_renderer.glyph.fill_color = {field: cb_obj.value, transform: transform};
for (const lr of line_renderers) {
lr.glyph.y = {field: active};
}
source.data['active_column'] = source.data[year_select.value]
source.change.emit()
line_fig.reset.emit()
"""))
top_area = row(year_select, ability_select)
show(layout(column([top_area, heatmap, line_fig]), sizing_mode="stretch_both"))
Any ideas? We’re so close!