Hi, I will try to be succinct.
What I am trying to accomplish:
Change the color scheme with customized color maps after a button click.
My method:
According to the user guide, I try to style my circles by replacing the glyphs with clones.
Styling plot elements — Bokeh 3.8.0 Documentation
My setting:
I’m running with bokeh server.
Unexpected behavior:
If I update the glyphs outside of a button click callback, then the glyphs get updated fine.
However, if I update the glyphs within a button click callback, then the color changes will break.
My code:
Here is a minimum code snippet to reproduce my case:
from bokeh.models import ColumnDataSource, Div, Button
from bokeh.layouts import row as bokeh_row, column as bokeh_column
from bokeh.plotting import figure
from bokeh.transform import CategoricalColorMapper
from bokeh.io import curdoc
import pandas as pd
import numpy as np
N = 100 # or any number of rows you want
df = pd.DataFrame({
'x': np.random.uniform(0, 20, N),
'y': np.random.uniform(0, 20, N),
'color1': np.random.choice(['a', 'b'], N),
'color2': np.random.choice(['a', 'b'], N)
})
source = ColumnDataSource(df)
print(source)
cmap1 = CategoricalColorMapper(palette=[[32,14,23], [121,144,77]], factors=['a', 'b'])
cmap2 = CategoricalColorMapper(palette=["red", "blue"], factors=['a', 'b'])
TOOLS="hover,crosshair,pan,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select,examine,fullscreen,help"
p = figure(tools=TOOLS)
color_mapper1 = {"field": "color1", "transform": cmap1}
color_mapper2 = {"field": "color2", "transform": cmap2}
renderer = p.circle(
source=source,
x="x", y="y",
radius=0.25,
fill_color="yellow"
#fill_color=color_mapper1
)
def color_callback1():
print("current field:", renderer.glyph.fill_color["field"])
renderer.glyph = renderer.glyph.clone(fill_alpha=1, fill_color=color_mapper1, line_color=None)
renderer.selection_glyph = renderer.glyph.clone(fill_alpha=1, fill_color=color_mapper1, line_color=None)
renderer.nonselection_glyph = renderer.glyph.clone(fill_alpha=0.2, fill_color=color_mapper1, line_color=None)
def color_callback2():
renderer.glyph.fill_color = color_mapper1
def color_callback3():
renderer.glyph.fill_color = color_mapper2
def print_field():
#renderer.glyph.fill_color = color_mapper1
print(renderer.glyph.fill_color["field"])
div.text = renderer.glyph.fill_color["field"]
color_button1 = Button(label="update color1", button_type="primary")
color_button1.on_click(color_callback1)
print_button = Button(label="print color", button_type="primary")
print_button.on_click(print_field)
color_button2 = Button(label="color to 1", button_type="primary")
color_button2.on_click(color_callback2)
color_button3 = Button(label="color to 2", button_type="primary")
color_button3.on_click(color_callback3)
div = Div(text="Hello")
curdoc().add_root(bokeh_column([p, bokeh_row(color_button1, print_button, color_button2, color_button3), div]))
renderer.glyph = renderer.glyph.clone(fill_alpha=1, fill_color=color_mapper2, line_color=None)
print(renderer.glyph.fill_color["field"]
Behavior:
a. initially, the glyphs are colored just fine by replacing the glyphs with clones outside of button click call back.
b. color changes are responsive if I just replace the color map (click “color to 1” and “color to 2” back and forth).
c. the color update is broken if I try to update the glyphs through the cloning method (not replacing the color map but replace the glyphs with clones) in a button callback (click “update color1”). Even though I do see that the color map is updated (click “print color”). Reassigning color_map would break too.
Root cause:
The glyphs are indeed replaced with new glyphs in the renderer. However this is not reflected in the plot. The plot is stuck with the old glyph.
This behavior is quite weird to be honest. I would expect that if a glyph styling through replacing with clones to behave consistently regardless of whether the update is called within a button callback or not.
Is there any way to work around this? Basically, I want to update my color map through bespoke color mapper.