0

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

initial plot

And here one after changing the selected value:

after switching

Would greatly appreaciate any hints that could help me figure this out

2
  • At a glance, it seems that the root cause is the ranges of the figure - you set them statically but you never update them. If that's not the case, please provide the code as a single block - splitting it into multiple incomplete sections makes it near impossible to reproduce. Please also provide at least some example data. In other words, a minimal reproducible example is required. Commented Oct 11, 2020 at 15:07
  • Interesting, so writing y_range=source.data["species"] in the figure and then in the callback changing the source.data["species"] does not change the y_range too? How would I trigger a "re-reading" of the source for the figure? Commented Oct 11, 2020 at 16:10

1 Answer 1

1

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))
Sign up to request clarification or add additional context in comments.

1 Comment

you just saved my day! Now that you mention it. it seems pretty obvious. Never seen this being used in an example online though and kind of hard to figure out for a beginner, so thank you very much

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.