Bokeh- hovertool does not reflect CDSView in heatmap - python

I have a ColumnDataSource like:
MONTH | YEAR | TYPE | COUNT
1 2018 A 5
3 2019 A 3
2 2018 B 6
....
5 2018 C 1
5 2017 C 4
I create a heatmap with a hovertool and radio buttons that alternate through the different TYPE views i.e.
heat_source=ColumnDataSource(data=df)
A_view= CDSView(source=heat_source, filters=[GroupFilter(column_name="TYPE", group="A")])
B_view= CDSView(source=heat_source, filters=[GroupFilter(column_name="TYPE", group="B")])
p_heat= figure(x_range=months, y_range=years,
y_axis_type='datetime',plot_width=405, plot_height=650, toolbar_location=None)
h=HoverTool(tooltips=[('Type','#TYPE'),('Transactions','#COUNT{0,0}'),
('Date','#MONTH-#YEAR')])
p_heat.add_tools(h)
radio_group=RadioGroup(labels=["A","B",'C'], active=0)
radio_group.on_change('active',lambda attr,old,new: update())
def update():
if radio_group.active==0:
p_heat.rect(x="MONTH",y="YEAR", width=1, height=1, source=heat_source, view=A_view,line_color=None)
if radio_group.active==1:
p_heat.rect(x="MONTH",y="YEAR", width=1, height=1, source=heat_source, view=B_view,line_color=None)
Everything works fine, except when I toggle the TYPEs, the hover data is still unchanged. I've tried embedded the hovertool within the radio button callback for each If, but no avail.

You can create the rect GlyphRenderer once, and change the group property of the filter in a javascript callback:
from bokeh.plotting import figure, output_notebook, show
from bokeh.models import ColumnDataSource, CDSView, GroupFilter, RadioGroup, CustomJS, HoverTool
from bokeh.palettes import viridis
from bokeh.transform import linear_cmap
from bokeh.layouts import column
import pandas as pd
import numpy as np
from itertools import product
output_notebook()
df = pd.DataFrame(list(product(range(1, 13), range(2000, 2018), ["A", "B"])), columns=["MONTH", "YEAR", "TYPE"])
df["COUNT"] = np.random.randint(0, 100, size=df.shape[0])
heat_source=ColumnDataSource(data=df)
view= CDSView(source=heat_source, filters=[GroupFilter(column_name="TYPE", group="A")])
p_heat= figure(plot_width=405, plot_height=650, toolbar_location=None)
h=HoverTool(tooltips=[('Type','#TYPE'),('Transactions','#COUNT{0,0}'),
('Date','#MONTH-#YEAR')])
p_heat.add_tools(h)
rect = p_heat.rect(x="MONTH",y="YEAR", width=1, height=1, source=heat_source, view=view,line_color=None,
fill_color=linear_cmap("COUNT", viridis(256), 0, 100))
type_widget = RadioGroup(labels=["A", "B"], active=0)
def callback(source=heat_source, filter=view.filters[0], cmap=rect.glyph.fill_color["transform"]):
filter.group = cb_obj.labels[cb_obj.active]
if filter.group == "A":
cmap.low=0
cmap.high=100
else:
cmap.low=-100
cmap.high=200
source.change.emit()
type_widget.js_on_change("active", CustomJS.from_py_func(callback))
show(column(type_widget, p_heat))

Related

How to run Bokeh server on Streamlit

I would like to show an interactive Bokeh plot on Streamlit but I am not able to add a widget to the plot. The plot with show(p) command shows on a tab but I cannot show the widget. The Bokeh serve show command works perfectly but how can I use it inside Streamlit.
import pandas as pd
import streamlit as st
from bokeh.models import ColumnDataSource, Select
from bokeh.plotting import figure,ColumnDataSource, show
from bokeh.models import FactorRange
from bokeh.models import NumeralTickFormatter
from math import radians
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, Grid, LinearAxis, Plot, VBar
from bokeh.io import curdoc, show
from bokeh.models import HoverTool
from bokeh.models.widgets import Slider
from bokeh.layouts import column, row
import streamlit
st.title("Hello world!")
uploaded_file = st.file_uploader("Choose a file")
if uploaded_file is not None:
df = pd.read_excel(uploaded_file)
df = pd.read_excel("C:/Users/Files/Data.xls")
df1 = df.groupby(['A','B'])['C'].sum().reset_index()
#st.write(df1)
df2 = df1.sort_values(by=['B'], ascending=False)
st.write(df2)
data_source = ColumnDataSource(df2)
x = 10
range = FactorRange(factors=list(df2.iloc[:x]['A'].unique()))
p = figure(x_range = range ,plot_height=600,title="Top Spend by
Vendors",toolbar_location=None, plot_width=1200)
r = p.vbar(x='A', top='B', width=0.04, color ='red', source=data_source)
p.yaxis.formatter=NumeralTickFormatter(format="$0,0")
p.xaxis.major_label_orientation=radians(60)
def update_plot(attr, old, new):
label = "Siddharth"
v_custom = streamlit.slider(label,10,30,5, step=1)
st.write('Values:', v_custom)
range = FactorRange(factors=list(df2.iloc[:v_custom]['Supplier'].unique()))
Currnew = ColumnDataSource(df2[['A', 'B']])
p.x_range.factors = list(df_filter1['Vendor'].unique())
r.data_source.data = Currnew.data
v_custom.on_change('value', update_plot)
Bokeh 2.0 is broken while using in streamlit.
Use the earlier version to use bokeh in streamlit

Bokeh update map tooltip using select or slider

I am trying to update a worldmap tooltip using a slicer or dropdown select. I got following question which sorted the most of the stuff for a Bokeh Slider custom JS callback
import pandas as pd
import random
from datetime import timedelta
df = pd.DataFrame({'base' : ["2017-01-01" for t in range(10000)],
'Date' : [random.randint(0, 1035) for t in range(10000)],
'Sales' : [random.random() for t in range(10000)]})
df['base'] = pd.to_datetime(df['base'])
df["Date2"] = df.apply(lambda x: x["base"] + timedelta(days=x['Date']), axis=1)
df.drop(['base', 'Date'], axis=1, inplace=True)
df.set_index('Date2', inplace=True)
df['month'] = df.index.month
df['year'] = df.index.year
df['day'] = df.index.day
df.head()
from bokeh.models.widgets import Slider,Select
from bokeh.io import output_notebook, show, output_file
from bokeh.layouts import widgetbox, column
from bokeh.models import Slider, ColumnDataSource, CustomJS
from bokeh.plotting import figure, curdoc
from bokeh.core.properties import value
from bokeh.models.ranges import FactorRange
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import ColumnDataSource, CDSView, IndexFilter, BooleanFilter, HoverTool
source1=df.groupby(['year','month','day'], as_index = False).sum()
source = source1[source1['year']== 2017]
sourcex = source[source['month'] ==1]
Overall=ColumnDataSource(source)
Curr=ColumnDataSource(sourcex)
boolinit = source['month']==1
view = CDSView(source=Overall, filters=[BooleanFilter(boolinit)])
hover3 = HoverTool(tooltips = [('day', '#day'),('Sales','#{Sales}{0,0}')],
formatters = {'day': 'datetime','Sales': 'numeral'})
p = figure(title='YEARLY SALES', plot_width=600, plot_height=400, min_border=3,
tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],
toolbar_location="above")
r = p.vbar(x='day', top='Sales', width=0.2, color='#e8bc76', source=Curr)
p.xaxis.axis_label = 'Day'
p.xaxis.axis_label_text_font_style = 'normal'
p.xaxis.axis_label_text_font_size = '12pt'
callback = CustomJS(args=dict(source=Overall, sc=Curr), code="""
var f = select.value;
sc.data['day'] = [];
sc.data['Sales'] = [];
for (var i = 0; i <= source.get_length(); i++){
if (source.data['month'][i] == f){
sc.data['day'].push(source.data['day'][i])
sc.data['Sales'].push(source.data['Sales'][i])
}
}
sc.change.emit();
""")
select = Select(options=["1","2","3"], title="Month", callback=callback)
callback.args["select"] = select
layout = column(select, p)
#Display plot inline in Jupyter notebook
output_notebook()
output_file("Filterdata.html")
show(layout)
Now, I replicated the same for a worldmap as below:
import pandas as pd
import geopandas as gpd
current_week = 4
shapefile = 'data/countries_110m/ne_110m_admin_0_countries.shp'
gdf = gpd.read_file(shapefile)[['ADMIN', 'ADM0_A3', 'geometry']]
gdf.columns = ['country', 'country_code', 'geometry']
gdf = gdf.drop(gdf.index[159])
df = pd.DataFrame({'Country':['India','India'],
'SalesGain':['10%','20%'],
'Week':[4,5],
'Color':[0.2,0.4]
})
import json
from bokeh.models.widgets import Slider,Select
from bokeh.io import output_notebook, show, output_file
from bokeh.layouts import widgetbox, column
from bokeh.models import Slider, ColumnDataSource, CustomJS
from bokeh.plotting import figure, curdoc
from bokeh.core.properties import value
from bokeh.models.ranges import FactorRange
from bokeh.palettes import brewer
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import ColumnDataSource, CDSView, IndexFilter, BooleanFilter, HoverTool,GeoJSONDataSource, LinearColorMapper, ColorBar
from bokeh.plotting import figure, output_file, show
output_file("worldmap.html")
merged = gdf.merge(df, left_on = 'country', right_on = 'Country', how = 'left')
merged_json = json.loads(merged.to_json())
json_data = json.dumps(merged_json)
geosource_all = GeoJSONDataSource(geojson = json_data)
df_curr = df[df['Week']==current_week]
merged_curr = gdf.merge(df_curr, left_on = 'country', right_on = 'Country', how = 'left')
merged_json_curr = json.loads(merged_curr.to_json())
json_data_curr = json.dumps(merged_json_curr)
geosource_curr = GeoJSONDataSource(geojson = json_data_curr)
# boolinit = merged['Week']!=current_week
boolinit = merged['Week']==current_week
view = CDSView(source=geosource_all, filters=[BooleanFilter(boolinit)])
hover3 = HoverTool(tooltips = [('Country', '#Country'),('Sales','#SalesGain')])
#Define a sequential multi-hue color palette.
palette = brewer['YlGnBu'][8]
#Reverse color order so that dark blue is highest value
palette = palette[::-1]
#Instantiate LinearColorMapper that linearly maps numbers in a range, into a sequence of colors. Input nan_color.
color_mapper = LinearColorMapper(palette = palette, low = 0, high = 12, nan_color = '#d9d9d9')
#Define custom tick labels for color bar.
tick_labels = {'0': '0', '2':'2%', '4':'4%', '6':'6%', '8':'8%','10':'10%','12':'12%'}
#Create color bar.
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=6,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal', major_label_overrides = tick_labels)
#Create figure object.
p = figure(title='Covid-19 Impact', plot_width=900, plot_height=600, min_border=3,
tools = [hover3,'box_zoom','wheel_zoom', 'pan','reset'],toolbar_location="above")
p.title.text_font_size = '20pt'
p.title.text_color = "darkblue"
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
#Add patch renderer to figure.
p.patches('xs','ys', source = geosource_curr,fill_color = {'field' :'Color', 'transform' : color_mapper},
line_color = 'black', line_width = 0.25, fill_alpha = 1)
p.add_layout(color_bar, 'below')
callback = CustomJS(args=dict(source=geosource_all, sc=geosource_curr), code="""
var f = slider.value;
sc.data['Country'] = [];
sc.data['Week'] = [];
sc.data['SalesGain'] = [];
for (var i = 0; i <= source.get_length(); i++){
if ((source.data['Week'][i] == f ) || (source.data['Country'][i] == null) ){
sc.data['SalesGain'].push(source.data['SalesGain'][i])
sc.data['Week'].push(source.data['Week'][i])
sc.data['Country'].push(source.data['Country'][i])
}
}
sc.change.emit();
""")
# select = Select(options=["201951","201952","201953"], title="Week", callback=callback)
# callback.args["select"] = select
# layout = column(select, p)
slider = Slider(start=1, end=5, value=current_week, step=1, title="Month", callback=callback)
callback.args["slider"] = slider
layout = column(slider, p)
#Display plot inline in Jupyter notebook
output_notebook()
show(layout)
But in this case, as soon as I click on the slider, tooltip data vanish away. World map input file can be found here to smoothly run the code:
https://github.com/CrazyDaffodils/Interactive-Choropleth-Map-Using-Python/tree/master/bokeh-app/data
A tooltip disappears after a small delay after you move your mouse off the glyph that has triggered it.
Right now, Bokeh doesn't have any built-in way of changing that behavior. There's an open issue for that with a workaround that you might be able to adapt to your needs: https://github.com/bokeh/bokeh/issues/5724

Bokeh Graph not changing dynamically with click in column value in data table

I am trying to change a NetworkX graph by clicking a value in bokeh data table. I can see and visualize the required value in the layout box but the same value is not getting used to display/ change the NetworkX Graph.
I have tried using python call back, but its not working either.It says "Only JavaScript callbacks may be used with standalone output".
#from bokeh.io import vplot
from bokeh.plotting import figure
from bokeh.models.graphs import
from_networkx,NodesAndLinkedEdges,EdgesAndLinkedNodes
from bokeh.plotting import figure
from bokeh.models import Plot,Range1d
from bokeh.io import output_file, show
from datetime import date
from random import randint
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, DateFormatter,
TableColumn,Spinner
import bokeh.layouts as layouts
import bokeh.models.widgets as widgets
from bokeh.io import curdoc
source= ColumnDataSource(dict(CUSTOMER_NO = pr, priority =
np.arange(1,len(pr)+1,1)))
columns = [TableColumn(field = "CUSTOMER_NO", title = "CUSTOMER_NO"),
TableColumn(field = "priority", title = "PRIORITY")]
data_table = DataTable(source = source, columns = columns, width = 200,
height = 280, editable = True, reorderable = False)
text_row = TextInput(value = None, title = "Row index:", width = 420)
text_CUSTOMER_NO = TextInput(value = None, title = "CUSTOMER_NO:", width
= 420)
text_priority = TextInput(value = None, title = "priority:", width = 420)
plot=figure(title='test',x_range=Range1d(-1.1,1.1),
y_range=Range1d(-1.1,1.1),tools="",toolbar_location=None)
s =
G.subgraph(nx.shortest_path(G.to_undirected(),text_CUSTOMER_NO.value))
pos=nx.planar_layout(s)
graph=from_networkx(s,pos)
plot.renderers.append(graph)
source_code = """
row = cb_obj.indices[0]
text_row.value = String(row);
text_CUSTOMER_NO.value = String(source.data['CUSTOMER_NO'][row])
text_priority.value = String(source.data['priority'][row]);"""
callback = CustomJS(args = dict(source = source, text_row =
text_row,text_CUSTOMER_NO=text_CUSTOMER_NO,text_priority=text_priority),
code = source_code)
source.selected.js_on_change('indices', callback)
layout = column(data_table, text_row,text_CUSTOMER_NO,text_priority,
plot)
#show(layout)
bokeh.io.output_notebook()
bokeh.io.show(layout)
#bokeh.io.show(plot)
There is no error message given, the output shows a correct bokeh data table, the clicks on the customer no shows me correct values in layout boxs but the networkx shows a black triangle.
I was able to somehow achieve this using python callback.Can some one please help me to achieve the same using CustomJS. The code is below.
from datetime import date
from random import randint
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
import bokeh.layouts as layouts
import bokeh.models.widgets as widgets
from bokeh.io import curdoc
from bokeh.io import vplot
from bokeh.plotting import figure
from bokeh.models.graphs import
from_networkx,NodesAndLinkedEdges,EdgesAndLinkedNodes
from bokeh.plotting import figure
from bokeh.models import Plot,Range1d
from bokeh.io import output_file, show
from datetime import date
from random import randint
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
import bokeh.layouts as layouts
import bokeh.models.widgets as widgets
from bokeh.io import curdoc
G = nx.Graph()
G.add_nodes_from([1,2,3,4,5,6,7,8,9,10])
G.add_edges_from([(1, 2), (1, 3),(3,4),(5,6),(6,7),(8,9),(9,10)])
data = dict(
CUSTOMER_NO=[1,2,3,4,5,6,7,8,9,10],
priority=[1,2,3,4,5,6,7,8,9,10],
)
d_source= ColumnDataSource(data)
columns = [TableColumn(field = "CUSTOMER_NO", title = "CUSTOMER_NO"), TableColumn(field = "priority", title = "PRIORITY")]
data_table = DataTable(source = source, columns = columns, width = 200, height = 280, editable = True, reorderable = False)
def table_select_callback(attr, old, new):
# print 'here'
# print new
# selected_row = new['1d']['indices'][0]
selected_row = new[0]
s = G.subgraph(nx.shortest_path(G.to_undirected(), data['CUSTOMER_NO'][selected_row]))
#download_count = data['downloads'][selected_row]
#chart_data = np.random.uniform(0, 100, size=download_count)
plot = figure(title='test',x_range=Range1d(-1.1,1.1),y_range=Range1d(-1.1,1.1), tools = ['reset', 'pan','wheel_zoom','save', 'lasso_select', ])
#p = figure(title='bla')
pos=nx.random_layout(s)
graph=from_networkx(s,pos)
plot.renderers.append(graph)
#r = p.line(x=range(len(chart_data)), y=chart_data)
root_layout.children[1] = plot
d_source.selected.on_change('indices', table_select_callback)
root_layout = layouts.Column(data_table, widgets.Div(text='Select customer number'))
curdoc().add_root(root_layout)

how can i use tap tool with bar chart -bokeh

while using bokeh tap tool with bar chart I need to get the id of selected bar.how can I get this id of each bar without using customJS
the code is running using bokeh server
source = ColumnDataSource(data=df)
p = figure(x_range=source.data['month'], plot_height=600, toolbar_location=None, tools="tap,hover", title="month analysis")
p.vbar(x='month', top='count', width=0.5, source=source, legend="month",
line_color='white', fill_color=factor_cmap('month', palette=Spectral6, factors=df['month']))
hover = HoverTool(tooltips=[("count", "#count")])
p.legend.orientation = "horizontal"
p.legend.location = "top_right"
taptool = p.select(type=TapTool)
curdoc().add_root(row( p, width=800))
This should work. The ID of a selected glyph can be found in source.selected.indices.
#!/usr/bin/python3
import pandas as pd
from bokeh.plotting import figure, show, curdoc
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, HoverTool, TapTool
from bokeh.transform import factor_cmap
from bokeh.palettes import Spectral6
from bokeh.layouts import row
from bokeh.events import Tap
df = pd.read_csv('data.csv')
df['count'] = df['count'].astype(dtype='int32')
source = ColumnDataSource(data=df)
p = figure(x_range=source.data['month'], plot_height=600, toolbar_location=None, tools="tap,hover", title="month analysis")
p.vbar(x='month', top='count', width=0.5, source=source, legend="month",
line_color='white', fill_color=factor_cmap('month', palette=Spectral6, factors=df['month']))
hover = HoverTool(tooltips=[("count", "#count")])
p.legend.orientation = "horizontal"
p.legend.location = "top_right"
taptool = p.select(type=TapTool)
def callback(event):
selected = source.selected.indices
print(selected)
p.on_event(Tap, callback)
curdoc().add_root(row( p, width=800))

How to dynamically hide glyphs and legend items with Bokeh

I am trying to implement checkboxes in bokeh where each checkbox should show/hide the line associated with it. I'm aware it's possible to achieve this with legends, but I want this effect to happen in two plots at the same time. Also, the legend should update as well. In the example below the checkboxes appear, but do nothing. I am clearly not grasping how to update de dataframe used as source. Thanks for any help.
from bokeh.io import show, curdoc
from bokeh.models import HoverTool, ColumnDataSource, Legend
from bokeh.plotting import figure
from bokeh.palettes import Category10
from bokeh.models.widgets import CheckboxGroup
from bokeh.layouts import row
import pandas as pd
def update(atrr, old, new):
lines_to_plot = [checkbox_group.labels[i] for i in checkbox_group.active]
cols = ['x']
for label in lines_to_plot:
cols += [label + 'y']
cols += [label]
newdf = df0[cols]
source.data.update(ColumnDataSource(newdf))
df0 = pd.DataFrame({'x': [1, 2, 3], 'Ay' : [1, 5, 3], 'A': [0.2, 0.1, 0.2], 'By' : [2, 4, 3], 'B':[0.1, 0.3, 0.2]})
columns = ['A', 'B']
checkbox_group = CheckboxGroup(labels=columns, active=[0, 1])
tools_to_show = 'box_zoom,save,hover,reset'
p = figure(plot_height =300, plot_width = 1200,
toolbar_location='above',
tools=tools_to_show)
legend_it = []
color = Category10[10]
columns = ['A', 'B']
source = ColumnDataSource(df0)
for i, col in enumerate(columns):
c = p.line('x', col, source=source, name=col, color=color[i])
legend_it.append((col, [c]))
legend = Legend(items=legend_it, location=(5,114))#(0, -60))
p.add_layout(legend, 'right')
hover = p.select(dict(type=HoverTool))
hover.tooltips = [("Name","$name"), ("Aux", "#$name")]
hover.mode = 'mouse'
layout = row(p,checkbox_group)
checkbox_group.on_change('active', update)
curdoc().add_root(layout)
You will have to manage LegendItem objects manually. Here is a complete example:
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.palettes import Viridis3
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup, Legend, LegendItem
p = figure()
props = dict(line_width=4, line_alpha=0.7)
x = np.linspace(0, 4 * np.pi, 100)
l0 = p.line(x, np.sin(x), color=Viridis3[0], **props)
l1 = p.line(x, 4 * np.cos(x), color=Viridis3[1], **props)
l2 = p.line(x, np.tan(x), color=Viridis3[2], **props)
legend_items = [LegendItem(label="Line %d" % i, renderers=[r]) for i, r in enumerate([l0, l1, l2])]
p.add_layout(Legend(items=legend_items))
checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"], active=[0, 1, 2], width=100)
def update(attr, old, new):
l0.visible = 0 in checkbox.active
l1.visible = 1 in checkbox.active
l2.visible = 2 in checkbox.active
p.legend.items = [legend_items[i] for i in checkbox.active]
checkbox.on_change('active', update)
layout = row(checkbox, p)
curdoc().add_root(layout)

Categories