Reputation: 111
The Problem
I wanted to create an interactive hbar plot, where you can switch between 3 different data sources, using a select widget, a python callback and a local bokeh serve. The plot with the default source renders fine, but when I switch to a different source, the y labels stay the same and the plot turns blank. Changing back to the original value on the select widget does not show the plot I started out with and stays blank. When I hard-code the inital source to a different one in the code, it renders just fine until I switch it by using the widget again, so the data itself seems to work fine individually.
Am I missing something? I read through many threads, docs and tutorials but can't find anything wrong with my code.
Here is what I have done so far:
I read a .csv and create 3 seperate dataframes and then convert then to columndatasources. Every source has 10 data entries with the columns "species", "ci_lower" and "ci_upper". Here is an example of one source (all three are built exactly the same way, with different taxon classes):
df = pd.read_csv(os.path.join(os.path.dirname(__file__), "AZA_MLE_Jul2018_utf8.csv",), encoding='utf-8')
m_df = df[df["taxon_class"]=="Mammalia"]
m_df = m_df.sort_values(by="mle", ascending=False)
m_df = m_df.reset_index(drop=True)
m_df = m_df.head(10)
m_df = m_df.sort_values(by="species", ascending=False)
m_df = m_df.reset_index(drop=True)
m_source = bp.ColumnDataSource(m_df)
I saved all 3 sources in a dict:
sources_dict={
"Mammalia": m_source,
"Aves": a_source,
"Reptilia": r_source
}
... and then created my variable called "source" that should change interactively with the "Mammalia" source as default:
source = sources_dict["Mammalia"]
Next I created a figure and added a hbar plot with the source variable as follows:
plot = bp.figure(x_range=(0, np.amax(source.data["ci_upper"])+5), y_range=source.data["species"])
plot.hbar(y="species", right="ci_lower", left="ci_upper", height=0.5, fill_color="#b3de69", source=source)
Then I added the select widget with a python callback:
def select_handler(attr, old, new):
source.data["species"]=sources_dict[new].data["species"]
source.data["ci_lower"]=sources_dict[new].data["ci_lower"]
source.data["ci_upper"]=sources_dict[new].data["ci_upper"]
select = Select(title="Taxonomic Class:", value="Mammalia", options=list(sources_dict.keys()))
select.on_change("value", select_handler)
curdoc().add_root(bk.layouts.row(plot, select))
I tried this:
My suspicion was that the error lies within the callback function, so I tried many different variants, all with the same bad result. I will list some of them here:
I tried using a python native dictionary:
new_data= {
'species': sources_dict[new].data["species"],
'ci_lower': sources_dict[new].data["ci_lower"],
'ci_upper': sources_dict[new].data["ci_upper"]
}
source.data=new_data
I tried assigning the whole data source, not just swapping the data
source=sources_dict[new]
I also tried using dict()
source.data = dict(species=sources_dict[new].data["species"], ci_lower=sources_dict[new].data["ci_lower"], ci_upper=sources_dict[new].data["ci_upper"])
Screenshots
Here is a screenshot of the initial plot, when I run the py file with bokeh serve --show file.py
And here one after changing the selected value:
Would greatly appreaciate any hints that could help me figure this out
Upvotes: 0
Views: 354
Reputation: 10652
Answering your question in the comment, changing data
does not change the ranges because y_range=some_thing
is just a convenience over creating a proper range class that's done behind the curtain.
Here's how you can do it manually. Notice that I don't touch x_range
at all - by default it's DataRange1d
that computes its start/end values automatically.
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import Select, ColumnDataSource
from bokeh.plotting import figure
d1 = dict(x=[0, 1], y=['a', 'b'])
d2 = dict(x=[8, 9], y=['x', 'y'])
ds = ColumnDataSource(d1)
def get_factors(data):
return sorted(set(data['y']))
p = figure(y_range=get_factors(d1))
p.circle(x='x', y='y', source=ds)
s = Select(options=['1', '2'], value='1')
def update(attr, old, new):
if new == '1':
ds.data = d1
else:
ds.data = d2
p.y_range.factors = get_factors(ds.data)
s.on_change('value', update)
curdoc().add_root(column(p, s))
Upvotes: 1