You can use CustomJSTransform
to do whatever transforms you want on the x, top and bottom values in vbar to accomplish this. It might look a bit complicated but the three transforms follow the same structure/idea: the transform depends on whether profit is positive or negative.
For the x field → we want to shift the bar left or right 0.15 dependent on profit value
For the top field → we want to set the top to the OPPOSITE top value (e.g. top = rev if profit is negative because the bar will be plotted on top of exp)
For the bottom field → we want to set the bottom to the top value (e.g. bottom = exp if profit is negative because the bar will be plotted on top of exp)
Also note that you don’t need a net field at all in the CDS: it’s calculated in the transforms.
> from bokeh.transform import dodge, transform
> from bokeh.models import ColumnDataSource, CustomJSTransform
> from bokeh.plotting import figure, save
>
>
>
> src = ColumnDataSource(data={'Item':['A','B'],'Rev':[10,11],'Exp':[6,12]})
> p = figure(x_range=src.data['Item'])
>
> p.vbar(x=dodge('Item',-0.15,range=p.x_range),bottom=0,top='Rev',width=0.3,source=src,fill_color='green',fill_alpha=0.5)
> p.vbar(x=dodge('Item',0.15,range=p.x_range),bottom=0,top='Exp',width=0.3,source=src,fill_color='red',fill_alpha=0.5)
>
> #set up three transforms
>
> #x transform: for every record in source, if profit is positive, shift forward 0.15, otherwise shift back 0.15
> #only "trick" is that internally, when you assign a categorical x_range, category A is an x = 0.5, category B = x of 1.5, etc.
> trX = CustomJSTransform(args=dict(src=src)
> ,v_func='''
> var result = []
> for (var i = 0; i < src.data['Item'].length; i++){
> var prof = src.data['Rev'][i]-src.data['Exp'][i]
> if (prof>0){
> result.push(i+0.5+0.15)
> }
> else {
> result.push(i+0.5-0.15)
> }
> }
> return result
> '''
> )
> #top transform: for every record in source, if profit is positive, then set top = Rev, otherwise set top to Exp
> trTop = CustomJSTransform(args=dict(src=src)
> ,v_func='''
> var result = []
> for (var i = 0; i < src.data['Item'].length; i++){
> var prof = src.data['Rev'][i]-src.data['Exp'][i]
> if (prof>0){
> result.push(src.data['Rev'][i])
> }
> else {
> result.push(src.data['Exp'][i])
> }
> }
> return result
> '''
> )
> #bot transform is basically the opposite of top--->
> # for every record in source, if profit is positive, then set bot = Exp, otherwise set bot to Rev
> trBot = CustomJSTransform(args=dict(src=src)
> ,v_func='''
> var result = []
> for (var i = 0; i < src.data['Item'].length; i++){
> var prof = src.data['Rev'][i]-src.data['Exp'][i]
> if (prof>0){
> result.push(src.data['Exp'][i])
> }
> else {
> result.push(src.data['Rev'][i])
> }
> }
> return result
> '''
> )
>
> p.vbar(x=transform(field_name='Item',transform=trX)
> ,top=transform(field_name='Item',transform=trTop)
> ,bottom=transform(field_name='Item',transform=trBot)
> ,width=0.3,source=src,fill_alpha=0.5)
>
> save(p,r'E:\Personal\test.html')