Updating CDS in wedge fails (Uncaught Error: Size mismatch)


I am trying to dynamically update a wedge, replacing the CDS data with a Python callback.
If I only update the data corresponding to the wedge “angles”, everything works as expected.
However, if I add a new row with a different “category” (which, in my intention, should be represented by a new wedge “section”) the callback doesn’t work and triggers an “Uncaught Error: Size mismatch” in the browser console.

What am I doing wrong? Minimal example below.

from math import pi
import pandas as pd
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource, Select
from bokeh.layouts import row
from bokeh.palettes import Category10
from bokeh.plotting import figure
from bokeh.transform import cumsum

x1 = {'A': 157, 'B': 93, 'C': 89}
x2 = {'A': 34, 'B': 58, 'C': 27, 'D': 192 } # Adding a new category "D" breaks the plot

def make_df(x):
    df = pd.DataFrame.from_dict(x, orient='index').reset_index()
    df.rename(columns={0:'value', 'index':'country'}, inplace=True)
    df['angle'] = df['value']/df['value'].sum() * 2*pi
    df['color'] = Category10[len(df['value'])]
    return df

df1 = make_df(x1)
df2 = make_df(x2)

src = ColumnDataSource(df1)

region = figure(height=350, toolbar_location=None, outline_line_color=None, sizing_mode="scale_both", name="region", x_range=(-0.4, 1))

rr = region.annular_wedge(x=-0, y=1, inner_radius=0.2, outer_radius=0.32,
                  start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
                  line_color="white", fill_color='color', source=src)

options = ['x1', 'x2']
select = Select(value=options[0], title='Select here:', options=options)

region.grid.grid_line_color = None

def update(attr, old, new):
    if new == 'x2':
        src.data = ColumnDataSource.from_df(df2)
        src.data = ColumnDataSource.from_df(df1)

select.on_change('value', update)
curdoc().add_root(row(select, region))

@dabbatelli This is a bug. Please submit a GitHub Issue with all relevant information. In the immediate term, I would suggest adding multiple glyphs and sources up front, and using the Select to toggle their visibility as appropriate.

Thanks @Bryan. Bug opened here.

In my real application, the source was supposed to dynamically update from a DataFrame.groupby(), so adding all glyphs and sources up front wasn’t really feasible.

I got around the issue by setting the categories column of the DataFrame as categorical dtype:

df['category'] = pd.Categorical(df['category'])

This way, any df.groupby('category').aggregatefunction() always returns a dataframe with the same number of rows (same as the number of total categories) and the CDS doesn’t need to change size.

The drawback is that the chart always gets legend entries for all categories, even when their aggregate value is 0, but this was perfectly acceptable in my case.