Change colour of a glyph from js

Hi all,

I am using bokeh 2.1.1. I am trying to change the colour of a glyph using js. A checkbox is used to change the colour mapper attached to the glyph.
The glyph is initialised with a CategoricalColorMapper and the correct colour is shown. I am trying to change the colour mapper to a linear_cmap when the checkbox “heat map” is ticked. The new colours are not picked up.

Could you help me to achieve it?

See code example below.

Thanks

from bokeh.plotting import figure

from bokeh.plotting import output_file

from bokeh.plotting import show

from bokeh.models import ColumnDataSource

from bokeh.models import CategoricalColorMapper

from bokeh.transform import linear_cmap

from bokeh.layouts import layout

from bokeh.models.widgets import CheckboxGroup

from bokeh.models.callbacks import CustomJS

output_file("bokeh_change_colour_mapper_from_js.html", title="bokeh colour mapper", mode='inline')

heatmap_color_palette = [

    '#0A3278', # (10, 50, 120)

    '#0F4BA5', # (15, 75, 165)

    '#1E6EC8', # (30, 110, 200)

    '#3CA0F0', # (60, 160, 240)

    '#50B4FA', # (80, 180, 250)

    '#82D2FF', # (130, 210, 255)

    '#A0F0FF', # (160, 240, 255)

    '#FFE878', # (255, 232, 120)

    '#FFC03C', # (255, 192, 60)

    '#FFA000', # (255, 160, 0)

    '#FF5F00', # (255, 95, 0)

    '#FF3200', # (255, 50, 0)

    '#E11400', # (225, 20, 0)

    '#C00000', # (192, 0, 0)

    '#A50000' # (165, 0, 0)

]

circle_data = {

    'x': [1, 9, 4, 7, 2, 5, 3, 6],

    'y': [1, 9, 4, 7, 2, 5, 3, 6],

    'size': [9, 9, 6, 6, 9, 9, 6, 6],

    'desc': ['desc_1', 'desc_9', 'desc_4', 'desc_7', 'desc_2', 'desc_5', 'desc_3', 'desc_6'],

    'sensitivity': [2, 7, 9, 9, 1, 2, 8, 6]

}

color_mapper_from_sensitivity = linear_cmap('sensitivity', heatmap_color_palette, 0, 10)

color_mapper_from_desc = CategoricalColorMapper(

    factors=['desc_1', 'desc_9', 'desc_4', 'desc_7', 'desc_2', 'desc_5', 'desc_3', 'desc_6'],

    palette=['#330000', '#FF0000', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#7F00FF', '#FF007F']

)

p = figure(

    x_axis_type='datetime',

    plot_width=800, plot_height=800,

    background_fill_color='#DCDCDC', background_fill_alpha=0.4

)

circle_data_source = ColumnDataSource(circle_data)

circle_renderer = p.circle(

    x='x', y='y', size='size', source=circle_data_source,

    fill_color={'field': 'desc', 'transform': color_mapper_from_desc},

    muted_alpha=0.2,

    line_color=None)

code = """

// the event that triggered the callback is cb_obj:

// The event type determines the relevant attributes

//console.log('Tap event occurred at x-position: ' + cb_obj.x)

console.log('active: ' + this.active)

console.log('active: ' + cb_obj.active)

if (this.active.length) {

    circle_renderer.glyph.color = color_mapper_from_sensitivity

}

else {

    circle_renderer.glyph.color = {'field': 'train_desc', 'transform': color_mapper_from_desc}

}

circle_data_source.change.emit()

circle_renderer.change.emit()

p.change.emit()

return

"""

callback = CustomJS(

    args=dict(

        p=p, circle_renderer=circle_renderer,

        circle_data_source=circle_data_source,

        color_mapper_from_desc=color_mapper_from_desc,

        color_mapper_from_sensitivity=color_mapper_from_sensitivity

    ),

    code=code

)

checkbox_group = CheckboxGroup(labels=["Heat map"], active=[])

checkbox_group.js_on_click(handler=callback)

l = layout([

    [checkbox_group],

    [p],

])

show(l)

Two main errors:

  • You’re using color, that controls the outline color, instead of fill_color
  • You’re using 'train_desc' instead of 'desc'

Plus some minor issues that I won’t describe.
Here’s a working version:

from bokeh.layouts import layout
from bokeh.models import CategoricalColorMapper
from bokeh.models import ColumnDataSource
from bokeh.models.callbacks import CustomJS
from bokeh.models.widgets import CheckboxGroup
from bokeh.plotting import figure
from bokeh.plotting import show
from bokeh.transform import linear_cmap, transform

heatmap_color_palette = [
    '#0A3278',  # (10, 50, 120)
    '#0F4BA5',  # (15, 75, 165)
    '#1E6EC8',  # (30, 110, 200)
    '#3CA0F0',  # (60, 160, 240)
    '#50B4FA',  # (80, 180, 250)
    '#82D2FF',  # (130, 210, 255)
    '#A0F0FF',  # (160, 240, 255)
    '#FFE878',  # (255, 232, 120)
    '#FFC03C',  # (255, 192, 60)
    '#FFA000',  # (255, 160, 0)
    '#FF5F00',  # (255, 95, 0)
    '#FF3200',  # (255, 50, 0)
    '#E11400',  # (225, 20, 0)
    '#C00000',  # (192, 0, 0)
    '#A50000'  # (165, 0, 0)
]
circle_data = {
    'x': [1, 9, 4, 7, 2, 5, 3, 6],
    'y': [1, 9, 4, 7, 2, 5, 3, 6],
    'size': [9, 9, 6, 6, 9, 9, 6, 6],
    'desc': ['desc_1', 'desc_9', 'desc_4', 'desc_7', 'desc_2', 'desc_5', 'desc_3', 'desc_6'],
    'sensitivity': [2, 7, 9, 9, 1, 2, 8, 6]
}
color_mapper_from_sensitivity = linear_cmap('sensitivity', heatmap_color_palette, 0, 10)
color_mapper_from_desc = transform('desc', CategoricalColorMapper(
    factors=['desc_1', 'desc_9', 'desc_4', 'desc_7', 'desc_2', 'desc_5', 'desc_3', 'desc_6'],
    palette=['#330000', '#FF0000', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#7F00FF', '#FF007F']
))
p = figure(
    x_axis_type='datetime',
    plot_width=800, plot_height=800,
    background_fill_color='#DCDCDC', background_fill_alpha=0.4
)
circle_data_source = ColumnDataSource(circle_data)
circle_renderer = p.circle(
    x='x', y='y', size='size', source=circle_data_source,
    fill_color=color_mapper_from_desc,
    muted_alpha=0.2,
    line_color=None
)
callback = CustomJS(
    args=dict(renderer=circle_renderer,
              desc_cm=color_mapper_from_desc,
              sensitivity_cm=color_mapper_from_sensitivity),
    code="renderer.glyph.fill_color = this.active.length ? sensitivity_cm : desc_cm;"
)
checkbox_group = CheckboxGroup(labels=["Heat map"], active=[])
checkbox_group.js_on_change('active', callback)

show(layout([[checkbox_group], [p]]))

Note that if you prefer to use tuples for colors, you should be able to use bokeh.colors.rgb.RGB. I think you can use it just as is, but maybe you will have to call to_hex in a list comprehension.

Thank you for the quick reply.