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

Question:

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?

Asked By: mojovski

||

Answers:

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 some 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

Answered By: mosc9575
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.