mojovski
mojovski

Reputation: 601

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?

Upvotes: 0

Views: 82

Answers (1)

mosc9575
mosc9575

Reputation: 6337

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

Upvotes: 1

Related Questions