Can't update CDSView in a bokeh plot - python

I know that many topics on this subject has already been discussed but i can't really see what's wrong in my code
I have a very big amount of data to plot (lines), and i want to highlight some of them (by circle) according to user choice via a select button)
I have tried to make my code as the most simple as possible to reflect my problem.
The line is plotted, the circle based on the "by defaut" choice of the select button are plotted, but nothing is updated when selecting another "stupid_label" in my select widget
import pandas as pd
import numpy as np
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
from bokeh.models.widgets import Select
from bokeh.layouts import row, column
def update_plot(attr, old, new):
view.filters[0] = GroupFilter(column_name='stupid_label', group=stupidlabel.value)
def make_plot(fim):
TOOLS = "save,pan,box_zoom,reset,wheel_zoom"
p = figure(title="the plot that makes me mad !",plot_width=800, plot_height=400,tools=TOOLS)
p.line(fim.mydates,fim.myvalues,color='blue')
return p
def main():
f = {'mydates': [19123, 19124, 19125, 19126,19127,19128,19129,19129,19130], 'myvalues': [34, 41, 12, 7, 27, 40, 32, 11, 1], 'stupid_label': ['POUET', 'POUET','BANZAI','BANZAI','BANZAI', 'YOUPI','YOUPI','POUET','POUET']}
fim = pd.DataFrame(data=f)
p = make_plot(fim)
u_stupid_label=np.unique(fim.stupid_label)
stupidlabel = Select(value=u_stupid_label[0],options=list(u_stupid_label))
src= ColumnDataSource(fim)
view = CDSView(source=src,filters=[GroupFilter(column_name='stupid_label', group=stupidlabel.value)])
p.circle('mydates','myvalues',source=src,view=view,color='black')
stupidlabel.on_change('value', update_plot)
layout = row(p, stupidlabel)
curdoc().add_root(layout)
curdoc().title = "please.. works !!!"
main()

All,
I removed my def main(), and main() at the end, and it works.

Related

Can bokeh dynamically update the number of columns in a gridplot?

I have 2 bokeh rows. The top row contains a DataTable and a TextInput, both of which are able to stretch_width in order to fit the width of the browser. The bottom row contains a gridplot, which is able to stretch_width, but only does so by distorting the scale of the image. Ideally, I would like the gridplot to update the amount of columns displayed based on the size of the browser.
Consider the following example:
import pandas as pd
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.models import ColumnDataSource, TextInput
from bokeh.plotting import figure, output_file, save
from bokeh.layouts import row, column, gridplot
def get_datatable():
"""this can stretch width without issue"""
df = pd.DataFrame({'a': [0, 1, 2], 'b': [2, 3, 4]})
source = ColumnDataSource(df)
Columns = [TableColumn(field=i, title=i) for i in df.columns]
data_table = DataTable(columns=Columns, source=source, sizing_mode='stretch_width', max_width=9999)
return data_table
def get_text_input():
"""this can stretch width without issue"""
return TextInput(value='Example', title='Title', sizing_mode="stretch_width", max_width=9999)
def get_gridplot():
"""
this requires columns to be hard-coded
stretch_width is an option, but only distorts the images if enabled
"""
figs = []
for _ in range(30):
fig = figure(x_range=(0,10), y_range=(0,10))
_ = fig.image_rgba(image=[], x=0, y=0)
figs.append(fig)
return gridplot(children=figs, ncols=2)
top_row = row([get_datatable(), get_text_input()], max_width=9999, sizing_mode='stretch_width')
bottom_row = row(get_gridplot())
col = column(top_row, bottom_row, sizing_mode="stretch_width")
output_file("example.html")
save(col)
My end goal is to have the gridplot automatically update the amount of columns based on the width of the browser. Is there a way to do this natively in bokeh? If not, is it possible to do this via a CustomJs javascript callback?
Solution
Consider using sizing_mode=“scale_width” when calling figure.
fig = figure(x_range=(0,10), y_range=(0,10), sizing_mode=“scale_width”)
Note
It may be preferable to use scale_width instead of stretch_width more generally.
Bokeh Doc Example: https://docs.bokeh.org/en/latest/docs/user_guide/layout.html#multiple-objects

Why my bokeh server app won't update the figure

here's my data :https://drive.google.com/drive/folders/1CabmdDQucaKW2XhBxQlXVNOSiNRtkMm-?usp=sharing
i want to use the select to choose the stock i want to show;
and slider to choose the year range i want to show;
and checkboxgroup to choose the index i want to compare with.
the problem is when i adjust the slider, the figure will update, but when i use the select and checkboxgroup, the figure won't update,
what's the reason?
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput , Select , Div, CheckboxGroup
from bokeh.plotting import figure
import pandas as pd
import numpy as np
price=pd.read_excel('price.xlsx',index_col=0)
# input control
stock = Select(title='Stock',value='AAPL',options=[x for x in list(price.columns) if x not in ['S&P','DOW']])
yr_1 = Slider(title='Start year',value=2015,start=2000,end=2020,step=1)
yr_2 = Slider(title='End year',value=2020,start=2000,end=2020,step=1)
index = CheckboxGroup(labels=['S&P','DOW'],active=[0,1])
def get_data():
compare_index = [index.labels[i] for i in index.active]
stocks = stock.value
start_year = str(yr_1.value)
end_year = str(yr_2.value)
select_list = []
select_list.append(stocks)
select_list.extend(compare_index)
selected = price[select_list]
selected = selected [start_year:end_year]
for col in selected.columns:
selected[col]=selected[col]/selected[col].dropna()[0]
return ColumnDataSource(selected)
def make_plot(source):
fig=figure(plot_height=600, plot_width=700, title="",sizing_mode="scale_both", x_axis_type="datetime")
data_columns = list(source.data.keys())
for data in data_columns[1:]:
fig.line(x=data_columns[0],y=data,source=source,line_width=3, line_alpha=0.6, legend_label=data)
return fig
def update(attrname, old, new):
new_source = get_data()
source.data.clear()
source.data.update(new_source.data)
#get the initial value and plot
source = get_data()
plot = make_plot(source)
#control_update
stock.on_change('value', update)
yr_1.on_change('value', update)
yr_2.on_change('value', update)
index.on_change('active', update)
# Set up layouts and add to document
inputs = column(stock, yr_1, yr_2, index)
curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Stocks"
You're creating a new ColumnDataSource for new data. That's not a good approach.
Instead, create it once and then just assign its data as appropriate.
In your case, I would do it like this:
Create ColumnDataSource just once, as described above
Do not use .update on CDS, just reassign .data
Create the legend manually
For that one line that's susceptible to the select change choose a static x field and use it everywhere instead
Change the first legend item's label when you change the select's value to instead of that x field it has the correct name

Cannot get a Bokeh checkbox group to update graph

I am trying to create a simple interactive graph with a checkbox group. I want the checkboxes to result in showing the appropriate line on the graph when ticked. I am doing this within Jupyter Notebook.
I've managed to get it embedded in Jupyter, and I wrote a callback function that does execute code. I am able to create a new ColumnDataSource from the checkbox selection. However, the graph is just not updating.
I've gone through every post on here I could, and look at every tutorial I could find. Most of them simply have an update callback which creates the new source, then sets the graph's source to the new one, which I believe is supposed to update the graph. I have also seen variations where people assign it as oldsource.data = newsource.data. This doesn't work for me either.
I am wondering whether there is any inherent limitations in embedding to Jupyter Notebook that I need Javascript for, or limitations to how sources can be updated. Or maybe I am just missing something very obvious? Code below:
import os
import pandas as pd
import numpy as np
import bokeh.plotting as bk
import bokeh.layouts as ly
import bokeh.models as md
import bokeh.colors as cl
import bokeh.palettes as plet
from bokeh.io import curdoc
from bokeh.io import show as io_show
from bokeh.models.widgets import CheckboxGroup, Select, Button
from bokeh.plotting import output_file, show, figure, output_notebook, reset_output, curdoc
data_list = ["one", "two", "three", "four", "five", "six"]
data_list2 = ["one", "two"]
data_fac = [1, 2, 3, 4, 5, 6]
data_fac_dict = dict(zip(data_list,data_fac))
data_x = np.linspace(0,100)
df = pd.DataFrame(columns = data_list)
def modify_doc(doc):
def make_data(data_list):
#Make new source with appropriate datasets
df = pd.DataFrame(columns = data_list)
for case in data_list:
df[case] = data_x * data_fac_dict[case]
result = md.ColumnDataSource(df)
return result
#Make colors
list_colors = plet.Dark2[len(data_list)]
dict_colors = dict(zip(data_list,list_colors))
#Default source with one datapoint
src = make_data(["one"])
print(src.data.keys())
#Plot graphs
p = bk.figure()
for case in src.data.keys():
if case != "index":
p.line(source = src, x = 'index', y = case, color = dict_colors[case])
print("plotting loop")
def update(attr,old,new):
#Callback
print("update triggered")
selection = list()
for i in wg_chk.active:
selection.append(data_list[i])
src = make_data(selection)
print(selection)
wg_chk = CheckboxGroup(labels = data_list, active = [0]*len(data_list))
wg_chk.on_change('active', update)
layout = ly.row(wg_chk,p)
doc.add_root(layout)
bk.show(modify_doc, notebook_url='localhost:8888')
UPDATE #1
I changed the code in the callback to make the appropriate dataframe, then create a dict using ColumnDataSource.from_df, then set src.data equal to it as below. Still doesn't seem to work. I used a print to make sure data_new has correct keys.
df_new = make_df(selection)
data_new = md.ColumnDataSource.from_df(df_new)
src.data = data_new
For clarity, I am using the newest version of Bokeh and Python as of today (Bokeh 1.0.2, Python 3.7.1)
UPDATE #2
As per the comments, I pre-generated all the required glyphs ahead of time, so they are, in essence, "slots for data" instead of being generated on demand for any amount of datasets. As they are now persistent, this allows me to toggle them on/off with the .visible property easily. I now have six "slots" for data to be plotted with corresponding glyphs, and I added a function within the callback to update their respective data sources (in this case, changing a linear to a quadratic curve). I also updated Bokeh to the newest version (1.3.4). Note that this is specifically embeddable in a Jupyter Notebook.
Here is the code for reference:
import os
import pandas as pd
import numpy as np
import bokeh.plotting as bk
import bokeh.layouts as ly
import bokeh.models as md
import bokeh.colors as cl
import bokeh.palettes as plet
from bokeh.io import curdoc
from bokeh.io import show as io_show
from bokeh.models.widgets import CheckboxGroup, Select, Button, RadioGroup
from bokeh.plotting import output_file, show, figure, output_notebook, reset_output, curdoc
data_list = ["one", "two", "three", "four", "five", "six"]
data_list2 = ["one", "two"]
data_fac = [1, 2, 3, 4, 5, 6]
data_fac_dict = dict(zip(data_list,data_fac))
data_x = np.linspace(0,100)
df = pd.DataFrame(columns = data_list)
for case in data_list:
df[case] = data_x * data_fac_dict[case] + np.power(data_x, 3) * data_fac_dict[case]
def modify_doc(doc):
#Make colors
list_colors = plet.Dark2[len(data_list)]
dict_colors = dict(zip(data_list,list_colors))
p = bk.figure()
def make_line(case):
line = p.line(x = 'index', y = case, source = src_store[case], color = dict_colors[case])
return line
#Make six sources, make one line per source, and set them to invisible
src_store = dict()
list_lines = dict()
for case in data_list:
src_store[case] = md.ColumnDataSource(df[[case]])
list_lines[case] = make_line(case)
list_lines[case].visible = False
#First checkbox defaults to ticked, so let's show it by default.
list_lines["one"].visible = True
def modify_data(order):
#Modify the data and update the six sources' data with it
df = pd.DataFrame(columns = data_list)
src_store_new = dict()
data_new = dict()
for case in data_list:
df[case] = data_x * data_fac_dict[case] + np.power(data_x,order) * data_fac_dict[case]
data_new[case] = md.ColumnDataSource.from_df(df[[case]])
src_store[case].data = data_new[case]
def update(attr,old,new):
#Callback
print("update triggered")
#Get selection of lines to display
selection = list()
for i in wg_chk.active:
selection.append(data_list[i])
#Set visibility according to selection
for case in data_list:
list_lines[case].visible = case in selection
#Get line multiplier from radio buttons and update sources
order = wg_rad.active + 1
modify_data(order)
print(selection)
wg_rad = RadioGroup(labels=["x*0", "x*1"], active = 0)
wg_chk = CheckboxGroup(labels = data_list, active = [0]*len(data_list))
wg_chk.on_change('active', update)
wg_rad.on_change('active', update)
layout = ly.row(ly.column(wg_chk,wg_rad),p)
doc.add_root(layout)
bk.show(modify_doc, notebook_url='localhost:8888')
When you plot a Bokeh glyph, that glyph object has an associated data source. If you want the glyph to update, you need to update that existing datasource, i.e. modify it by setting it's .data property. The code above does not do that. It creates a new data source, that is not attached to or configured on anything, and then immediate throws it away (it's a local variable in a function, since nothing keeps a reference to it, it disappears when the function finishes).
You need to update whatever existing data source that you used initially:
source.data = new_data # plain python dict
And, at least as of Bokeh 1.3.4 new_data must be a plain Python dictionary. It is not supported to "migrate" a .data value from one CDS to another:
source1.data = source2.data # BAD! WILL NOT WORK
Attempting to do so will probably raise an explicit error in the near future. There is a from_df static method on ColumnDataSource you can use to convert DataFrames to the right kind of dict.

Python Bokeh: Slider callback in ColumnDataSource not update

I having a problem with the callback, I got everything worked expect the part when the graph doesn't update even thou the array is updated when I change the slider.
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
data = {'x_values': [0,0,2,2,4,4],
'y_values': [10,0,0,5,5,10]} #Seting up data
source = ColumnDataSource(data=data) # Map plot
plot = figure(title="Step Well",
tools="save,wheel_zoom")
plot.line('x_values', 'y_values',source=source)
def update_data(attrname, old, new):
Step = StepHeight.value
x = [0,0,2,2,4,4]
y = [10,0,0,Step,Step,10]
source.data = ColumnDataSource(dict(x=x, y=y))
source.on_change('value', update_data)
StepHeight = Slider(title="Step Height",
value=4.0,
start=2.0, end=6.0, step=0.2)
# Set up layouts and add to document
inputs = widgetbox(StepHeight)
layout = row(inputs, plot)
curdoc().title = "Sliders"
curdoc().add_root(layout)
You were trying to make source.data a column data source but source should be the column data source. Source.data is just a dictionary. I changed some things in your code and it should work fine now.
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
data = {'x_values': [0,0,2,2,4,4],
'y_values': [10,0,0,5,5,10]} #Seting up data
source = ColumnDataSource(data=data) # Map plot
plot = figure(title="Step Well",
tools="save,wheel_zoom")
plot.line('x_values', 'y_values',source=source)
def update_data(attrname, old, new):
y = [10,0,0,new,new,10]
source.data['y_values'] = y
StepHeight = Slider(title="Step Height",
value=4.0,
start=2.0, end=6.0, step=0.2)
StepHeight.on_change('value', update_data)
# Set up layouts and add to document
inputs = widgetbox(StepHeight)
layout = row(inputs, plot)
curdoc().title = "Sliders"
curdoc().add_root(layout)

Holoviews AdjointLayout with Bokeh Widgets

I am trying to append an AdjointLayout of a Scatter plot with two supporting histograms to a Bokeh dashboard. However, whenever trying to incorporate the two in a single row, the Bokeh widgets encounter display issues and the AdjointLayout never scales. Is this the current expected behavior or is here a different approach I need to take to currently accomplish this?
Minimal Example of the problem:
import numpy as np
import pandas as pd
import holoviews as hv
from bokeh.layouts import layout
from bokeh.models import Select
from bokeh.io import curdoc
renderer = hv.renderer('bokeh').instance(mode='server')
np.random.seed(10)
data = np.random.rand(100,4)
opts = {}
opts['color_index'] = 2
opts['size_index'] = 3
opts['scaling_factor'] = 50
points = hv.Points(data, vdims=['z', 'size']).opts(plot=opts)
fields = ['berry', 'cherry', 'dairy']
x = Select(title='X-Axis:', value=fields[0], options=fields)
y = Select(title='Y-Axis:', value=fields[1], options=fields)
dashboard = points + points[0.3:0.7, 0.3:0.7].hist()
app = renderer.get_plot(dashboard).state
dashboard = layout([
[[x, y], app],
])
curdoc().add_root(dashboard)
Using Bokeh 0.13.0 and Holoviews 1.10.5

Categories

Resources