pythonbokeh

bokeh vbar_stack: positive values on positive side, negative values on negative side


Example program

import bokeh.colors
import pandas as pd
from bokeh.plotting import figure, show
import bokeh.models
import bokeh.palettes
import bokeh.transform

data = {
    'date': ['2024-04-15', '2024-04-16', '2024-04-17' ],
    'Bill': [1,  1, -1],
    'Note': [1, -1, -1],
    'Bond': [1,  0, -1]
}

df = pd.DataFrame(data)

df['date'] = pd.to_datetime(df['date'])

df.set_index('date', inplace=True)

p = figure(sizing_mode='stretch_both', x_axis_type='datetime', x_axis_label='date', y_axis_label='change')

p.vbar_stack(stackers=['Bill', 'Note', 'Bond'], x='date', width=pd.Timedelta(days=0.9), color=bokeh.palettes.Category20[3], source=df, legend_label=['Bill', 'Note', 'Bond'])

p.legend.click_policy = 'hide'

show(p)

Output

enter image description here

Notes

In the first column, all three values are positive, so they stack upwards as expected.

In the third column, all three values are negative, so they stack downwards as expected.

In the middle column, we have 1 positive value, so it stacks upward, then we have one negative value, so it then stacks downward. The result is, we end up with the appearance of a stack of height 1.

Question

Is there a way to have positive values only stack up on the positive side of the y-axis and for negative values to only show up on the negative side of the y-axis?

In other words, the chart would look as follows:

enter image description here


Solution

  • The trick is to split the DataFrame in a positiv and a negative one and draw two stackers.

    Here is the code:

    p = figure(x_axis_type='datetime', x_axis_label='date', y_axis_label='change')
    
    p.vbar_stack(
        stackers=['Bill', 'Note', 'Bond'],
        x='date', width=pd.Timedelta(days=0.9),
        color=bokeh.palettes.Category20[3],
        source=df[df<0].fillna(0),
        legend_label=['Bill', 'Note', 'Bond'],
        line_width=0,
    )
    p.vbar_stack(
        stackers=['Bill', 'Note', 'Bond'],
        x='date', width=pd.Timedelta(days=0.9),
        color=bokeh.palettes.Category20[3],
        source=df[df>0].fillna(0),
        legend_label=['Bill', 'Note', 'Bond'],
        line_width=0,
    )
    p.legend.click_policy = 'hide'
    
    show(p)
    

    It is important to fill the selection with zeros to avoid an addtition with np.nan. I also added line_width=0, to avoid thin orange boxes on both sides of the bar.

    The result looks like below:

    stacker with respect of the sign