Daniel Töws
Daniel Töws

Reputation: 357

Changing figure layout elements during bokeh serve

I have a hexgraph displaying a heatmap for my data. For this I use a ColumnDataSource containing the x,y information, as well as the values and the color for this item. Addtionally I provide a color bar to the hex graph showing the information which value will lead to which color. This should work for different data sets, however I am not able to update the color bar according to min and high values during run time.

Here is an example code (the simple myapp example provided by bokeh) where each button press should add a color bar with different values:

from random import random
from bokeh.layouts import column
from bokeh.models import Button, LinearColorMapper, ColorBar, BasicTicker
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc
from colorcet import CET_L18 as palette

# create a plot and style its properties
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None

# add a text renderer to our plot (no data yet)
r = p.text(x=[], y=[], text=[], text_color=[], text_font_size="20pt",
           text_baseline="middle", text_align="center")
i = 0

ds = r.data_source
# create a callback that will add a number in a random location
def callback():
    global i

    # BEST PRACTICE --- update .data in one step with a new dict
    new_data = dict()
    new_data['x'] = ds.data['x'] + [random()*70 + 15]
    new_data['y'] = ds.data['y'] + [random()*70 + 15]
    new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
    new_data['text'] = ds.data['text'] + [str(i)]
    ds.data = new_data
    color_mapper = LinearColorMapper(palette=palette, low=0, high=1000*i)

    color_bar = ColorBar(color_mapper=color_mapper, ticker=BasicTicker(),
                         label_standoff=12, border_line_color=None, location=(0, 0), orientation="horizontal")
    p.add_layout(color_bar, 'below')
    i = i + 1

# add a button widget and configure with the call back
button = Button(label="Press Me")
button.on_click(callback)
#show(p)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))

However, the color bar is not added during runtime. I also tried to add it before hand, then it will be displayed but not updated. How am I able to add a color bar and/or update it during runtime?

Upvotes: 0

Views: 709

Answers (1)

Oluwafemi Sule
Oluwafemi Sule

Reputation: 38942

First, move setup of the layout outside of the callback for the button and have the callback only update data.

Next, use the linear_cmap function from bokeh.transform to construct the linear color map. This function gives you a transformer to get the color for a value in the color palette.

Finally, update the high value of the transform in the callback

Complete Source

from random import random
from bokeh.layouts import column
from bokeh.models import Button, ColorBar, BasicTicker
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from colorcet import CET_L18 as palette
from bokeh.transform import linear_cmap

# create a plot and style its properties
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None

i = 0

color_mapper = linear_cmap(field_name='text', palette=palette, low=0, high=i)
color_bar = ColorBar(
    color_mapper=color_mapper['transform'], 
    ticker=BasicTicker(),
    label_standoff=12,
    border_line_color='black',
    location=(0, 0),
    orientation="horizontal")

# add a text renderer to our plot (no data yet)
ds = ColumnDataSource(dict(x=[],y=[],text=[]))
r = p.text(x='x', y='y', text='text', text_color=color_mapper, text_font_size="20pt",
           text_baseline="middle", text_align="center", source=ds)
p.add_layout(color_bar, 'below')

# create a callback that will add a number in a random location
def callback():
    global i

    i = i + 1 
    # BEST PRACTICE --- update .data in one step with a new dict
    new_data = dict()
    new_data['x'] = ds.data['x'] + [random()*70 + 15]
    new_data['y'] = ds.data['y'] + [random()*70 + 15]
    new_data['text'] = ds.data['text'] + [i]
    ds.data = new_data
    color_mapper['transform'].high = i

# add a button widget and configure with the call back
button = Button(label="Press Me")
button.on_click(callback)
#show(p)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))

Upvotes: 1

Related Questions