pythonbokehpandas-bokeh

Nested pie chart in bokeh


I'm trying to achieve the following o/p from bokeh which is deprecated now. In the new structure, I need to layer everything like working in HTML. Is there any simplified example like this bokeh pie chart(deprecated) ?enter image description here


Solution

  • You can combine wegde and annular_wedge

    The example code below creates this figure:

    medal cake

    Example code

    Pandas

    Lets say we have this data table for the medals of a sport event.

    import numpy as np
    import pandas as pd
    
    df = pd.DataFrame({
        'Country':['Fra', 'Deu', 'Can', 'USA'],
        'Gold': [0,1,2,3],
        'Silver': [1,2,2,2],
        'Bronze':[3,2,1,3]
    })
    >>> df
      Country  Gold  Silver  Bronze
    0     FRA     0       1       3
    1     GER     1       2       2
    2     CAN     2       2       1
    3     USA     3       2       3
    

    Now we have to calculate the angles to use the bokeh wedges. We use a copy to not overwrite the original data.

    # get a copy and calculate the angles
    _df = df.copy()
    _df['Country_total'] = _df[['Gold', 'Silver', 'Bronze']].sum(axis=1)
    total_sum = _df['Country_total'].sum()
    _df["End"] = _df['Country_total'].div(total_sum).mul(2 * np.pi).cumsum().round(6)
    _df["Start"] = _df["End"].shift(1).fillna(0)
    _df['Color'] = ['blue', 'red', 'green', 'magenta']
    _df
    

    Bokeh

    Here we loop over all rows of the DataFrame and draw the annular wedge first and then the inner wedge to get the correct color in the legend.

    from bokeh.models import Legend
    from bokeh.plotting import show, figure, output_notebook
    output_notebook()
    
    p = figure(width=400, height=300, match_aspect=True, x_range=(-2.2,2.2), y_range=(-2.2,2.2))
    
    p.add_layout(
        Legend(
            click_policy="hide",
            margin=1,
        ),
        "right",
    )
    
    p.rect(x=0, y=0, width=2, height=2, alpha=0)
    
    wedges = []
    for i, item in _df.iterrows():
        start = item["Start"]
        end = item["End"]
    
        start_angle = start
        end_angle = start
        for value, color in zip(['Gold', 'Silver', 'Bronze'], ['gold', 'silver', '#bf8970']):
            angle = np.round(item[value] / total_sum * 2 * np.pi, 6)
            end_angle += angle
            p.annular_wedge(
                x=0,
                y=0,
                inner_radius=1,
                outer_radius=2,
                start_angle=start_angle,
                end_angle=end_angle,
                color=color,
                line_color='white',
                legend_label=item['Country'],
            )
            start_angle += angle
    
        p.wedge(
            x=0,
            y=0,
            radius=1,
            start_angle=start,
            end_angle=end,
            color=item["Color"],
            line_color='white',
            legend_label=item['Country'],
        )
    p.xgrid.visible = False
    p.ygrid.visible = False
    
    show(p)
    

    Comment

    I hope this is close tou your wanted solution.