Python callbacks with bokeh server - Select widget returning blank plots

Hi, I recently learned how to use bokeh and I am having trouble making my python callbacks work with the bokeh server.

Here is my code (I’m building a candlestick chart with weather data):

from bokeh.client import push_session
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, DatetimeTickFormatter, CDSView, BooleanFilter,HoverTool
from bokeh.models import Select
from bokeh.layouts import column, widgetbox
from make_datasets import hourly_all_winters


# Convert dataset to a column data source
source = ColumnDataSource(data={
    'date': hourly_all_winters['date'],
    'max': hourly_all_winters['max'],
    'min': hourly_all_winters['min'],
    'first': hourly_all_winters['first'],
    'last': hourly_all_winters['last'],
    'average': hourly_all_winters['mean'],
    'winter': hourly_all_winters['winter'],
})

# Create first plot and select only the box_zoom and reset tools
y_range = (-30, 30)
p = figure(y_range=y_range, x_axis_type="datetime", plot_width=950, plot_height=200,
            title=f"Daily temperature variations - winter {source.data['winter'][0]}",
            x_axis_label='Months', y_axis_label='Temperature in °C',
            tools="box_zoom,reset")
p.xaxis.formatter = DatetimeTickFormatter(months=['%B'])

# Create the range line glyph
p.segment('date', 'max', 'date', 'min', source=source, color="black")

# Create the ascending bars glyph - we need to create a view of our data with a boolean mask to only plot the data
# we want
booleans_inc = [True if last > first else False for last, first in zip(source.data['last'], source.data['first'])]
view_inc = CDSView(source=source, filters=[BooleanFilter(booleans_inc)])

booleans_dec = [True if last < first else False for last, first in zip(source.data['last'], source.data['first'])]
view_dec = CDSView(source=source, filters=[BooleanFilter(booleans_dec)])

w = 365 * 60 * 2000

p.vbar('date', w, 'first', 'last', source=source, view=view_inc, fill_color="#2c8cd1", line_color="#2c8cd1")
p.vbar('date', w, 'first', 'last', source=source, view=view_dec, fill_color="#F2583E", line_color="#F2583E")

# Create a hover tool so that we can see min, max, first and last values for each record
# over the plot
hover = HoverTool(tooltips=[("First", "@first{first.1f}"),
                            ("Last", "@last{last.1f}"),
                            ("Min", "@min{min.1f}"),
                            ("Max", "@max{max.1f}"), ], mode='vline')

p.add_tools(hover)

# Make a slider object
slider = Select(options=['', '2013-2014', '2014-2015', '2015-2016', '2016-2017', '2017-2018', '2018-2019'],
                value='', title='Winter')

def update_plot(attr, old, new):
    if new == '':
        source.data = ColumnDataSource(data={'date': hourly_all_winters['date'],
                                             'max': hourly_all_winters['max'],
                                             'min': hourly_all_winters['min'],
                                             'first': hourly_all_winters['first'],
                                             'last': hourly_all_winters['last'],
                                             'average': hourly_all_winters['mean'],
                                             'winter': hourly_all_winters['winter'],}).data
    else:
        new_source = ColumnDataSource(data={'date': hourly_all_winters[hourly_all_winters.winter == winter]['date'],
                      'max': hourly_all_winters[hourly_all_winters.winter == new]['max'],
                      'min': hourly_all_winters[hourly_all_winters.winter == new]['min'],
                      'first': hourly_all_winters[hourly_all_winters.winter == new]['first'],
                      'last': hourly_all_winters[hourly_all_winters.winter == new]['last'],
                      'average': hourly_all_winters[hourly_all_winters.winter == new]['mean'],
                      'winter': hourly_all_winters[hourly_all_winters.winter == new]['winter'],
                      })
        source.data = new_source.data

# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)

# put the button and plot in a layout and add to the document
curdoc().add_root(column(widgetbox(slider), p))

show(curdoc())

I tried both starting with my “2013-2014” data and the full dataset but every time I run my code I get the first visualization working, but selecting other data with the dropdown only returns a blank plot. Any thoughts on what I may have missed? I also tried using push_session() to see if it’d help with no luck.

Hi @CICarlier I am not sure what resources you drew inspiration from, but this approach of using push_session and loop_until_closed has been affirmatively discouraged for a very long time, and in fact loop_until_closed will be completely removed as public API in the upcoming 2.0 release. This approach of pushing applications to a Bokeh server from “outside” is officially unsupported.

Bokeh server application code (i.e the code for creating a new Document) should be run in the same process that the Bokeh server is running in. You can find more information about Bokeh server applications in the User’s Guide:

https://docs.bokeh.org/en/latest/docs/user_guide/server.html

I would advise studying that, updating your approach to a supported single-module, directory format, or embedded server approach, and then posing any questions or difficulties here.

Hi @Bryan - I also tried without push_session but it did not work either hence me experimenting with other things. I’ll edit my post with the other code.

@CICarlier So this line:

show(curdoc())

Suggest some confusion. In a Python script, show is for generating standalone output, i.e. purely static HTML, JS, and, CSS with no Bokeh server involved. If you want to run this code as a Bokeh server application, you have to run it with the Bokeh server. Delete that last line, then:

bokeh serve --show myapp.py

in the shell console.

Oh, I also just noticed this:

source.data = new_source.data

That will not work. It’s not possible to “re-home” a .data from one CDS to another. Starting in Bokeh 2.0 this will generate an immediate exception. (Currently leads to silent failure). The value for .data always needs to be a plain Python dict:

source.data = new_data_dict  # Plain Python dict

thank you so much @Bryan - these two changes made the trick, interactivity works now. Really appreciate your help!

1 Like