T. Moser
T. Moser

Reputation: 111

Switching data source for bokeh serve hbar plot not working: Blank plot

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

Upvotes: 0

Views: 354

Answers (1)

Eugene Pakhomov
Eugene Pakhomov

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

Related Questions