Search code examples
pythonplotbokeh

How to override the properties of a single glyph inside a Bokeh plot?


I have the following simple example, and I would like to reset the colour property of one of the circles to be red, as a separate process, after the renderer is initialised.

from bokeh.plotting import show

x = [2, 4, 8, 5, 9, 3]
y = [4, 8, 2, 7, 4, 6]
id = ['1001', '1002', '1003', '1004', '1005', '1006']

cds = ColumnDataSource(data={'x': x, 'y': y, 'id': id})

p = figure(width=300, height=200)
p.circle(x='x', y='y', source=cds, size=5, color='green')

# This is my issue?!
#p.circle[0].color = 'red'

show(p)

I'm aware that I can use the following API to achieve the result, but that is not an option considering what I would like to do next.

selected_circle = Circle(fill_alpha=1, fill_color="firebrick", line_color=None)
nonselected_circle = Circle(fill_alpha=0.2, fill_color="blue", line_color="firebrick")

Solution

  • If you want to change the color in a separate step, you can just hide the previous circle with a new one:

    x = [2, 4, 8, 5, 9, 3]
    y = [4, 8, 2, 7, 4, 6]
    id_ = ['1001', '1002', '1003', '1004', '1005', '1006']
    
    cds = ColumnDataSource(data={'x': x, 'y': y, 'id': id_})
    
    p = figure(width=300, height=200)
    p.circle(x='x', y='y', source=cds, size=5, color='green')
    
    # Coordinate of the red circle that will hide the first one:
    red_x = x[0]
    red_y = y[0]
    
    p.circle(x=red_x, y=red_y, size=5, color='red')
    
    show(p)
    

    But tbh I would prefer to specify the color from the beginning, like the option below.

    x = [2, 4, 8, 5, 9, 3]
    y = [4, 8, 2, 7, 4, 6]
    id_ = ['1001', '1002', '1003', '1004', '1005', '1006']
    color = ['red'] + ['green']*5  # add a color flag
    
    cds = ColumnDataSource(data={'x': x, 'y': y, 'id': id_, 'color': color})
    
    p = figure(width=300, height=200)
    
    p.circle(x='x', y='y', size=5, source=cds, color='color')
    
    show(p)
    

    EDIT

    In the example below, you can change the color of a circle with a button. It can be tested in a jupyter notebook.

    # Modified to work with Bokeh (v3.2.0) and JupyterLab (v3.3.2)
    
    from bokeh.layouts import column
    from bokeh.models import ColumnDataSource, CheckboxButtonGroup, CustomJS
    from bokeh.plotting import show, figure, output_notebook
    import pandas as pd
    output_notebook()
    
    def create_plot(doc):
        
        # create fake data
        x = [2, 4, 8, 5, 9, 3]
        y = [4, 8, 2, 7, 4, 6]
        id_ = ['1001', '1002', '1003', '1004', '1005', '1006']
        color = ['red'] + ['green']*5
        df = pd.DataFrame({'x': x, 'y': y, 'ids': id_, 'color': color})
    
        # create column data source
        cds = ColumnDataSource(data=df)
    
        # create figure
        p = figure(width=300, height=200)
        p.circle(x='x', y='y', size=5, source=cds, color='color')
    
        # Create widget (checkbox button)
        checkbox_button_group = CheckboxButtonGroup(labels=id_, active=[0])
    
        # Update the colors after a click on an id
        checkbox_button_group.js_on_change("active", CustomJS(code="""
            console.log('checkbox_button_group: active=' + this.active, this.toString())
            """))
    
        def set_col(row, red_ids):
            color = 'red' if row in red_ids else 'green'
            return color
    
        def update_data(df, red_ids=None):
            if red_ids is None:
                red_ids = []
            
            df['color'] = df['ids'].apply(lambda x: set_col(x, red_ids))
            cds.data = df
    
        def update(attr, old, new):
            red_ids = [checkbox_button_group.labels[i] for i in checkbox_button_group.active]
            update_data(df, red_ids)
    
        checkbox_button_group.on_change('active', update)
    
        # display button & plot in a column
        doc.add_root(column(checkbox_button_group, p))
    
    show(create_plot, notebook_url="localhost:8891")