Search code examples
pythonbokeh

bokehjs: computing a new plot automatically as sum of all activated plots


Having 3 line plots in Bokehjs, I would like Bokeh to show a fourth one, which is the sum of the other 3. Example:

y1=[1,2,3,4,5]
y2=[4,5,6,7,8]
y3=[1,8,2,6,4]

Automatically generated plot would be:

y_all = [6,15,11,17,17]

Is there a way to accomplish this? Maybe with a js callback?


Solution

  • I am not sure what you want, so I start with a very basic approche.

    I assume you can use pandas. And your given DataFrame is this:

    import pandas as pd
    from bokeh.plotting import figure, show, output_notebook
    output_notebook()
    
    df = pd.DataFrame({
        'y1':[1,2,3,4,5],
        'y2':[4,5,6,7,8],
        'y3':[1,8,2,6,4],
    })
    

    Static solution

    With pandas.DataFrame.sum() you can create the sum and then you can use multi_line from bokeh.

    df['y_all'] = df.sum(axis=1)
    p = figure(width=300, height=300)
    p.multi_line(
        xs=[df.index]*4, ys=list(df.values.T), color=['red', 'green','blue', 'black']
    )
    show(p)
    

    multi_line plot

    Interactive solution

    Because you mentioned JS, I created an interactive solution. This solution is based on this post.

    Here the sum is calculated on the fly by the selection given by the active CheckBoxes.

    import pandas as pd
    from bokeh.models import CheckboxGroup, CustomJS, ColumnDataSource
    from bokeh.layouts import row
    from bokeh.plotting import figure, show, output_notebook
    output_notebook()
    
    df = pd.DataFrame({
        'y1':[1,2,3,4,5],
        'y2':[4,5,6,7,8],
        'y3':[1,8,2,6,4],
    })
    
    df['y_all'] = df.sum(axis=1)
    source = ColumnDataSource(df)
    
    colors = ['red', 'green','blue', 'black']
    
    p = figure(width=300, height=300)
    
    line_renderer = []
    names = list(df.columns)
    for name, color in zip(names, colors):
        line_renderer.append(
            p.line(
                x = 'index',
                y = name,
                color=color,
                name =name,
                source=source
            )
        )
    checkbox = CheckboxGroup(labels=names, active=list(range(len(names))), width=100)
    callback = CustomJS(args=dict(lines=line_renderer,checkbox=checkbox, source=source),
        code="""
        const data = source.data;
        for (let i = 0; i < data['y_all'].length; i++) {
            data['y_all'][i] = 0
        }
    
        for(var j=0; j<lines.length; j++){
            lines[j].visible = checkbox.active.includes(j);
        }
    
        console.log(data)
        console.log(checkbox)
        for(var item of checkbox.active){
            let next_y = lines[item]["properties"]["name"]["spec"]["value"]
            if (next_y != 'y_all'){
                for (let i = 0; i < data[next_y].length; i++) {
                    data['y_all'][i] += data[next_y][i]
                }
            }
        }
        source.change.emit();
        """
    )
    checkbox.js_on_change('active', callback)
    layout = row(p,checkbox)
    show(layout)
    

    interacitve sum