I am aiming in the below code to make a stacked bar chart with bokeh, appended with sliders so I can increase or decrease the size of each bar segment and shift the others in turn.
My issue right now is that it will not update when running from a bokeh server. My guess is maybe bokeh does not run the calculations again after updating the source... Or I am getting a source conflict. (So far I have only implemented it for "Engineering". Wanted to get that to work before I sort the rest out.
Other things of note. I am using a depreciated technique of providing each glyph with bottom / top data as well as a source. This was done as it was the only way I could get the hovertool to show.
The only way I have got this to work was to redraw the graph completely, I would be ok with this option, but it was stacking the graphs on top of each other. Is there a way to clear all previous graphs in Bokeh? Obviously I would prefer a solution which just alters the data and doesn't completely redraw the graph.
from bokeh.plotting import figure, show, curdoc
from bokeh.models import NumeralTickFormatter
from bokeh.models import HoverTool
from bokeh.models import ColumnDataSource
from bokeh.layouts import widgetbox, column
from bokeh.models import CustomJS, Slider
from matplotlib import colors
import pandas as pd
import numpy as np
# Read Data
df=pd.read_csv('/home/mint/SAGD_Costs.csv')
# Master source
source = ColumnDataSource(df)
# Bar Tops Data
engtop = source.data['Engineering'][0]
equiptop = source.data['Engineering'][0] + source.data['Equipment'][0]
bulktop = source.data['Engineering'][0] + source.data['Equipment'][0] + source.data['Bulk_Materials'][0]
inditop = source.data['Engineering'][0] + source.data['Equipment'][0] + source.data['Bulk_Materials'][0] + source.data['Indirects'][0]
labtop = source.data['Engineering'][0] + source.data['Equipment'][0] + source.data['Bulk_Materials'][0] + source.data['Indirects'][0] + source.data['Labour'][0]
# Source for Stupid Hovertool
engsource = ColumnDataSource(data=dict(x=[0], y=[engtop], desc = ['Engineering']))
equipsource = ColumnDataSource(data=dict(x=[0], y=[equiptop-engtop], desc = ['Equipment']))
bulksource = ColumnDataSource(data=dict(x=[0], y=[bulktop-equiptop], desc = ['Bulk Materials']))
indisource = ColumnDataSource(data=dict(x=[0], y=[inditop-bulktop], desc = ['Indirects']))
labsource = ColumnDataSource(data=dict(x=[0], y=[labtop-inditop], desc = ['Labour']))
# HoverTool Label
hover = HoverTool(
tooltips=[
('Item', '@desc'),
('Cost', '@y{$ 0.00 a}'),
]
)
# Other Tools
TOOLS = 'box_zoom, box_select, resize, reset'
# Figure
p = figure(title="Capital Costs Breakdown", title_location="above", plot_width=600, plot_height=600, x_range=(-2, 2), tools=[TOOLS, hover])
# Plots
engbar = p.vbar(x=source.data['Year'][0], width=2, bottom=0,
top=engtop, alpha=0.75, color="darkslategrey", legend="Engineering", source=engsource)
equipbar = p.vbar(x=[source.data['Year'][0]], width=2, bottom=engtop,
top = equiptop, alpha=0.75, color="teal", legend="Equipment", source=equipsource)
bulkbar = p.vbar(x=[source.data['Year'][0]], width=2, bottom=equiptop,
top=bulktop, alpha=0.75, color="cyan", legend="Bulk Materials", source=bulksource)
indibar = p.vbar(x=[source.data['Year'][0]], width=2, bottom=bulktop,
top=inditop, alpha=0.75, color="powderblue", legend="Indirects", source=indisource)
labbar = p.vbar(x=[source.data['Year'][0]], width=2, bottom=inditop,
top=labtop, alpha=0.75, color="lavender", legend="Labour", source=labsource)
# Format
p.yaxis[0].formatter = NumeralTickFormatter(format="$0,000")
# Set up widgets
eng_slider = Slider(start=5000000, end=100000000, value=40000000, step=5000000, title="Engineering")
def update_data(attrname, old, new):
# Get the current slider values
a = eng_slider.value
# Generate the new curve
df['Engineering'][0] = a
source = ColumnDataSource(df)
#source.data = dict(x=x, y=y)
for w in [eng_slider]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = widgetbox(eng_slider)
# Show!
curdoc().add_root(column(inputs, p))
curdoc().title = "Sliders"
Not sure the etiquette on answering your own question... Its mostly fixed however the Hovertools are not working correctly. As the Hovertool is @y it is showing the total of the stack at each item. I want it to show the difference. Is it possible to calculate a value for the HoverTool?
In case my situation helps someone, The mistake I was making above is I was changing a value with the slider which had to be then passed through a calculation before being passed into the Glyph.
The correct way, is to do any calculations within the update function
If you come from Pandas & Matplotlib like me you may end up building in df column calls into your charts e.g. x = df['Column_name'][0]. When plotting with Glyphs in bokeh, I believe the correct way is to create a source with the data you want, so you can just pass x and y into your Glyph. See the: Master source, Get source data, Calculate Top & Bottom and New Sources from my code below.
# Read Data
df=pd.read_csv('/home/mint/SAGD_Costs.csv')
# Master source
source = ColumnDataSource(df)
# Get source data
a = source.data['Engineering'][0]
b = source.data['Equipment'][0]
c = source.data['Bulk_Materials'][0]
d = source.data['Indirects'][0]
e = source.data['Labour'][0]
# Calculate Top & Bottom
ab = 0
at = a
bb = a
bt = a + b
cb = a + b
ct = a + b + c
db = a + b + c
dt = a + b + c + d
eb = a + b + c + d
et = a + b + c + d + e
# New sources
engsource = ColumnDataSource(data=dict(x=[ab], y=[at], desc = ['Engineering']))
equipsource = ColumnDataSource(data=dict(x=[bb], y=[bt], desc = ['Equipment']))
bulksource = ColumnDataSource(data=dict(x=[cb], y=[ct], desc = ['Bulk Materials']))
indisource = ColumnDataSource(data=dict(x=[db], y=[dt], desc = ['Indirects']))
labsource = ColumnDataSource(data=dict(x=[eb], y=[et], desc = ['Labour']))
# HoverTool Label
hover = HoverTool(
tooltips=[
('Item', '@desc'),
('Cost', '@y{$ 0.00 a}'),
]
)
# Other Tools
TOOLS = 'box_zoom, box_select, resize, reset'
# Figure
p = figure(title="Capital Costs Breakdown", title_location="above", plot_width=600, plot_height=600, x_range=(-2, 2), tools=[TOOLS, hover])
# Plots
engbar = p.vbar(x=0, width=2, bottom = 'x',
top ='y', alpha=0.75, color="darkslategrey", legend="Engineering", source=engsource)
equipbar = p.vbar(x=0, width=2, bottom = 'x',
top = 'y', alpha=0.75, color="teal", legend="Equipment", source=equipsource)
bulkbar = p.vbar(x=0, width=2, bottom = 'x',
top ='y', alpha=0.75, color="cyan", legend="Bulk Materials", source=bulksource)
indibar = p.vbar(x=0, width=2, bottom = 'x',
top ='y', alpha=0.75, color="powderblue", legend="Indirects", source=indisource)
labbar = p.vbar(x=0, width=2, bottom = 'x',
top = 'y', alpha=0.75, color="lavender", legend="Labour", source=labsource)
# Format
p.yaxis[0].formatter = NumeralTickFormatter(format="$0,000")
# Set up widgets
eng_slider = Slider(start=5000000, end=100000000, value=40000000, step=5000000, title="Engineering")
equip_slider = Slider(start=5000000, end=100000000, value=40000000, step=5000000, title="Equipment")
bulk_slider = Slider(start=5000000, end=100000000, value=40000000, step=5000000, title="Bulk_Materials")
indi_slider = Slider(start=5000000, end=100000000, value=40000000, step=5000000, title="Indirects")
lab_slider = Slider(start=5000000, end=100000000, value=40000000, step=5000000, title="Labour")
def update_data(attrname, old, new):
# Get the current slider values
a = eng_slider.value
b = equip_slider.value
c = bulk_slider.value
d = indi_slider.value
e = lab_slider.value
# Calculate Top & Bottom
ab = 0
at = a
bb = a
bt = a + b
cb = a + b
ct = a + b + c
db = a + b + c
dt = a + b + c + d
eb = a + b + c + d
et = a + b + c + d + e
# New sources
engsource.data=dict(x=[ab], y=[at], desc = ['Engineering'])
equipsource.data=dict(x=[bb], y=[bt], desc = ['Equipment'])
bulksource.data=dict(x=[cb], y=[ct], desc = ['Bulk Materials'])
indisource.data=dict(x=[db], y=[dt], desc = ['Indirects'])
labsource.data=dict(x=[eb], y=[et], desc = ['Labour'])
for w in [eng_slider, equip_slider, bulk_slider, indi_slider, lab_slider]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = widgetbox(eng_slider, equip_slider, bulk_slider, indi_slider, lab_slider)
# Show!
curdoc().add_root(column(inputs, p))
curdoc().title = "Sliders"