Bokeh Slider Not Updating in JupyterLab - python

I am running an adapted version of https://github.com/bokeh/bokeh/issues/9431
Bokeh 2.2.3
Python 3.8.6
JupyterLab 2.2.9
In JupyterLab, it displays just fine, but it is not updating the color bar and image when the Range Slider is adjusted. Does anyone know how to fix this?
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import Row
from bokeh.models import LinearColorMapper, BasicTicker, ColorBar, CustomJS
from bokeh.models.widgets import RangeSlider
import bokeh.io
from bokeh.resources import INLINE
bokeh.io.output_notebook(INLINE)
data = np.random.rand(10,10)
color_mapper = LinearColorMapper(palette="Viridis256", low=0, high=1)
figure = figure(x_range=(0,1), y_range=(0,1))
img = figure.image(image=[data], color_mapper=color_mapper,
dh=[1.0], dw=[1.0], x=[0], y=[0])
color_bar = ColorBar(color_mapper=color_mapper, ticker= BasicTicker(),
location=(0,0))
figure.add_layout(color_bar, 'right')
range_slider = RangeSlider(start=data.min(), end=data.max(), value=(data.min(), data.max()), step=.1, title="Stuff", width=400)
range_slider.js_on_change("value", CustomJS(code="""
range = cb_obj.range
img.glyph.color_mapper.low = cb_obj.value[0];
img.glyph.color_mapper.high = cb_obj.value[1];
"""))
show(Row(figure, range_slider))

If you look in the browser JS console you can see the relevant error messages, e.g. "range is not defined". You need to pass any objects you want to refer to in the JS code in the args paramter of CustomJS. This is described in the docs. Here is a working version (tested w/ Bokeh 2.3, Jupyterlab >=3):
cb = CustomJS(args=dict(slider=range_slider, glyph=img.glyph), code="""
glyph.color_mapper.low = slider.value[0];
glyph.color_mapper.high = slider.value[1];
""")
range_slider.js_on_change("value", cb)
Also, FYI it's generally a bad idea to shadow existing function names:
figure = figure(...) # BAD, now you can never call figure again

Related

Use new values from bokeh widgets for interactive python plotting

I need to use the values we receive from Bokeh widgets to run some code and plot newly generated data. I cannot modify the data inside javascript that goes into CustomJS as there not available the libraries that are available in python. I understand that I can use bokeh server and run python callbacks, but this has to happen in a Databricks notebook. Is this possible?
As I cannot expose the code that I am working on, I prepared a sample code that hopefully conveys what I am trying to do.
import numpy as np
from bokeh.io import curdoc, show
from bokeh.models import ColumnDataSource, Grid, LinearAxis, Patches, Plot, Rect
from bokeh.embed import file_html
from bokeh.plotting import figure, output_file, show
from bokeh.resources import CDN
x = [x*0.005 for x in range(0, 200)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
button_cds = ColumnDataSource(data=dict(button_val=[1]))
plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
callback = CustomJS(args=dict(button_cds = button_cds), code="""
var data = button_cds.data;
var f = cb_obj.value;
data['button_val'] = f;
button_cds.change.emit();
""")
slider = Slider(start=0.1, end=4, value=1, step=.1, title="power")
slider.js_on_change('value', callback)
for i in range(len(source.data['x'])):
source.data['y'][i] = x[i]**button_cds.data['button_val'][0]
layout = column(slider, plot)
displayHTML(file_html(layout, CDN))
Please note that I can do modifying the source.data in CustomJS, but I have to use python code.

Incomplete bokeh plot

I have a pandas dataframe of 10 columns and trying to get bar plot using Bokeh.
The HTML file has the complete plot when I use plot_width=10000.
However when I increase the plot width(so that there is space between x axes values) to 30000, the plot does not fill beyond 2010. Here is the complete code. Please suggest the way forward.
from bokeh.palettes import Viridis6 as palette
from bokeh.transform import factor_cmap
from bokeh.models import ColumnDataSource,FactorRange,HoverTool
from bokeh.palettes import Spectral6
from flask import Flask, request, render_template, session, redirect,send_file
import numpy as np
import pandas as pd
from bokeh.plotting import figure, show, output_file,save
from bokeh.embed import components,file_html
from bokeh.resources import CDN
from bokeh.layouts import row,column
from bokeh.core.properties import value
dates = pd.date_range('20050101', periods=3900)
df = pd.DataFrame(np.random.randn(3900, 10), index=dates, columns=list('ABCDEFGHIJ'))
s = df.resample('M').mean().stack()
s.index = [s.index.get_level_values(0).strftime('%Y-%m-%d'),s.index.get_level_values(1)]
x = s.index.values
l1=list(s.index.levels[1])
counts = s.values
source = ColumnDataSource(data=dict(x=x, counts=counts))
p = figure(x_range=FactorRange(*x), plot_height=250,plot_width=30000, title='Plotting data',
toolbar_location=None, tools="")
p.vbar(x='x', top='counts', width=1, source=source, line_color="white")
p.y_range.start = s.values.min()
p.y_range.end = s.values.max()
p.x_range.range_padding = 0.01
p.y_range.range_padding = 0.01
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None
output_file('test_plot.html')
save([p])
show(p)
This works fine for me with Bokeh 1.0.4 and OSX/Safari. I suspect this is a limitation/issue with the underlying HTML Canvas implementation in whatever browser you are using, in which case there is nothing we can do about it. The only suggestions I can make are to split the plot up into smaller subplots, or use a different browser (or possibly different version of the same browser)

What's the command to "reset" a bokeh plot?

I have a bokeh figure that has a reset button in the toolbar. Basically, I want to "reset" the figure when I update the data that I'm plotting in the figure. How can I do that?
UPDATE: A PR has been submitted for this feature. After Bokeh 0.12.16 is released, the following will work:
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import Button, CustomJS
from bokeh.plotting import figure
p = figure(tools="reset,pan,wheel_zoom,lasso_select")
p.circle(list(range(10)), list(range(10)))
b = Button()
b.js_on_click(CustomJS(args=dict(p=p), code="""
p.reset.emit()
"""))
show(column(p, b))
As of Bokeh 0.12.1 there is no built in function to do this. It would possible to make a custom extension that does this. However, that would take a little work and experimentation and dialogue. If you'd like to pursue that option, I'd encourage you to come to the public mailing list which is better suited to iterative collaboration and discussion than SO. Alternatively, please feel free to open a feature request on the project issue tracker
Example with a radiogroup callback, that's the best way I found to reset while changing plots, just get the range of the data and set it to the range:
from bokeh.plotting import Figure
from bokeh.models import ColumnDataSource, CustomJS, RadioGroup
from bokeh.layouts import gridplot
from bokeh.resources import CDN
from bokeh.embed import file_html
x0 = range(10)
x1 = range(100)
y0 = [i for i in x0]
y1 = [i*2 for i in x1][::-1]
fig=Figure()
source1=ColumnDataSource(data={"x":[],"y":[]})
source2=ColumnDataSource(data={"x0":x0,"x1":x1,"y0":y0,"y1":y1})
p = fig.line(x='x',y='y',source=source1)
callback=CustomJS(args=dict(s1=source1,s2=source2,px=fig.x_range,py=fig.y_range), code="""
var d1 = s1.get("data");
var d2 = s2.get("data");
var val = cb_obj.active;
d1["y"] = [];
var y = d2["y"+val];
var x = d2["x"+val];
var min = Math.min( ...y );
var max = Math.max( ...y );
py.set("start",min);
py.set("end",max);
var min = Math.min( ...x );
var max = Math.max( ...x );
px.set("start",min);
px.set("end",max);
for(i=0;i<=y.length;i++){
d1["y"].push(d2["y"+val][i]);
d1["x"].push(d2["x"+val][i]);
}
s1.trigger("change");
""")
radiogroup=RadioGroup(labels=['First plot','Second plot'],active=0,callback=callback)
grid = gridplot([[fig,radiogroup]])
outfile=open('TEST.html','w')
outfile.write(file_html(grid,CDN,'Reset'))
outfile.close()
The Bokeh website is seriously lacking in examples for different ways to set callbacks for the different widgets.
I was struggling to make it work with Bokeh 2.2.1, but this JS p.reset.emit() does not seem to work.
What worked for me was to manually set the Figure renderers attribute to an empty list inside a callback function, called via on_click(). This only works with a Bokeh server running, though:
$ bokeh serve --show example.py
example.py:
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.plotting import curdoc, figure
p = figure(tools="reset,pan,wheel_zoom,lasso_select")
p.circle(list(range(10)), list(range(10)))
def clear_plot(attr):
p.renderers = []
b = Button(label="Clear plot")
b.on_click(clear_plot)
curdoc().add_root(column(p, b))

Bokeh: chart from pandas dataframe won't update on trigger

I have got a pandas dataframe whose columns I want to show as lines in a plot using a Bokeh server. Additionally, I would like to have a slider for shifting one of the lines against the other.
My problem is the update functionality when the slider value changes. I have tried the code from the sliders-example of bokeh, but it does not work.
Here is an example
import pandas as pd
from bokeh.io import vform
from bokeh.plotting import Figure, output_file, show
from bokeh.models import CustomJS, ColumnDataSource, Slider
df = pd.DataFrame([[1,2,3],[3,4,5]])
df = df.transpose()
myindex = list(df.index.values)
mysource = ColumnDataSource(df)
plot = Figure(plot_width=400, plot_height=400)
for i in range(len(mysource.column_names) - 1):
name = mysource.column_names[i]
plot.line(x = myindex, y = str(name), source = mysource)
offset = Slider(title="offset", value=0.0, start=-1.0, end=1.0, step=1)
def update_data(attrname, old, new):
# Get the current slider values
a = offset.value
temp = df[1].shift(a)
#to finish#
offset.on_change('value', update_data)
layout = vform(offset, plot)
show(layout)
Inside the update_data-function I have to update mysource, but I cannot figure out how to do that. Can anybody point me in the right direction?
Give this a try... change a=offset.value to a=cb_obj.get('value')
Then put source.trigger('change') after you do whatever it is you are trying to do in that update_data function instead of offset.on_change('value', update_data).
Also change offset = Slider(title="offset", value=0.0, start=-1.0, end=1.0, step=1, callback=CustomJS.from_py_func(offset))
Note this format I'm using works with flexx installed. https://github.com/zoofio/flexx if you have Python 3.5 you'll have to download the zip file, extract, and type python setup.py install as it isn't posted yet compiled for this version...

Running python code by clicking a button in Bokeh

Does anyone have an example of how to run python code in jupyter by clicking a button in Bokeh?
UPDATE The original answer was very out of date. The answer has been updated to reflect changes since Bokeh 0.11 which was released in January of 2016.
A complete example pared from the sliders demo, that uses features from Bokeh 0.12.4:
from numpy import linspace, pi, sin
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
# Set up data
x = linspace(0, 4*pi, 200)
y = sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(x_range=(0, 4*pi), y_range=(-2.5, 2.5))
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Set up widgets
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1)
# Set up callbacks
def update(attrname, old, new):
# Get the current slider values
a = amplitude.value
k = freq.value
# Update the data for the new curve
source.data = dict(x=x, y=a*sin(k*x))
amplitude.on_change('value', update)
freq.on_change('value', update)
# Set up layout and add to document
inputs = widgetbox(amplitude, freq)
curdoc().add_root(row(inputs, plot, width=1200))
Run with bokeh serve --show <filename> and get the following responsive web app in your browser:

Categories

Resources