Related
I'm new in Bokeh, and I'm trying to construct an interactive scatter plot.
In this plot, I would like to change the p-value cut-off and, then, change the colors according to the rule (if FC < 0, the dots are blue; if FC > 0, dots are red.
I've tried:
from bokeh.plotting import ColumnDataSource, figure, output_file, show, curdoc
from bokeh.models import BoxSelectTool, LassoSelectTool,CustomJS, Slider
from bokeh.transform import linear_cmap
from bokeh.layouts import gridplot, column, row
import pandas as pd
import numpy as np
fc = np.random.uniform(0, 4, 1000)
p_value = np.random.uniform(0,1,1000)
df = pd.DataFrame([fc, p_value], ['FC', 'pvalue']).transpose()
df['log2(fc)'] = np.log2(df['FC'])
df['-log10(p)'] = -np.log10(df['pvalue'])
output_file("df.html")
y = df['-log10(p)']
x = df['log2(fc)']
col = np.where(y> -np.log10(0.5), np.where( x>-0, np.where( x<0, '#606060','#E4001B'), '#6194BC'), '#606060' )
source = ColumnDataSource(data=dict(
x=df['log2(fc)'],
y=df['-log10(p)'],
pvalue=df['pvalue'],
FC = df['log2(fc)'],
color = col))
TOOLTIPS = [
("FC", "#FC"),
("pvalue", "#pvalue")
]
TOOLS="hover,crosshair,pan,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select,"
p = figure(plot_width=500, plot_height=500, tooltips=TOOLTIPS,
title="test", tools=TOOLS)
p.circle('x', 'y', size=5, source=source, alpha = 0.5, fill_color = 'color', line_color='color')
p.xaxis.axis_label = "log2(FC)"
p.yaxis.axis_label = "-log10(p)"
p.background_fill_color = "#fafafa"
p_slider = Slider(start=0.0, end=5, step=.01, title="p_value")
callback = CustomJS(args=dict(source=source,plot=p, color_mapper = col, pvalue=p_slider),
code="""
const data = source.data;
var P = pvalue.value;
const x = df['p_value']
const y = df['log2(fc)']
source.change.emit();
""")
p_slider.js_on_change('value', callback)
layout = row(
p,
column(p_slider),
)
show(layout)
Despite this approach show me the bar to control the p-value, I'm not getting to control the bar, neither change the colors according to p-value...
Thanks for your help!
enter image description here I want to change bokeh chart at run time when click on a radio button. Here is what I've tried till now:
import numpy as np
import pandas as pd
from bokeh.core.properties import value
from bokeh.models.widgets import Paragraph,PreText,RadioButtonGroup
from bokeh.layouts import widgetbox
from bokeh.models.widgets import CheckboxGroup, RadioGroup
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource,LabelSet,CustomJS,Row
from bokeh.plotting import figure, show,save
from bokeh.transform import dodge
from bokeh.palettes import Viridis
colors = ["#c9d9d3", "#718dbf", "#e84d60"]
dataframe = pd.read_csv('Experience.csv')
source = ColumnDataSource(dataframe)
exp = dataframe['Experience']
ys = list(dataframe.keys())
ys.remove('Experience')
TOOLTIPS = [("Experience", "#Experience")]
p = figure(x_range=exp, y_range=(0, 100), plot_height=350, tools=['hover','save','reset','zoom_out','zoom_in','pan','box_zoom'],tooltips=TOOLTIPS)
stacked = p.vbar_stack(stackers=ys, x='Experience',color=colors,source=source,legend=[value(x) for x in ys],name=ys,width=0.5,)
colorList = Viridis[len(ys)]
labels = []
for y, offset, color in zip(ys, [-0.25, 0, 0.25], colorList):
bar = p.vbar(x=dodge('Experience', offset, range=p.x_range), top=y, width=0.2, source=source, legend=y + ' ', color=color)
bar.visible = False
radiogroup = RadioGroup(labels = ["StackedBar", "Bar"], active = 0,)
radiogroup.callback = CustomJS(args = dict(stacked = stacked, bar = bar), code = """
for (i in stacked)
stacked[i].visible = false;
bar.visible = false;
if (cb_obj.active == 0)
for (i in stacked)
stacked[i].visible = true;
else if (cb_obj.active == 1)
bar.visible = true; """)
layout = Row(p, radiogroup)
show(layout)
It is showing two graphs in one figure, but I want bar graph default and when I click on the radio button the graph should change based on click event.here is my full code..pl check and tell what i am doing wrong
The vbar_stack returns a list of glyphs so you need to toggle visibility each of them separately like this:
from bokeh.plotting import show, figure
from bokeh.models import RadioGroup, CustomJS, Row
from bokeh.models.sources import ColumnDataSource
import pandas as pd
data = {'fruits' : ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries'],
'2015' : [2, 1, 4, 3, 2, 4],
'2016' : [5, 3, 4, 2, 4, 6],
'2017' : [3, 2, 4, 4, 5, 3]}
df = pd.DataFrame(data)
df['total'] = df.sum(axis = 1)
p = figure(x_range = data['fruits'], title = "Fruit Counts by Year", tools = "hover", tooltips = "$name #fruits: #$name")
vbar_stack = p.vbar_stack(["2015", "2016", "2017"], x = 'fruits', width = 0.9, color = ["#c9d9d3", "#718dbf", "#e84d60"], source = data)
vbar = p.vbar(x = 'fruits', width = 0.5, top = 'total', source = ColumnDataSource(df))
vbar.visible = False
radiogroup = RadioGroup(labels = ["StackedBar", "Bar"], active = 0,)
radiogroup.callback = CustomJS(args = dict(vbar_stack = vbar_stack, vbar = vbar), code = """
for (i in vbar_stack)
vbar_stack[i].visible = false;
vbar.visible = false;
if (cb_obj.active == 0)
for (i in vbar_stack)
vbar_stack[i].visible = true;
else if (cb_obj.active == 1)
vbar.visible = true; """)
layout = Row(p, radiogroup)
show(layout)
See below your code with some small corrections:
import os
import numpy as np
import pandas as pd
from bokeh.core.properties import value
from bokeh.models.widgets import Paragraph, PreText, RadioButtonGroup
from bokeh.layouts import widgetbox
from bokeh.models.widgets import CheckboxGroup, RadioGroup
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, LabelSet, CustomJS, Row
from bokeh.plotting import figure, show, save
from bokeh.transform import dodge
from bokeh.palettes import Viridis
colors = ["#c9d9d3", "#718dbf", "#e84d60"]
dataframe = pd.read_csv(os.path.join(os.path.dirname(__file__), 'Experience.csv'))
source = ColumnDataSource(dataframe)
exp = dataframe['Experience']
ys = list(dataframe.keys())
ys.remove('Experience')
TOOLTIPS = [("Experience", "#Experience")]
p = figure(x_range = exp, y_range = (0, 100), plot_height = 350, tools = ['hover', 'save', 'reset', 'zoom_out', 'zoom_in', 'pan', 'box_zoom'], tooltips = TOOLTIPS)
stacked = p.vbar_stack(stackers = ys, x = 'Experience', color = colors, source = source, legend = [value(x) for x in ys], name = ys, width = 0.5,)
colorList = Viridis[len(ys)]
labels = []
bars = []
for y, offset, color in zip(ys, [-0.25, 0, 0.25], colors):
bar = p.vbar(x = dodge('Experience', offset, range = p.x_range), top = y, width = 0.2, source = source, color = color)
bar.visible = False
bars.append(bar)
radiogroup = RadioGroup(labels = ["StackedBar", "Bar"], active = 0,)
radiogroup.callback = CustomJS(args = dict(stacked = stacked, bars = bars), code = """
for (i in stacked)
stacked[i].visible = false;
for (i in bars)
bars[i].visible = false;
if (cb_obj.active == 0)
for (i in stacked)
stacked[i].visible = true;
else if (cb_obj.active == 1)
for (i in bars)
bars[i].visible = true; """)
layout = Row(p, radiogroup)
show(layout)
I want to pass a slider value (that I've built with Bokeh) back to my Python code. The code generates 2 lines on a plot and allow me to alter the slope and intercept of one of them. But it fails when I introduce the callback javascript to pass the slider value as "ff" back into my Python code.
Can you help me with the callback syntax for getting the slider value back to python (eg see the print(ff) final line of the code) - I do want to do something more interesting than just print it out ultimately!
The error message from the callback is:
ValueError: expected an element of Dict(String, Instance(Model)), got {'my_dict': {'s': 0.5}}
My code is:-
from ipywidgets import interact
import numpy as np
from bokeh.io import push_notebook, show, output_notebook
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.models.callbacks import CustomJS
output_notebook()
x = np.linspace(0, 20, 200) # create equally spaced points.
s = 0.5 # slope.
i = 3 # intercept.
y = s * x + i # straight line.
my_dict = dict(s=s) # need to create a dict object to hold what gets passed in the callback.
callback = CustomJS(args=dict(my_dict=my_dict), code="""
var ff = cb_obj.value
my_dict.change.emit()
""")
// ff should be the slider value.
p = figure(title="simple line example", plot_height=300, plot_width=600, y_range=(-20,20),
background_fill_color='#efefef')
r = p.line(x, y, color="#8888cc", line_width=1.5, alpha=0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2*x+1.2, color="#0088cc", line_width=1.9, alpha=0.2) # 2nd line.
def update(w=s, a=i):
r.data_source.data['y'] = w * x + a # allow updates for the line r.
push_notebook()
show(p, notebook_handle=True)
interact(update, w=(-10,10), a=(-12,12) )
print(ff) # Return what the slider value is. I want ff accessible back in my python code.
I don't have Jupyter Notebook so these 2 examples are pure Bokeh apps, first one is using JS callback and the second one is using Python callback (Bokeh v1.0.4).
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, Slider, CustomJS, Range1d
from bokeh.layouts import column
slider_slope = Slider(start = 0, end = 1, value = 0.5, step = 0.1)
slider_intercept = Slider(start = 0, end = 20, value = 10, step = 1)
slider_code = ''' i = slider_intercept.value
s = slider_slope.value
x = r.data_source.data['x'];
y = [];
for (index = 0; index < x.length; index ++)
y.push((s * x[index]) + i);
r.data_source.data['y'] = y
r.data_source.change.emit(); '''
s = slider_slope.value # slope.
i = slider_intercept.value # intercept.
x = np.linspace(-40, 20, 200)
y = [(s * xx + i) for xx in x]
p = figure(title = "simple line example", plot_height = 500, plot_width = 600, y_range = Range1d(start = -80, end = 40), background_fill_color = '#efefef')
r = p.line(x, y, color = "red", line_width = 1.5, alpha = 0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2 * x + 1.2, color = "blue", line_width = 1.9, alpha = 0.2) # 2nd line.
slider_callback = CustomJS(args = dict(slider_slope = slider_slope,
slider_intercept = slider_intercept,
r = r), code = slider_code)
slider_slope.callback = slider_callback
slider_intercept.callback = slider_callback
layout = column(p, slider_slope, slider_intercept)
show(layout, notebook_handle = True)
You can easily translate it to Bokeh server app with Python callback:
import numpy as np
from bokeh.plotting import figure, show, curdoc
from bokeh.models import Slider, CustomJS
from bokeh.layouts import column
slider_slope = Slider(title = 'Slope', start = 0, end = 1, value = 0.5, step = 0.1)
slider_intercept = Slider(title = 'Intercept', start = 0, end = 20, value = 10, step = 1)
s = slider_slope.value # slope.
i = slider_intercept.value # intercept.
x = np.linspace(-40, 20, 200)
y = [(s * xx + i) for xx in x]
p = figure(title = "simple line example", plot_height = 500, plot_width = 600, y_range = Range1d(start = -80, end = 40), background_fill_color = '#efefef')
r = p.line(x, y, color = "red", line_width = 1.5, alpha = 0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2 * x + 1.2, color = "blue", line_width = 1.9, alpha = 0.2) # 2nd line.
def update(attr, old, new):
s = slider_slope.value # slope.
i = slider_intercept.value # intercept
x = r.data_source.data['x'];
y = []
for value in x:
y.append((s * value) + i)
r.data_source.data['y'] = y
slider_slope.on_change('value', update)
slider_intercept.on_change('value', update)
layout = column(p, slider_slope, slider_intercept)
curdoc().add_root(layout)
Result:
This is the solution. It creates a bokeh server app. It is run (from spyder) by using the file called: 20190328_start_bokeh_server.py
There is a straight line plotted and controlled by sliders. Clicking the button saves the slider values to a csv file.
To get the code below to run use this code (that's contained in 20190404_start_bokeh_server.py) in the console:
import os
os.chdir("C:\Users") # Change the working directory to be the script location.
os.system("start call bokeh serve --show 20190404_bokeh_server.py ") # Alternatively: This command can be typed into the anacondas prompt, once I've navigated to the directory holding the .py file.
"""
import pandas as pd
import numpy as np
from random import random
from numpy.random import randn
from bokeh.plotting import figure, show, curdoc
from bokeh.models import Slider, CustomJS, Range1d, Button
from bokeh.layouts import column
from bokeh.plotting import figure, curdoc
import os
slider_slope = Slider(title = 'Slope', start = 0, end = 1, value = 0.5, step = 0.1)
slider_intercept = Slider(title = 'Intercept', start = 0, end = 20, value = 10, step = 1)
s = slider_slope.value # slope.
i = slider_intercept.value # intercept.
x = np.linspace(-40, 20, 200)
y = [(s * xx + i) for xx in x]
p = figure(title = "simple line example", plot_height = 500, plot_width = 600, y_range = Range1d(start = -80, end = 40), background_fill_color = '#efefef')
r = p.line(x, y, color = "red", line_width = 1.5, alpha = 0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2 * x + 1.2, color = "blue", line_width = 1.9, alpha = 0.2) # 2nd line. This could be actuals.
def update(attr, old, new):
s = slider_slope.value # slope.
i = slider_intercept.value # intercept
x = r.data_source.data['x'];
y = []
for value in x:
y.append((s * value) + i)
r.data_source.data['y'] = y
# create a callback that will save the slider settings to a csv file when the button is clicked.
def callback():
os.chdir("C:\\Users") # Change the working directory to where I want to save the csv.
mydf = pd.DataFrame.from_dict({'slope':[0],'intercept':[0]}) # Create a DataFrame using pandas, based on a dictionary definition. Set the values to be 0 by default.
mydf.loc[0] = [slider_slope.value, slider_intercept.value] # Assign the first row to slope and intercept.
mydf.to_csv('slider.csv',index=True) # Write to the csv the final values of the button.
# add a button widget and configure with the call back
button = Button(label="Save slope and intercept to csv")
button.on_click(callback)
slider_slope.on_change('value', update)
slider_intercept.on_change('value', update)
layout = column(p, slider_slope, slider_intercept, button)
curdoc().add_root(layout)
show(layout, notebook_handle = True) # Launch the chart in the web browser.
Looking to do something along the lines of a UI as here: Bokeh: Using Checkbox widget to hide and show plots wherein I can selectively show/hide the whole figure in a column of figures. A drop down menu (OptionMenu with multiple selections) where I could select which plots showed up (assuming I could name the figures) would be preferable.
I am not familiar with JS, any guidance? (Thanks in advance)
I'd hope that the image wouldn't be visible anymore and the next figure would jump up like so:
eg:
I have multiple figures in a column generated as:
from bokeh.io import output_file, show
from bokeh.layouts import column
from bokeh.plotting import figure
output_file("layout.html")
x = list(range(11))
y0 = x
y1 = [10 - i for i in x]
y2 = [abs(i - 5) for i in x]
# create a new plot
s1 = figure(plot_width=250, plot_height=250, title=None)
s1.circle(x, y0, size=10, color="navy", alpha=0.5)
# create another one
s2 = figure(plot_width=250, plot_height=250, title=None)
s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)
# create and another
s3 = figure(plot_width=250, plot_height=250, title=None)
s3.square(x, y2, size=10, color="olive", alpha=0.5)
# put the results in a column and show
show(column(s1, s2, s3))
Plots don't have a visible toggle, at least as of version 0.13. So you will have to reset the children value of the layout widget. I'm not quite sure what interaction you intend with a dropdown. Here is a complete example with a checkbox:
from bokeh.io import output_file, show
from bokeh.layouts import column, row
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup, CustomJS
output_file("layout.html")
x = list(range(11))
y0 = x
y1 = [10 - i for i in x]
y2 = [abs(i - 5) for i in x]
s1 = figure(plot_width=250, plot_height=250, title=None)
s1.circle(x, y0, size=10, color="navy", alpha=0.5)
s2 = figure(plot_width=250, plot_height=250, title=None)
s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)
s3 = figure(plot_width=250, plot_height=250, title=None)
s3.square(x, y2, size=10, color="olive", alpha=0.5)
col = column(s1, s2, s3)
checkbox = CheckboxGroup(labels=["Plot 1", "Plot 2", "Plot 3"],
active=[0, 1, 2], width=100)
callback = CustomJS(args=dict(plots=[s1,s2, s3], col=col, checkbox=checkbox), code="""
const children = []
for (const i of checkbox.active) {
children.push(plots[i])
}
col.children = children
""")
checkbox.js_on_change('active', callback)
show(row(checkbox, col))
You could do something similar with a MultiSelect:
select = MultiSelect(options=[("0", "Plot 1"), ("1", "Plot 2"), ("2", "Plot 3")],
value=["0", "1", "2"], width=100)
callback = CustomJS(args=dict(plots=[s1,s2, s3], col=col, select=select), code="""
const children = []
for (const i of select.value) {
children.push(plots[i])
}
col.children = children
""")
select.js_on_change('value', callback)
Small FYI that that code is a little sloppy—it's relying on JS implicitly casting strings like "0" to numbers.
s1.tags, s2.tags, s3.tags = ['Foo'], ['Bar'], ['Arr'] # name your plots
plots = [s1, s2, s3]
labels = [(plots[i].tags[0]) for i in range(len(plots))]
active = list(range(0, len(plots)))
chkbx = CheckboxButtonGroup(labels=labels, active=active)
callback = CustomJS(args=dict(plots=plots, chkbx=chkbx), code="""
for (let i = 0; i < plots.length; i++){
plots[i].visible = chkbx.active.includes(i)
}
""")
chkbx.js_on_click(callback)
show(column([chkbx] + plots))
Thanks to #bigreddot and their answer for laying foundation for this solution.
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)