Using Bokeh 1.4 and Python 3.7. I have a set of patches that I'd like to vary the color theme for based on two different keys (and labels) from the same ColumnDataSource. I want to stick to using one ColumnDataSource because my real file is quite large and the geometry (i.e. the xs and ys) are common between the two things i'd like to theme by.
See my working example:
from bokeh.io import show
from bokeh.models import ColumnDataSource,CustomJS, widgets, LinearColorMapper
from bokeh.palettes import RdBu6, Spectral11
from bokeh.plotting import figure
from bokeh.layouts import layout, column, row
source = ColumnDataSource(dict(
xs=[[1,2,2], [1,2,2], [3,4,4], [3,4,4]],
ys=[[3,3,4], [1,1,2], [3,3,4], [1,1,2]],
s1=[0, 50, 75, 50],
s2=[0, 25, 50, 75],
label_1=['Blue', 'Orangy', 'Red', 'Orangy'],
label_2=['S', 'P', 'E', 'C']
))
cmap1 = LinearColorMapper(palette='RdBu6', low = 0, high = 75)
cmap2 = LinearColorMapper(palette='Spectral11', low = 0, high = 75)
p = figure(x_range=(0, 7), y_range=(0, 5), plot_height=300)
patches = p.patches( xs='xs', ys='ys', fill_color={'field':'s1','transform':cmap1}
, legend_field='label_1', source=source)
b = widgets.Button(label = 'RdBu')
b.js_on_click(CustomJS(args=dict(b=b,source=source,patches=patches,cmap1=cmap1,cmap2=cmap2,p=p),
code="""if (b.label == 'RdBu')
{b.label='Spectral';
patches.glyph.fill_color = {field: 's2',transform:cmap2};}
else if (b.label == 'Spectral')
{b.label='RdBu';
patches.glyph.fill_color = {field: 's1',transform:cmap1}}"""
))
layout=column(row(p),row(b))
show(layout)
This yields this, and then this when clicking the button. You can see that the fill_color update part of the callback is working correctly as the colors change and even the colors in the legend change, but I have been unable to find a way instruct the CustomJS to properly update the legend entries so that in the second image there would be 4 entries with 'S','P','E' and 'C' as the legend labels.
From what I can tell, when I create the patches object and specify a legend_field argument, it constructs a legend for me with some sort of groupby/aggregate function to generate unique legend entries for me, and then it adds that legend to the figure object?
So that led me down the path of trying to drill down into p.legend:
p.legend.items #returns a list containing one LegendItem object
p.legend.items[0].label #returns a dictionary: {'field': 'label_1'}
I tried putting p.legend.items[0].label['field'] = 'label_2' outside of the callback and it worked as I hoped - the legend now reads S,P,E,C. But when I try putting that into the callback code it doesn't seem to update:
b.js_on_click(CustomJS(args=dict(b=b,source=source,patches=patches,cmap1=cmap1,cmap2=cmap2,p=p),
code="""if (b.label == 'RdBu')
{b.label='Spectral';
patches.glyph.fill_color = {field: 's2',transform:cmap2};
p.legend.items[0].label['field']='label_2'}
else if (b.label == 'Spectral')
{b.label='RdBu';
patches.glyph.fill_color = {field: 's1',transform:cmap1}
p.legend.items[0].label['field']='label_1'}"""
))
I feel like I'm very close but just missing one or two key things.... any advice/help appreciated!