Make the App faster and more responsive?

Hello everyone.

I was finally successful in making a choropleth map with a time slider in bokeh. There is only one thing that I would like to improve: Every time I move the slider I have to wait a lot to obtain the new map, and I think that there must be a way to optimize this.

This is how my app looks like:

And this is the code that I wrote in the callback function:

def callback(attr, old, new):
    N = slider.value
    slider.title = 'Number of Incidences in ' + column_list[N]
    p.patches('xs', 'ys', fill_alpha=0.7,
              fill_color={'field': column_list[N], 'transform': mapper},
              line_color='black', line_width=0.2, source=geo_source)
    hover = p.select_one(HoverTool)
    hover.point_policy = "follow_mouse"
    hover.tooltips = [
        ("Municipality", "@Municipality"),
        ("Incidences", "@{" + column_list[N] + "}"),
    ]

slider.on_change('value', callback)

Basically, every time I move the slider, I read a different column from the column_list. I think that the problem is that I create the patches from scratch every time I move the slider. Is there a way to just change the color?

I also have a second question: is there a way to not show the number next to the slider?

Thanks in advance.

Lefteris

Hi,

The first issue with your code is that you call “p.patches” on each slider value change - it doesn’t remove old patches, just adds new ones.

To fix this, you can call “p.patches” once when you set up the document, store its return value, and change this value’s attributes in the callback.

The second issue is that you do unnecessary work - you always select the hover tool.

To fix this, you can store the hover tool instance outside of the callback.

The third potential issue is that if you move the slider too fast, you’ll see some sluggishness - the amount depends on the server and on the connection.

If it’s indeed the issue, you can use some kind of throttling - preferably in the UI (for example, like here https://stackoverflow.com/questions/38375961/throttling-in-bokeh-application).

Regards,

Eugene

···

On Thursday, November 23, 2017 at 9:34:08 PM UTC+7, Lefteris Koulierakis wrote:

Hello everyone.

I was finally successful in making a choropleth map with a time slider in bokeh. There is only one thing that I would like to improve: Every time I move the slider I have to wait a lot to obtain the new map, and I think that there must be a way to optimize this.

This is how my app looks like:

And this is the code that I wrote in the callback function:

def callback(attr, old, new):
    N = slider.value
    slider.title = 'Number of Incidences in ' + column_list[N]
    p.patches('xs', 'ys', fill_alpha=0.7,
              fill_color={'field': column_list[N], 'transform': mapper},
              line_color='black', line_width=0.2, source=geo_source)
    hover = p.select_one(HoverTool)
    hover.point_policy = "follow_mouse"
    hover.tooltips = [
        ("Municipality", "@Municipality"),
        ("Incidences", "@{" + column_list[N] + "}"),
    ]

slider.on_change('value', callback)

Basically, every time I move the slider, I read a different column from the column_list. I think that the problem is that I create the patches from scratch every time I move the slider. Is there a way to just change the color?

I also have a second question: is there a way to not show the number next to the slider?

Thanks in advance.

Lefteris

Thank you very much for your reply.

I don’t understand your first mark. Could you tell me how you would write this instead?

As for the second remark, the Hovertool I think that should be inside the callback, otherwise it will only show the values of a specific column. You can see how I’ve implemented this app in the following code. I think that the optimal solution would be to access somehow only the fill_color inside the callback.

N = 0


TOOLS = "pan,wheel_zoom,reset,hover,save"
color_column = '2010-01'

geojson = shp_brabant_lim_updated.to_json()

mapper = LinearColorMapper(palette=Magma256[::-1], low=shp_brabant_lim_updated[color_column].min(), high=shp_brabant_lim_updated[color_column].max())

geo_source = GeoJSONDataSource(geojson=geojson)
p = figure(title="Incidences in "+color_column, tools = TOOLS, toolbar_sticky=False, plot_width=600, plot_height=400)
p.patches('xs', 'ys', fill_alpha=0.7,
          fill_color={'field': color_column, 'transform': mapper},
          line_color='black', line_width=0.2, source = geo_source)

hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
    ("Municipality", "@Municipality"),
    ("Incidences", "@{"+color_column+"}"),
    ]

color_bar = ColorBar(color_mapper= mapper, label_standoff=12, border_line_color=None, location=(0,0))
p.add_layout(color_bar, 'right')

slider = Slider(start = 0, end = len(column_list)-1, value = N, step = 1, title = 'Number of Points in '+ column_list[N])

def callback(attr, old, new):
    N = slider.value
    slider.title = 'Number of Incidences in ' + column_list[N]
    p.patches('xs', 'ys', fill_alpha=0.7,
              fill_color={'field': column_list[N], 'transform': mapper},
              line_color='black', line_width=0.2, source=geo_source)
    hover = p.select_one(HoverTool)
    hover.point_policy = "follow_mouse"
    hover.tooltips = [
        ("Municipality", "@Municipality"),
        ("Incidences", "@{" + column_list[N] + "}"),
    ]

slider.on_change('value', callback)

layout = column(p, slider)
curdoc().add_root(layout)

Thank you very much,

Lefteris

Consider this code:

from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import HoverTool, Slider, ColumnDataSource
from bokeh.plotting import figure

ds = ColumnDataSource(data=dict(xs=[[0, 1, 1, 0], [1, 2, 2, 1]],
                                ys=[[0, 0, 1, 1], [1, 1, 2, 2]],
                                color0=['red', 'blue'],
                                color1=['green', 'yellow']))

initial_N = 0
total_N = 2

def color_column(n):
    return 'color{}'.format(n)

def tooltip(n):
    return [("Xs", "@xs"),
            ("Color", '@{{{}}}'.format(color_column(n)))]

def slider_title(n):
    return 'Color index: {}'.format(n)

p = figure(title="Slider changes glyph example", tools="hover",
           toolbar_sticky=False, plot_width=600, plot_height=400)

patches_renderer = p.patches('xs', 'ys', fill_alpha=0.7,
                             fill_color=dict(field=color_column(initial_N)),
                             line_color='black', line_width=0.2, source=ds)

hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = tooltip(initial_N)

slider = Slider(start=0, end=total_N - 1, step=1,
                value=initial_N, title=slider_title(initial_N))

def callback(attr, old, new):
    slider.title = slider_title(new)
    g = patches_renderer.glyph
    # `fill_color` is a plain Python `dict`, so it doesn't signal the document
    # about its changes. We have to trigger the change manually by setting the
    # whole field to a new value.
    g.fill_color = {**g.fill_color, 'field': color_column(new)}
    hover.tooltips = tooltip(new)

slider.on_change('value', callback)

layout = column(p, slider)
curdoc().add_root(layout)

Note that I change the glyph’s fill property and tooltips of the hover tool. I don’t do any unnecessary querying or plotting.

Also, if you don’t really need Bokeh server you can use this example to achieve the same: https://stackoverflow.com/questions/37059055/how-to-change-the-color-of-a-glyph-using-callback

Regards,

Eugene

···

On Thursday, November 23, 2017 at 9:53:00 PM UTC+7, Lefteris Koulierakis wrote:

Thank you very much for your reply.

I don’t understand your first mark. Could you tell me how you would write this instead?

As for the second remark, the Hovertool I think that should be inside the callback, otherwise it will only show the values of a specific column. You can see how I’ve implemented this app in the following code. I think that the optimal solution would be to access somehow only the fill_color inside the callback.

N = 0


TOOLS = "pan,wheel_zoom,reset,hover,save"
color_column = '2010-01'

geojson = shp_brabant_lim_updated.to_json()

mapper = LinearColorMapper(palette=Magma256[::-1], low=shp_brabant_lim_updated[color_column].min(), high=shp_brabant_lim_updated[color_column].max())

geo_source = GeoJSONDataSource(geojson=geojson)
p = figure(title="Incidences in "+color_column, tools = TOOLS, toolbar_sticky=False, plot_width=600, plot_height=400)
p.patches('xs', 'ys', fill_alpha=0.7,
          fill_color={'field': color_column, 'transform': mapper},
          line_color='black', line_width=0.2, source = geo_source)

hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
    ("Municipality", "@Municipality"),
    ("Incidences", "@{"+color_column+"}"),
    ]

color_bar = ColorBar(color_mapper= mapper, label_standoff=12, border_line_color=None, location=(0,0))
p.add_layout(color_bar, 'right')

slider = Slider(start = 0, end = len(column_list)-1, value = N, step = 1, title = 'Number of Points in '+ column_list[N])

def callback(attr, old, new):
    N = slider.value
    slider.title = 'Number of Incidences in ' + column_list[N]
    p.patches('xs', 'ys', fill_alpha=0.7,
              fill_color={'field': column_list[N], 'transform': mapper},
              line_color='black', line_width=0.2, source=geo_source)
    hover = p.select_one(HoverTool)
    hover.point_policy = "follow_mouse"
    hover.tooltips = [
        ("Municipality", "@Municipality"),
        ("Incidences", "@{" + column_list[N] + "}"),
    ]

slider.on_change('value', callback)


layout = column(p, slider)
curdoc().add_root(layout)

Thank you very much,

Lefteris

Thank you very much! You were completely right!
I adjusted my code according to your example, and everything runs smoothly. I can finally present this nice visualization.

I think that the magic is here:

g.fill_color = {**g.fill_color, 'field': color_column(new)}

Again I am very thankful for your remarks.

Lefteris

I’m glad it worked. :slight_smile:

Yes, that’s the most interesting line. “patches_renderer.glyph” is an instance of “HasProps”, so each time you change one of its properties in Python, the changes will be propagated to the UI.

Eugene

···

On Thursday, November 23, 2017 at 11:06:31 PM UTC+7, Lefteris Koulierakis wrote:

Thank you very much! You were completely right!
I adjusted my code according to your example, and everything runs smoothly. I can finally present this nice visualization.

I think that the magic is here:

g.fill_color = {**g.fill_color, 'field': color_column(new)}

Again I am very thankful for your remarks.

Lefteris