bokeh how to dynamically change color in glyph based on underlying data

Viewed 1480

I made the following dashboard in bokeh. By clicking the "Update Data" button the yellow histogram data changes. I'd like to change colour to the yellow histogram if the mean is greater or lower than the mean of the grey histogram.

enter image description here

This is the code:

from bokeh.plotting import figure
import numpy as np
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column


from bokeh.models import Button
from bokeh.layouts import widgetbox


from bokeh.plotting import ColumnDataSource

button = Button(label='Update Data')

def generate_time_differences(n=1000, skew_p=0.1, mean=0, std=1, skew_mean=1, skew_std=6):
    normal_dist = np.random.normal(loc=mean, scale=std, size=int(n * (1 - skew_p)))
    skewed_dist = np.random.normal(loc=skew_mean, scale=skew_std, size=int(n * skew_p))

    return np.append(normal_dist, skewed_dist)


def generate_plot_data(data, density=True, bins=50):
    hist, edges = np.histogram(data, density=density, bins=bins)

    df = pd.DataFrame({'top': hist,
                         'left': edges[:-1],
                         'right': edges[1:]}

                      )
    df['mean'] = [np.mean(data) for i in range(len(df))]
    df['std'] = [np.std(data) for i in range(len(df))]
    df['n'] = [len(data) for i in range(len(df))]

    return ColumnDataSource(df)


def create_histogram(plot_data_control, plot_data_observed, title, x_axis_label='Milliseconds',
                     y_axis_label='Frequency'):

    mean_control = np.mean(plot_data_control.data['mean'])
    mean_observed = np.mean(plot_data_observed.data['mean'])

    color_observed = 'yellow' if mean_observed > mean_control else 'green'


    p = figure(plot_height=150, plot_width=600,
               title=title,
               x_axis_label=x_axis_label,
               y_axis_label=y_axis_label)

    # Add a quad glyph for plot_data
    p.quad(source=plot_data_control, bottom=0, top='top', left='left', right='right',
           fill_color='gray', line_color='gray', fill_alpha=0.5, line_alpha=0.5, legend='Control')
    # Add another quad glyph for plot_data2
    p.quad(source=plot_data_observed, bottom=0, top='top', left='left', right='right',
           fill_color=color_observed, line_color=color_observed, fill_alpha=0.5, line_alpha=0.5, legend='Observed')
    return p

def update():

    mean = np.random.randint(500,1500)
    std = 100
    skew_mean =  2000
    skew_std = 500

    new_plot_data = generate_time_differences(n=100, skew_p=0.1, mean=mean, std=std, skew_mean=skew_mean, skew_std=skew_std)

    plot_data2.data =  generate_plot_data(new_plot_data).data


data = generate_time_differences(n=1000, skew_p=0.1, mean=1000, std=100, skew_mean=2000, skew_std=500)
data2 = generate_time_differences(n=100, skew_p=0.1, mean=1050, std=100, skew_mean=2100, skew_std=500)


plot_data = generate_plot_data(data)
plot_data2 = generate_plot_data(data2)


p1 = create_histogram(plot_data, plot_data2, 'Status1 to Status2')

button.on_click(update)

layout = column(widgetbox(button), p1)

curdoc().add_root(layout)
1 Answers

You can add the color to the columndatasource and change it when the callback function is called and if the condition is true. Added some comments to the code to further explain what I changed.

from bokeh.plotting import figure
import numpy as np
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column


from bokeh.models import Button
from bokeh.layouts import widgetbox


from bokeh.plotting import ColumnDataSource

button = Button(label='Update Data')

def generate_time_differences(n=1000, skew_p=0.1, mean=0, std=1, skew_mean=1, skew_std=6):
    normal_dist = np.random.normal(loc=mean, scale=std, size=int(n * (1 - skew_p)))
    skewed_dist = np.random.normal(loc=skew_mean, scale=skew_std, size=int(n * skew_p))

    return np.append(normal_dist, skewed_dist)


def generate_plot_data(data, density=True, bins=50):
    hist, edges = np.histogram(data, density=density, bins=bins)

    df = pd.DataFrame({'top': hist,
                         'left': edges[:-1],
                         'right': edges[1:]}

                      )
    df['mean'] = [np.mean(data) for i in range(len(df))]
    df['std'] = [np.std(data) for i in range(len(df))]
    df['n'] = [len(data) for i in range(len(df))]

    return ColumnDataSource(df)


def create_histogram(plot_data_control, plot_data_observed, title, x_axis_label='Milliseconds',
                     y_axis_label='Frequency'):

    mean_control = np.mean(plot_data_control.data['mean'])
    mean_observed = np.mean(plot_data_observed.data['mean'])
    #Change color if mean_observed > mean_control
    if mean_observed > mean_control:
        color_observed = 'yellow'
    else:
        color_observed = 'green'
    #Generate a list with the right color
    colorlst = []
    for i in range(len(plot_data_observed.data['top'])):
        colorlst.append(color_observed)
    #Add color list to sourcedata
    plot_data_observed.data['color'] = colorlst

    p = figure(plot_height=150, plot_width=600,
               title=title,
               x_axis_label=x_axis_label,
               y_axis_label=y_axis_label)

    # Add a quad glyph for plot_data
    p.quad(source=plot_data_control, bottom=0, top='top', left='left', right='right',
           fill_color='gray', line_color='gray', fill_alpha=0.5, line_alpha=0.5, legend='Control')
    # Add another quad glyph for plot_data2
    #Get color from source
    p.quad(source=plot_data_observed, bottom=0, top='top', left='left', right='right',
           fill_color='color', line_color='color', fill_alpha=0.5, line_alpha=0.5, legend='Observed')
    p.legend.click_policy="hide"
    return p

def update():
    mean = np.random.randint(500,1500)
    std = 100
    skew_mean =  2000
    skew_std = 500
    new_plot_data = generate_time_differences(n=100, skew_p=0.1, mean=mean, std=std, skew_mean=skew_mean, skew_std=skew_std)
    obs_data = generate_plot_data(new_plot_data).data
    #Get means
    mean_control = plot_data.data['mean'][0]
    mean_observed = obs_data['mean'][0]
    #Change color if mean_observed > mean_control
    if mean_observed > mean_control:
        color_observed = 'yellow'
    else:
        color_observed = 'green'
    #Generate a list with the right color
    colorlst = []
    for i in range(len(obs_data['top'])):
        colorlst.append(color_observed)
    #Add list to dictionary
    obs_data['color'] = colorlst
    #Set dictionary as sourcedata
    plot_data2.data =  obs_data


data = generate_time_differences(n=1000, skew_p=0.1, mean=1000, std=100, skew_mean=2000, skew_std=500)
data2 = generate_time_differences(n=100, skew_p=0.1, mean=1050, std=100, skew_mean=2100, skew_std=500)


plot_data = generate_plot_data(data)
plot_data2 = generate_plot_data(data2)


p1 = create_histogram(plot_data, plot_data2, 'Status1 to Status2')

button.on_click(update)

layout = column(widgetbox(button), p1)

curdoc().add_root(layout)
Related