Search code examples
pythonbokehalphamarker

make marker fill_alpha value-dependent in Bokeh


In Bokeh, is it possible to have the marker alpha vary with values in a specified field?

For example, to vary color and marker by a field:

from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.transform import factor_cmap,factor_mark

df = some dataframe
palette = ['#440154', '#404387', '#29788E', '#22A784', '#79D151', '#FDE724']
bok_sym = ['circle','asterisk','square_x','circle_x','diamond','hex']
cat_lst = list(df['cat_field'].unique())

df_cds = ColumnDataSource(data=df)

fig = figure(some kwargs)
fig.scatter(x='x',y='y',
            source = df_cds,
            marker = factor_mark('cat_field',bok_sym,cat_lst)
            fill_color = factor_cmap('cat_field',palette,cat_lst),
           )
show(fig)

There does not appear to be a similarly explicit function for alpha, but bokeh.transform does provide a generic transform

https://docs.bokeh.org/en/latest/docs/reference/transform.html

Which I've attempted with the following additions:

from bokeh.transform import transform

alph_lst = [0.2,0.9,0.9,0.9,0.9,0.9]

fig.scatter(x='x',y='y',
                source = df_cds,
                marker = factor_mark('cat_field',bok_sym,cat_lst)
                fill_color = factor_cmap('cat_field',palette,cat_lst),
                fill_alpha = transform('cat_field',dict(zip(cat_lst,alph_lst))),
               )

But without success.

Cheers

EDIT:

I'll note that I've already (unsuccessfully) tried to pass transparency as part of the hex code:

palette = ['#44015433', '#404387E6', '#29788EE6', '#22A784E6', '#79D151E6', '#FDE724E6']


Solution

  • As you stated in your question you can definitely achieve this with a custom Transform. They're a little cumbersome to wrap your head around (in my opinion) but hopefully this code should clear it up.

    Essentially you need 3 steps create a custom javascript function and apply it to a field name in bokeh:

    1. Write javascript code that achieve the transformation you want. In our case we're just mapping categorical values to alpha levels in a dictionary.
    2. Put this javascript snippet into a CustomJSTransform object, feeding it the necessary arguments as a dictionary. In this case I built the factor -> alpha mappings in python and passed it into javascript. We used the vfunc argument because we want to apply this transformation to each instance of a category in our data.
    3. In your call to the plot function p.scatter use the transform function to tie your specified column name to your custom transformation. In this case alpha = transform("cat_field", categorical_alpha_transformer)
    import pandas as pd
    import numpy as np
    
    from bokeh.plotting import figure
    from bokeh.models import ColumnDataSource
    from bokeh.transform import factor_cmap, factor_mark, transform
    from bokeh.models.transforms import CustomJSTransform
    from bokeh.io import output_notebook, show
    
    output_notebook()
    
    v_func  = """
    var new_xs = new Array(xs.length)
    for(var i = 0; i < xs.length; i++) {
        new_xs[i] = alpha_map[xs[i]]
    }
    return new_xs
    """
    
    df = pd.DataFrame({
        "cat_field": list("abcdef"), 
        "x": list(range(6)), 
        "y": list(range(6)),
    })
    
    palette = ['#440154', '#404387', '#29788E', '#22A784', '#79D151', '#FDE724']
    bok_sym = ['circle','asterisk','square_x','circle_x','diamond','hex']
    alphas = [.1, .25, .4, .55, .7, .85, 1]
    cat_lst = list(df['cat_field'].unique())
    
    alpha_map = dict(zip(cat_lst, alphas)) # {"a": .1, "b": .25, ... "f": 1}
    categorical_alpha_transformer = CustomJSTransform(args={"alpha_map": alpha_map}, v_func=v_func)
    
    
    df_cds = ColumnDataSource(data=df)
    
    fig = figure(width=250, height=250)
    fig.scatter(
        x='x',
        y='y',
        source = df_cds,
        marker = factor_mark('cat_field', bok_sym, cat_lst),
        fill_color = factor_cmap('cat_field', palette, cat_lst),
        size=20,
        alpha = transform("cat_field", categorical_alpha_transformer)
        
    )
    
    show(fig)
    

    enter image description here