Providing data for a multi line from a dataframe? Having trouble figuring out how to restructure the dataframe / use multi_line's syntax

Using @p-himik’s suggestion, I rejiggered the last code paste a little bit.

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.reset_index(drop=True, inplace=True)
df = df.astype('object')  # float columns were getting converted to Float64Arrays in the js callbacks, which have immutable lengths (push function not available)
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;
""")

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 = []
line_renderer_source_subsets = []
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].reset_index(drop=True))
                      , legend_label=str(cause))
    line_renderers.append(r)
    # for each line, maintain a subset that is filtered by option but not by ability; this is the "master" source
    # for that line, which will then be filtered by ability in the ability callback.
    line_renderer_source_subsets.append(ColumnDataSource(data=df[df.option == cause].reset_index(drop=True)))

fields_to_update = list(df.columns.values) + ['index']
ability_select.js_on_change('value', CustomJS(args=dict(source=source, heatmap_source=source,
                                                        line_renderers=line_renderers,
                                                        line_renderer_source_subsets=line_renderer_source_subsets,
                                                        ability_select=ability_select,
                                                        fields_to_update=fields_to_update), code="""
    // CDSView takes care of heatmap filtering by ability
    heatmap_source.change.emit();
    
    // filter lines by ability.
    // iterate over all lines:
    for (var m = 0; m < line_renderers.length; m++) {
        // clear out renderer's old data source
        line_renderers[m].data_source.clear();
        // iterate over all rows in 'master' data source for this line... 
        for (var n = 0; n < line_renderer_source_subsets[m].data.index.length; n++) {
            // save the ones matching the selected ability...
            if (line_renderer_source_subsets[m].data.ability[n] == ability_select.value) {
                // and for each field we're tracking, push the field/value combination to this line's data source
                for (var f in fields_to_update) {
                    var field = fields_to_update[f];
                    line_renderers[m].data_source.data[field].push(line_renderer_source_subsets[m].data[field][n]);
                }
            }
        }
        line_renderers[m].data_source.change.emit();
    }
"""))

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"))
4 Likes