Interactive Slider using Bokeh - javascript

I'm trying to use a bokeh interactive slider to modify the contents of a plot, similar the example here. I have a two nested lists x and y.
I simply want the slider to change the index of the lists to plot. i.e. If the slider index = 0, then plot x[0] vs y[0], if the slider index is 1, plot x[1] vs y[1], etc...
The documentation example computes the new data on the fly, which is not feasible for the data that I need to work with.
When I run the code below, nothing shows up in the plot... I don't know javascript, so I'm guessing this is where I'm going wrong.
I'm running Python 3.5 and Bokeh 0.12. This is all run within a jupyter-notebook.
import numpy as np
from bokeh.layouts import row
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import Figure, show
from bokeh.io import output_notebook
from bokeh.resources import INLINE
output_notebook(INLINE)
x = [[x*0.05 for x in range(0, 500)],
[x*0.05 for x in range(0, 500)]]
y = [np.sin(x[0]),
np.cos(x[1])]
source = ColumnDataSource(data=dict(x=x, y=y))
plot = Figure(plot_width=400, plot_height=400)
plot.line('x'[0], 'y'[0], source=source, line_width=3, line_alpha=0.6)
callback = CustomJS(args=dict(source=source), code="""
var data = source.get('data');
var f = cb_obj.get('value');
x = data['x'][f];
y = data['y'][f];
source.trigger('change');
""")
slider = Slider(start=0, end=1, value=0, step=1, title="index", callback=callback)
layout = row(plot, slider)
show(layout)

Instead of having a slider changing the index of the data to be plotted, you could define two ColumnDataSources: source_visible and source_available where the first one holds the data that is currently being shown in the plot and the second one acts as a data repository from where we can sample data in CustomJS callback based on user selection on the web page:
import numpy as np
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, Slider, CustomJS
from bokeh.plotting import Figure, show
# Define data
x = [x*0.05 for x in range(0, 500)]
trigonometric_functions = {
'0': np.sin(x),
'1': np.cos(x),
'2': np.tan(x),
'3': np.arctan(x)}
initial_function = '0'
# Wrap the data in two ColumnDataSources
source_visible = ColumnDataSource(data=dict(
x=x, y=trigonometric_functions[initial_function]))
source_available = ColumnDataSource(data=trigonometric_functions)
# Define plot elements
plot = Figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source_visible, line_width=3, line_alpha=0.6)
slider = Slider(title='Trigonometric function',
value=int(initial_function),
start=np.min([int(i) for i in trigonometric_functions.keys()]),
end=np.max([int(i) for i in trigonometric_functions.keys()]),
step=1)
# Define CustomJS callback, which updates the plot based on selected function
# by updating the source_visible ColumnDataSource.
slider.callback = CustomJS(
args=dict(source_visible=source_visible,
source_available=source_available), code="""
var selected_function = cb_obj.value;
// Get the data from the data sources
var data_visible = source_visible.data;
var data_available = source_available.data;
// Change y-axis data according to the selected value
data_visible.y = data_available[selected_function];
// Update the plot
source_visible.change.emit();
""")
layout = row(plot, slider)
show(layout)
Keep in mind that if your data is large, it might take a while to send it all at once to the client's browser.

Related

Want to display HoverTool tooltip data with the TapTool in bokeh

I am new to python and want to make an interactive chart with a dataset given in class. I want to be able to simply display the data associated with the point I select with the TapTool in Bokeh. The data is too dense to jsut simply use the hovertool though. It seems the hovertool did not require javascript, but apparently getting the tap tool requires knowing a whole other coding language. I am not familiar with python as it is, so adding a javascript callback is making things a bit complicated.
Right now I have this figure plotted with my dataframe using data from the California Housing dataset from sci-kit. I also have the US_states sampledata from bokeh to plot too.
from bokeh.layouts import layout
from bokeh.plotting import figure, show, output_file
from bokeh.models import Circle, Div, ColumnDataSource, CustomJS, MultiChoice, HoverTool, LinearColorMapper, ColorBar
from bokeh.io import output_notebook
from bokeh.sampledata import us_states
from bokeh.transform import transform
from bokeh.models.tools import *
#US state sample data
us_states = us_states.data.copy()
us_states = us_states["CA"]
#Creating figure and adding california basemap
color = LinearColorMapper(palette = 'Viridis256',
low = df.MedInc.min(),
high = df.MedInc.max())
color2 = LinearColorMapper(palette = 'Inferno256',
low = df.price.min(),
high = df.price.max())
cal = figure(title = "California Housing Data Geographic Distribution",
plot_width = 1000)
cal.circle('Longitude','Latitude',source = geo_source,
color= transform('MedInc', color),size =2,
alpha = 0.2,legend_label = "Median Income")
cal.circle('Longitude','Latitude',source = geo_source,
color= transform('price', color2),size =1,
alpha = 0.2,legend_label = "House Price")
color_bar = ColorBar(color_mapper = color,
label_standoff = 14,
location = (0,0),
title = 'Median Income')
color_bar2 = ColorBar(color_mapper = color2,
label_standoff = 14,
location = (0,0),
title = 'House Price')
cal.add_layout(color_bar,'right')
cal.add_layout(color_bar2,'right')
cal.legend.location = "top_right"
cal.legend.click_policy="hide"
I have no clue what to write for the JS callback for the TapTool though since I'm not really sure how it works, how the variables transfer, etc.
callback = CustomJS(args=dict(source=geo_source), code="""
//Insert JS code here
""")
taptool = TapTool(callback=callback)
geo_source.selected.js_on_change('indices', callback)
cal.add_tools(taptool)
you should add figure to Tap event
plot.on_event(Tap, function)
and in function you can use indices to update your data/graph
def function():
print(source.selected.indices)

Implementing JavaScript callback for checkboxes in bokeh?

Short version: bokeh checkboxes with JS callbacks to plot subsets of a dataframe?
Longer version: The answer here gives a good explanation for multiselect, but I want to do the same thing for checkboxes. All my data is in one pandas dataframe, and I'm using the checkboxes to select bits of that dataframe for plotting. I believe that with checkboxes it is something to do with cb_obj.active but I'm very unsure how to get it to work. In particular my plot includes colored rectangles and text, all information for which: position, text, color, is taken from my dataframe. And I don't know how much plotting must be done in the callback, and how much outside.
As far as I can tell, the callback calls a function to do the actual plotting.
I know I should give a minimal example, but I can't think how to simplify my code enough... so all I really want at the moment is an example of the use of checkboxes, with a JavaScript callback, to plot a subset of a dataframe. Somebody must have done this, but I haven't found an example yet! Thanks.
Here is an example:
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import Slider, CheckboxGroup, CustomJS, ColumnDataSource, CDSView
from bokeh.models.filters import CustomJSFilter
from bokeh.layouts import row
from bokeh.transform import factor_cmap
from bokeh.palettes import Category10_10
output_notebook()
You can use CustomJSFilter to calculate the indice of rows to show:
from bokeh.sampledata import iris
source = ColumnDataSource(data=iris.flowers)
species = iris.flowers.species.unique().tolist()
checkboxes = CheckboxGroup(labels=species, active=list(range(len(species))))
fig = figure()
filter = CustomJSFilter(code="""
let selected = checkboxes.active.map(i=>checkboxes.labels[i]);
let indices = [];
let column = source.data.species;
for(let i=0; i<column.length; i++){
if(selected.includes(column[i])){
indices.push(i);
}
}
return indices;
""", args=dict(checkboxes=checkboxes, source=source))
checkboxes.js_on_change("active", CustomJS(code="source.change.emit();", args=dict(source=source)))
fig.scatter("sepal_length", "sepal_width",
color=factor_cmap("species", Category10_10, species),
source=source, view=CDSView(source=source, filters=[filter]))
show(row(checkboxes, fig))
Here's an adapted version of my answer for MultiSelect that you referred to:
from bokeh.models import CustomJS, ColumnDataSource, CheckboxGroup, Column
from bokeh.plotting import figure, show
import pandas as pd
data = dict(letter = ['A','A','B','C','B','B','A','C','C','B'],
x = [1, 2, 1, 2, 3, 2, 2, 3, 2, 3],
y = ['10','20','10','30','10','40','10','30','10','40'])
data = pd.DataFrame(data)
data_source = ColumnDataSource(data)
source = ColumnDataSource(dict(x = [], y = []))
plot = figure()
plot.circle('x', 'y', line_width = 2, source = source)
callback = CustomJS(args = {'source': source, 'data_source': data_source},
code = """
var data = data_source.data;
var s_data = source.data;
var letter = data['letter'];
var select_vals = cb_obj.active.map(x => cb_obj.labels[x]);
console.log(select_vals);
var x_data = data['x'];
var y_data = data['y'];
var x = s_data['x'];
x.length = 0;
var y = s_data['y'];
y.length = 0;
for (var i = 0; i < x_data.length; i++) {
if (select_vals.indexOf(letter[i]) >= 0) {
x.push(x_data[i]);
y.push(y_data[i]);
}
}
source.change.emit();
console.log("callback completed");
""")
chkbxgrp = CheckboxGroup(labels = ['A', 'B', 'C'], active=[])
chkbxgrp.js_on_change('active', callback)
layout = Column(chkbxgrp, plot)
show(layout)
Remarks:
The callback won't work in Internet Explorer because it uses arrow functions, which IE does not support. If that is an issue, you need
do the mapping using something other than arrow functions
as user bigreddot commented in the answer you referred to, this could also be done using CDSView using a custom Filter, as GroupFilter does not support multiple values

Python Bokeh CustomJS: Debugging a JavaScript callback for the Taping-Tool

I am working with Python 3.6.2 and Bokeh 1.0.4 to create a custom JavaScript callback in my plot.
By tapping on one of the points in the plot, I'd like all points sharing the same attribute in the id-column to be highlighted.
Iterating over all datapoints with JavaScript and manipulating the respective 'selected'-attribute in the ColumnDataSource-object should do the trick.
Unfortunately I can not figure out how to correct this code.
# Import packages
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool
# Create the data for the points
x = [0, 1, 2, 3]
y = [0, 1, 0, 1]
ids = ['A','B','A','B']
data = {'x':x, 'y':y, 'id':ids}
source = ColumnDataSource(data)
# Add tools to the plot
tap = TapTool()
hover = HoverTool(tooltips=[("X", "#x"),
("Y", "#y"),
("ID", "#id")])
# Create a plotting figure
p = figure(plot_width=400, plot_height=400, tools=[tap,hover])
# Code for the callback
code = """
// Set column name to select similar glyphs
var column = 'id';
// Get data from ColumnDataSource
var data = source.data;
// Get indices array of all selected items
var selected = source.selected.indices;
// Array to store glyph-indices to highlight
var select_inds = [];
// Check if only a single glyph is selected
if(selected.length==1){
// Get the value of the column to find similar attributes/glyphs
attribute_value = data[column][selected[0]];
// Iterate over all entries in the ColumnDataSource
for (var i=0; i<data[column].length; ++i){
// Check if items have the same attribute
if(data[column][i]==attribute_value){
// Add index to selected list
select_inds.push(i);
}
}
}
// Set selected glyphs in ColumnDataSource
source.selected.indices = select_inds;
// Save changes to ColumnDataSource
source.change.emit();
"""
# Create a CustomJS callback with the code and the data
callback = CustomJS(args={'source':source}, code=code)
# Add the callback to the ColumnDataSource
source.callback=callback
# Plots circles
p.circle('x', 'y', source=source, size=25, color='blue', alpha=1, hover_color='black', hover_alpha=1)
# Show plot
show(p)
An older version of this problem with Bokeh 0.13.0 could not solve my problem.
You were almost there! The callback has to be added to the TapTool instead of the ColumnDataSource.
# Import packages
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, HoverTool, TapTool
# Create the data for the points
x = [0, 1, 2, 3]
y = [0, 1, 0, 1]
ids = ['A','B','A','B']
# Generate data source for the visualization
data = {'x':x, 'y':y, 'id':ids}
source = ColumnDataSource(data)
# Add tools to the plot
tap = TapTool()
hover = HoverTool(tooltips=[("X", "#x"),
("Y", "#y"),
("ID", "#id")])
# Create a plotting figure
p = figure(plot_width=400, plot_height=400, tools=[tap,hover])
# Code for the callback
code = """
// Set column name to select similar glyphs
var column = 'id';
// Get data from ColumnDataSource
var data = source.data;
// Get indices array of all selected items
var selected = source.selected.indices;
// Array to store glyph-indices to highlight
var select_inds = [];
// Check if only a single glyph is selected
if(selected.length==1){
// Get the value of the column to find similar attributes/glyphs
var attribute_value = data[column][selected[0]];
// Iterate over all entries in the ColumnDataSource
for (var i=0; i<data[column].length; ++i){
// Check if items have the same attribute
if(data[column][i]==attribute_value){
// Add index to selected list
select_inds.push(i);
}
}
}
// Set selected glyphs in ColumnDataSource
source.selected.indices = select_inds;
// Save changes to ColumnDataSource
source.change.emit();
"""
# Create a CustomJS callback with the code and the data
callback = CustomJS(args={'source':source}, code=code)
# Add the callback to the taptool
tap.callback=callback
# Plots circles
p.circle('x', 'y', source=source, size=25, color='blue', alpha=1, hover_color='black', hover_alpha=1)
# Show plot
show(p)

Get Selected Glyph from NetworkX Graph in Bokeh

I am trying to get the index of nodes selected using box select from a GraphRender object in Bokeh in order to create a linked datatable. (I want to be able to get the index for a selected node)
The question is somewhat similar to: JavaScript callback to get selected glyph index in Bokeh however I was unable to solve it using their proposed solution.
The full code is below and I have tried to solve it with a custom JS callback but I was unable to do so.
Any help is much appreciated. Thanks in advance!
(Note: This is my first question so please let me know if further information is required.)
import pandas as pd
import numpy as np
from bokeh.layouts import row, widgetbox, column
from bokeh.models import ColumnDataSource, CustomJS, StaticLayoutProvider, Oval, Circle
from bokeh.models import HoverTool, TapTool, BoxSelectTool, GraphRenderer
from bokeh.models.widgets import RangeSlider, Button, DataTable, TableColumn, NumberFormatter
from bokeh.io import curdoc, show, output_notebook
from bokeh.plotting import figure
import networkx as nx
from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes, NodesOnly
# Import / instantiate networkx graph
G = nx.Graph()
G.add_edge('a', 'b', weight=0.6)
G.add_edge('a', 'c', weight=0.2)
G.add_edge('c', 'd', weight=0.1)
G.add_edge('c', 'e', weight=0.7)
G.add_edge('c', 'f', weight=0.9)
G.add_edge('a', 'd', weight=0.3)
# Node Characteristics
node_name = list(G.nodes())
positions = nx.spring_layout(G)
node_size = [k*4 for k in range(len(G.nodes()))]
nx.set_node_attributes(G, node_size, 'node_size')
visual_attributes=ColumnDataSource(
pd.DataFrame.from_dict({k:v for k,v in G.nodes(data=True)},orient='index'))
# Edge characteristics
start_edge = [start_edge for (start_edge, end_edge) in G.edges()]
end_edge = [end_edge for (start_edge, end_edge) in G.edges()]
weight = list(nx.get_edge_attributes(G,'weight').values())
edge_df = pd.DataFrame({'source':start_edge, 'target':end_edge, 'weight':weight})
# Create full graph from edgelist
G = nx.from_pandas_edgelist(edge_df,edge_attr=True)
# Convert full graph to Bokeh network for node coordinates and instantiate Bokeh graph object
G_source = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
graph = GraphRenderer()
# Update loop where the magic happens
def update():
selected_df = edge_df[(edge_df['weight'] >= slider.value[0]) & (edge_df['weight'] <= slider.value[1])]
sub_G = nx.from_pandas_edgelist(selected_df,edge_attr=True)
sub_graph = from_networkx(sub_G, nx.spring_layout, scale=2, center=(0,0))
graph.edge_renderer.data_source.data = sub_graph.edge_renderer.data_source.data
graph.node_renderer.data_source.data = G_source.node_renderer.data_source.data
graph.node_renderer.data_source.add(node_size,'node_size')
def selected_points(attr,old,new):
selected_idx = graph.node_renderer.selected.indices #does not work
print(selected_idx)
# Slider which changes values to update the graph
slider = RangeSlider(title="Weights", start=0, end=1, value=(0.25, 0.75), step=0.10)
slider.on_change('value', lambda attr, old, new: update())
# Plot object which is updated
plot = figure(title="Meetup Network Analysis", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
tools = "pan,wheel_zoom,box_select,reset,box_zoom,crosshair", plot_width=800, plot_height=800)
# Assign layout for nodes, render graph, and add hover tool
graph.layout_provider = StaticLayoutProvider(graph_layout=positions)
graph.node_renderer.glyph = Circle(size='node_size')
graph.selection_policy = NodesOnly()
plot.renderers.append(graph)
plot.tools.append(HoverTool(tooltips=[('Name', '#index')]))
# Set layout
layout = column(slider,plot)
# does not work
#graph.node_renderer.data_source.on_change("selected", selected_points)
# Create Bokeh server object
curdoc().add_root(layout)
update()
Instead of putting the listener on graph.node_renderer.data_source.on_change, use this instead:
graph.node_renderer.data_source.selected.on_change("indices", selected_points)
This would trigger server-side response.
Hongyi

Dependent Sliders with Bokeh, how to write the callbacks

I want to be able to slide through many plots which are the result of simulations accross 3+ dimensions. I am using the Bokeh package via Python.
For simplicity, let's assume I have two dimensions : d, and nc. But nc depends on d in the following way:
if d=100, nc=56,57
if d=20, nc=5,6
And I have 4 pictures:
d_100_nc_56.png,
d_100_nc_57.png,
d_20_nc_5.png,
d_20_nc_6.png
So I want two sliders, one for d, and one for nc, to cycle the .png images through the image_url function of Bokeh.plotting.Figure . However, the value of the nc slider should update itself as I change the slider in d
from bokeh.io import vform
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import Figure, output_file, show
output_file('image.html')
source = ColumnDataSource(data=dict(url=['d_100_nc_55.png']))
p = Figure(x_range=(0,1), y_range=(0,1))
callback_nc = CustomJS(args=dict(source=source), code="""
var data = source.get('data');
var f = cb_obj.get('value')
old = data['url'][0]
to_replace=old.substring(old.lastIndexOf("nc_")+3,old.lastIndexOf(".png"))
data['url'][0] = old.replace(to_replace,f.toString(10))
source.trigger('change');
""")
callback_d = CustomJS(args=dict(source=source), code="""
var data = source.get('data');
var f = cb_obj.get('value')
old = data['url'][0]
to_replace=old.substring(old.lastIndexOf("d_")+2,old.lastIndexOf("_nc_"))
data['url'][0] = old.replace(to_replace,f.toString(10))
source.trigger('change');
""")
p.image_url('url',source=source, x=0, y=1,w=1,h=1)
p.text(x=0,y=0,text=source.data['url'])
slider_nc = Slider(start=55, end=65, value=1, step=1, title="nc", callback=callback_nc)
slider_d = Slider(start=20, end=100, value=100, step=80, title="density", callback=callback_d)
layout = vform(slider_nc,slider_d, p)
show(layout)
However, I do not know how to pass the d slider as an argument to the nc slider to fetch its properties and update them on the fly. Is this possible ? Otherwise it limits the use of multiple sliders through bokeh quite substantially.
Edit: updated for more recent versions
You pass the slider the same way you pass source, as an item in the args dictionary. Any Python-side Bokeh model you pass there is automatically made available to the callback. Then, BokehJS model properties exactly match the python properties described in the reference guide Here is an example that updates one slider based off another:
# Example from Bokeh 0.12.x
from bokeh.plotting import show, output_file
from bokeh.layouts import column
from bokeh.models import CustomJS, Slider
s1 = Slider(start=1, end=10, value=1, step=1)
s2 = Slider(start=0, end=1, value=0, step=1)
s1.callback = CustomJS(args=dict(s1=s1, s2=s2), code="""
s2.end = s1.value;
""")
output_file("foo.html")
show(column(s1,s2))

Categories

Resources