Search code examples
bokehbokehjs

Change ColorMapper via callback JS in standalone Bokeh plot


I would like to create a standalone document, similar to the server app example 'Crossfilter': to select different columns for coloring the circles and to update the colorbar as well.

I define a CustomJS with the code below, where I create a new LinearColorMapper with the calculated low and high values. (For debugging purposes, I intentionally add different palette than the one set via the Python code).

var low = Math.min.apply(Math,source.data[cb_obj.value]);
var high = Math.max.apply(Math,source.data[cb_obj.value]);
var color_mapper = new Bokeh.LinearColorMapper({palette:'Viridis5', low:low, high:high});
cir.glyph.fill_color = {field: cb_obj.value, transform: color_mapper};
cir.glyph.line_color = {field: cb_obj.value, transform: color_mapper};
color_bar.color_mapper = color_mapper;
source.change.emit();

As a result, when selecting the column, the circles become white, the line black, the ticks of the color bar change correctly, but the palette does not change.

Could you help me setting the proper attributes in the callback? Thank you in advance.

Original state, color mapper set from Python code

Original state, color mapper set from Python code

After selecting the column 'd'

After selecting the column 'd'

I created a "minimal working example" to show how far I got. The full project with the template it can be found here: https://github.com/pintergreg/bokehjscolormapperexample


Solution

  • It looks like you cannot reference a colour pallet like this in BokehJS. Just pass Viridis5 variable to JS callback and it works (tested on Bokeh v1.0.4):

    import pandas as pd
    from bokeh.models import ColumnDataSource, ColorBar, Select, CustomJS
    from bokeh.plotting import figure, show
    from bokeh.layouts import gridplot
    from bokeh.palettes import Spectral5, Viridis5
    from bokeh.transform import linear_cmap
    from bokeh.embed import components
    from jinja2 import Environment, FileSystemLoader
    
    df = pd.DataFrame({"a": [2, 6, 5, 3, 7, 8, 1, 9, 2, 4],
                       "b": [3, 5, 7, 1, 0, 6, 5, 4, 2, 9],
                       "c": [11, 12, 13, 14, 11, 13, 15, 14, 15, 12],
                       "d": [21, 23, 24, 25, 21, 22, 23, 24, 25, 22]})
    source = ColumnDataSource(df)
    
    mapper = linear_cmap(field_name = "c", palette = Spectral5,
                         low = min(df["c"]), high = max(df["c"]))
    
    fig = figure(plot_width = 400, plot_height = 400)
    
    cir = fig.circle(x = "a", y = "b", size = 12,
                     source = source, line_color = mapper, color = mapper)
    color_bar = ColorBar(color_mapper = mapper["transform"], width = 8,
                         location = (0, 0))
    fig.add_layout(color_bar, "right")
    
    codec = """
                var low = Math.min.apply(Math,source.data[cb_obj.value]);
                var high = Math.max.apply(Math,source.data[cb_obj.value]);
                var color_mapper = new Bokeh.LinearColorMapper({palette:viridis5, low:low, high:high});
                cir.glyph.fill_color = {field: cb_obj.value, transform: color_mapper};
                cir.glyph.line_color = {field: cb_obj.value, transform: color_mapper};
                color_bar.color_mapper.low = low;
                color_bar.color_mapper.high = high;
                color_bar.color_mapper.palette = viridis5;
                source.change.emit();
            """
    cb_cselect_c = CustomJS(args = dict(cir = cir, source = source, color_bar = color_bar, viridis5 = Viridis5),
                            code = codec)
    
    c_select = Select(title = "Select variable for color: ", value = "None",
                      options = ["c", "d"], callback = cb_cselect_c)
    
    layout = gridplot([[fig], [c_select]])
    show(layout)
    
    # env = Environment(loader=FileSystemLoader("."))
    # template = env.get_template("template.html")
    #
    # script, div = components(layout)
    #
    # with open("output.html", "w") as f:
    #     print(template.render(script=script, div=div), file=f)
    

    You also need to manually add this line to header section of the generated HTML file:

    <script type="text/javascript" src="http://cdn.bokeh.org/bokeh/release/bokeh-api-1.0.4.min.js"></script>