How to show or hide a graph line in Bokeh - bokeh

How do we toggle a line on and off (hide or show) in Bokeh again ? The example figure below does not update.
from bokeh.io import output_file, show
from bokeh.layouts import row
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup, CustomJS
output_file("toggle_lines.html")
### Main plot
plot = figure()
# Dummy data for testing
x = list(range(25))
y0 = [ 3**a for a in x]
l0 = plot.line(x, y0, color='blue')
l0.visible = False
checkbox = CheckboxGroup(labels=["l0"], active=[1])
checkbox.js_on_click(CustomJS(args=dict(l0=l0), code="""l0.visible = 0 in checkbox.active;"""))
layout = row(checkbox, plot)
show(layout)
Thank you,

Two main things:
your active argument should either be [0] indicating that the 0th checkbutton in the Group should be active, or should not be supplied, indicating that the default state of all checkboxes should be inactive. By indicating [1] you're telling bokeh there's actually 2 checkboxes and 1 of them is active which leads to errors.
You'll need to pass your checkbox object into the javascript code via the args in your callback (you've already done this with the line, just need to include the checkbox group as well.
This code worked for me:
from bokeh.io import output_file, show
from bokeh.layouts import row
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup, CustomJS
output_file("toggle_lines.html")
### Main plot
plot = figure(x_range=(0, 25))
# Dummy data for testing
x = list(range(25))
y0 = [ 3**a for a in x]
l0 = plot.line(x, y0, color='blue')
l0.visible = False
checkbox = CheckboxGroup(labels=["l0"])
checkbox.js_on_click(CustomJS(args=dict(l0=l0, checkbox=checkbox), code="""l0.visible = 0 in checkbox.active;"""))
layout = row(checkbox, plot)
show(layout)

Related

How to hide a line by default when using a checkbox in Bokeh

We can easily make an interactive graph with Bokeh where the user can toggle a line on and off but I haven’t found a way to hide the line by default (i.e before the user checks a checkbox). As an example, the code below kind of works but when the user first load the page, all the lines are on by default even know none of them are active (active=[]). The user must then select any checkbox to make them disappear and then select a given checkbox to display a given line which is clumsy. So how would one hide the lines by default before the user selects one of them?
from bokeh.io import output_file, show
from bokeh.layouts import row
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup, CustomJS
output_file("toggle_lines.html")
### Main plot
plot = figure()
# Dummy data for testing
x = list(range(90))
y0 = [ a**1.5 for a in x]
y1 = [ a**1.55 for a in x]
y2 = [ a**1.60 for a in x]
y3 = [ a**1.65 for a in x]
l0 = plot.line(x, y0, color='red')
l1 = plot.line(x, y1, color='red')
l2 = plot.line(x, y2, color='red')
l3 = plot.line(x, y3, color='red')
checkbox = CheckboxGroup(labels=["l0", "l1", "l2", "l3"], active=[])
checkbox.callback = CustomJS(args=dict(l0=l0, l1=l1, l2=l2, l3=l3, checkbox=checkbox),
code="""
l0.visible = 0 in checkbox.active;
l1.visible = 1 in checkbox.active;
l2.visible = 2 in checkbox.active;
l3.visible = 2 in checkbox.active;
""")
layout = row(checkbox, plot)
show(layout)
You can set .visible = False in the python code, for any that you want to start off invisible.
I just wanted to note that the JS code is wrong here, the correct code is
l0.visible = checkbox.active.includes(0);
etc, etc

How to use the format parameter of sliders?

Sliders have format property, see
https://docs.bokeh.org/en/latest/docs/reference/models/widgets.sliders.html
A) Where is the documentation for this property?
B) Is there an example of using the format attribute?
EDIT: is there a way to pass a function that takes the slider value and returns a string?
Formatting documentation can be found on this page with multiple examples. The sliders value can be used by calling slider.value.
I also edited an example where I added a formatter for the amplitude slider. The slider values in this example are used to change the sine wave.
You can run this example by using this command: bokeh serve script.py --show
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider, TextInput
from bokeh.plotting import figure
# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my sine wave",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Set up widgets
text = TextInput(title="title", value='my sine wave')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.0000001, format='0.000f') #Slider with different formatting
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
# Set up callbacks
def update_title(attrname, old, new):
plot.title.text = text.value
text.on_change('value', update_title)
def update_data(attrname, old, new):
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*np.sin(k*x + w) + b
source.data = dict(x=x, y=y)
for w in [offset, amplitude, phase, freq]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = column(text, offset, amplitude, phase, freq)
curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Sliders"

Bokeh: How to show both color and marker type in legend

I want to plot legend that shows both line color and marker type when using boekh package in jupyter notebook.
I have many lines in one plot. To distinguish them, I tried my best to distinguish them by evenly distributing their color in the color space. However, when the number of lines reaches e.g. 9, the color of some lines are quite similar. So, I want to add different marker types on top of different colors so that when two lines are similar in color, they have different marker type.
It was straight-forward with matplotlib, but not straight-forward with bokeh. Below is the code I have now that can only plot legend with color.
import pandas as pd
import numpy as np
import math
import matplotlib.pyplot as plt
%matplotlib inline
from bokeh.plotting import figure, show, ColumnDataSource, save, output_notebook, output_file, reset_output
from bokeh.io import export_svgs,export_png
from bokeh.models import HoverTool, Legend
from bokeh.layouts import gridplot
import colorsys # needed for generating N equally extinguishable colors
from operator import add # needed for add lists
d = {'Sex': ['male', 'male','male','male', 'male','male','female','female','female','female','female','female'], 'age': [20, 20,20, 25,25,25,20, 20,20,25,25,25], 'working_hours': [20,30,40,20,30,40,20,30,40,20,30,40],'income': [1000, 2000,3000,1500, 2500,3500,1100, 2100,3100,1300, 2300,3300] }
values = pd.DataFrame(data=d)
x_var = 'working_hours'
x_var_dimension = 'H'
y_var = 'income'
y_var_dimension = 'Dollars'
hover = HoverTool(tooltips=[("data (x,y)", "(#x, #y)")])
TOOLS=[hover]
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
nr_expressions_row_col=9
figs_array_row_col = []
figs_row_row_col=[]
legend_its_row_col = []
legend_its_row_col_renderer = []
loop_count = 0;
for key, group in values.groupby(['Sex']):
for key_sub1, group_sub1 in group.groupby(['age']):
loop_count+=1
#print type(key)
#print group_sub1
#print count
#hover = HoverTool(tooltips=[("data (x,y)", "($x, $y)")])
x_data = group_sub1[x_var].values;
y_data = group_sub1[y_var].values
(color_r,color_g,color_b) = colorsys.hsv_to_rgb(loop_count*1.0/nr_expressions_row_col, 1, 1)
plot_row_col_line = p.line(x_data, y_data,line_color=(int(255*color_r),int(255*color_g),int(255*color_b)))
legend_its_row_col.append(("%s %s"%(key,key_sub1), [plot_row_col_line]))
legend_row_col = Legend(items = legend_its_row_col, location=(0,0))
legend_row_col.click_policy = 'hide'
legend_row_col.background_fill_alpha = 0
p.add_layout(legend_row_col, 'left')
figs_row_row_col.append(p)
figs_array_row_col.append(figs_row_row_col)
grid_row_col = gridplot(figs_array_row_col)
reset_output()
output_notebook()
show(grid_row_col)
What I can get with my code is:
What I want is:
This should give the result you want, I've added a cyclic list with all the markers that can be used with p.scatter and it takes another marker every iteration. After this I add it with the line glyph to the legend dictionary.
#!/usr/bin/python3
import pandas as pd
import numpy as np
import math
from bokeh.plotting import figure, show, ColumnDataSource, save, output_file, reset_output
from bokeh.models import HoverTool, Legend
from bokeh.layouts import gridplot
import colorsys # needed for generating N equally extinguishable colors
from itertools import cycle
d = {'Sex': ['male', 'male','male','male', 'male','male','female','female','female','female','female','female'], 'age': [20, 20,20, 25,25,25,20, 20,20,25,25,25], 'working_hours': [20,30,40,20,30,40,20,30,40,20,30,40],'income': [1000, 2000,3000,1500, 2500,3500,1100, 2100,3100,1300, 2300,3300] }
values = pd.DataFrame(data=d)
x_var = 'working_hours'
x_var_dimension = 'H'
y_var = 'income'
y_var_dimension = 'Dollars'
hover = HoverTool(tooltips=[("data (x,y)", "(#x, #y)")])
TOOLS=[hover]
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
nr_expressions_row_col=9
figs_array_row_col = []
figs_row_row_col=[]
legend_its_row_col = []
legend_its_row_col_renderer = []
loop_count = 0;
markers = ['circle', 'square', 'triangle', 'asterisk', 'circle_x', 'square_x', 'inverted_triangle', 'x', 'circle_cross', 'square_cross', 'diamond', 'cross']
pool = cycle(markers)
for key, group in values.groupby(['Sex']):
for key_sub1, group_sub1 in group.groupby(['age']):
loop_count+=1
x_data = group_sub1[x_var].values;
y_data = group_sub1[y_var].values
(color_r,color_g,color_b) = colorsys.hsv_to_rgb(loop_count*1.0/nr_expressions_row_col, 1, 1)
plot_row_col_line = p.line(x_data, y_data,line_color=(int(255*color_r),int(255*color_g),int(255*color_b)))
plot_row_col_glyph = p.scatter(x_data, y_data, color=(int(255*color_r),int(255*color_g),int(255*color_b)), size=10, marker=next(pool))
legend_its_row_col.append(("%s %s"%(key,key_sub1), [plot_row_col_line, plot_row_col_glyph]))
legend_row_col = Legend(items = legend_its_row_col, location=(0,0))
legend_row_col.click_policy = 'hide'
legend_row_col.background_fill_alpha = 0
p.add_layout(legend_row_col, 'left')
figs_row_row_col.append(p)
figs_array_row_col.append(figs_row_row_col)
grid_row_col = gridplot(figs_array_row_col)
reset_output()
show(grid_row_col)

Adding interaction to heat map to show another bokeh plot based on the selection

I'm trying to add interaction to heatmap(using rect) using CustomJS to show another bokeh plot based on the selected value.
This is what I've tried
heat_map_df_stack = pd.DataFrame(heat_map_df.stack(), columns=['rate']).reset_index()
....
issue_heat_map = figure(title="",
x_range=issues, y_range=list(reversed(products)),
x_axis_location="above", plot_width=400, plot_height=400,
tools=TOOLS, toolbar_location='below',
tooltips=[('Product & Issue Id', '#Product #Issue'), ('Issue Count', '#rate')],
name='issue_heat_map')
....
issue_heat_map.rect(x="Issue", y="Product", width=1, height=1,
source=heat_map_df_stack,
fill_color={'field': 'rate', 'transform': mapper},
line_color=None)
....
taptool = issue_heat_map.select(type=TapTool)
taptool.callback = CustomJS(args = dict(source = ""), code =
"""
console.log('test')
console.log(cb_obj)
var inds = cb_obj.selected;
window.alert(inds);
""")
On click of the rect or selection, nothing is happening now.
[Edit] : I updated the above code. Now I'm able to see console log and alert, but have no clue on how to get selected value from the heat map.
Here is a version using the bokeh server. The code is a adaption of the heatmap example from the bokeh gallery.
from math import pi
import pandas as pd
import numpy as np
from bokeh.io import curdoc
from bokeh.models import LinearColorMapper, BasicTicker, PrintfTickFormatter, ColorBar
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import gridplot
from bokeh.sampledata.unemployment1948 import data
data['Year'] = data['Year'].astype(str)
data = data.set_index('Year')
data.drop('Annual', axis=1, inplace=True)
data.columns.name = 'Month'
years = list(data.index)
months = list(data.columns)
# reshape to 1D array or rates with a month and year for each row.
df = pd.DataFrame(data.stack(), columns=['rate']).reset_index()
source = ColumnDataSource(df)
# this is the colormap from the original NYTimes plot
colors = ["#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41", "#550b1d"]
mapper = LinearColorMapper(palette=colors, low=df.rate.min(), high=df.rate.max())
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom, tap"
p = figure(title="US Unemployment ({0} - {1})".format(years[0], years[-1]),
x_range=years, y_range=list(reversed(months)),
x_axis_location="above", plot_width=900, plot_height=400,
tools=TOOLS, toolbar_location='below',
tooltips=[('date', '#Month #Year'), ('rate', '#rate%')])
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "5pt"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = pi / 3
heatmap = p.rect(x="Year", y="Month", width=1, height=1,
source=source,
fill_color={'field': 'rate', 'transform': mapper},
line_color=None)
color_bar = ColorBar(color_mapper=mapper, major_label_text_font_size="5pt",
ticker=BasicTicker(desired_num_ticks=len(colors)),
formatter=PrintfTickFormatter(format="%d%%"),
label_standoff=6, border_line_color=None, location=(0, 0))
p.add_layout(color_bar, 'right')
# Adding the tap interaction + plot
other_source = ColumnDataSource({'x': range(10), 'y': range(10)})
other_plot = figure(title="Other Plot")
other_line = other_plot.line(x='x', y='y', source=other_source)
def update(attr, old, new):
if not old:
old = [1]
if new:
other_source.data.update(y=np.array(other_source.data['y'])/old[0]*new[0])
source.selected.on_change('indices', update)
curdoc().add_root(gridplot([[p, other_plot]]))
The important part are the last few lines, where I set up the second plot and add the update function to change the slope of the line in the second plot according to the selected rect from the heatmap.

Bokeh: Automatically refreshing bokeh plots

I'm trying out an example Bokeh Application (in 'single module format') for generating a chart from a dataset. In the given example, the user on the web page can click on a button and the chart will update with the latest data. I am trying to figure out how I can achieve this same behavior without requiring the user to click on the button. That is, I would like the chart to automatically update/refresh/reload at a specified interval without the need for user interaction. Ideally, I would only have to change something in myapp.py to accomplish this.
bokeh version is 0.12.0.
Demo code copied here for convenience:
# myapp.py
import numpy as np
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc
# create a plot and style its properties
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None
# add a text renderer to out plot (no data yet)
r = p.text(x=[], y=[], text=[], text_color=[], text_font_size="20pt",
text_baseline="middle", text_align="center")
i = 0
ds = r.data_source
# create a callback that will add a number in a random location
def callback():
global i
ds.data['x'].append(np.random.random()*70 + 15)
ds.data['y'].append(np.random.random()*70 + 15)
ds.data['text_color'].append(RdYlBu3[i%3])
ds.data['text'].append(str(i))
ds.trigger('data', ds.data, ds.data)
i = i + 1
# add a button widget and configure with the call back
button = Button(label="Press Me")
button.on_click(callback)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))
Turns out there's a method in the Document object:
add_periodic_callback(callback, period_milliseconds)
Not sure why this isn't mentioned outside of the API...
Yeah ,add_periodic_callback()
import numpy as np
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None
r = p.text(x=[], y=[], text=[], text_color=[], text_font_size="20pt",
text_baseline="middle", text_align="center")
i = 0
ds = r.data_source
def callback():
global i
ds.data['x'].append(np.random.random()*70 + 15)
ds.data['y'].append(np.random.random()*70 + 15)
ds.data['text_color'].append(RdYlBu3[i%3])
ds.data['text'].append(str(i))
ds.trigger('data', ds.data, ds.data)
i = i + 1
curdoc().add_root(column(p))
curdoc().add_periodic_callback(callback, 1000)

Resources