Donut#

Download this notebook from GitHub (right-click to download).


Title: Donut Element#

Dependencies: Bokeh

Backends: Bokeh, Matplotlib

import pandas as pd

import holoviews as hv

hv.extension('bokeh')

The Donut Element represents proportional data as wedges of an annular ring. The key dimension provides the category for each slice and the value dimension determines the slice size.

A Donut can be created from a list of tuples where each tuple contains a category and its corresponding value.

data = [('Rent', 1200), ('Food', 400), ('Transport', 200), ('Entertainment', 150)]
d = hv.Donut(data)
d

We can achieve the same plot using a Pandas DataFrame.

df = pd.DataFrame(
    {
        'Category': ['Rent', 'Food', 'Transport', 'Entertainment'],
        'Amount': [1200, 400, 200, 150],
        'Cumulative': [1200, 1600, 1800, 1950],
    }
)

hv.Donut(df, kdims=['Category'], vdims=['Amount', 'Cumulative'])

Donut charts also support numeric categories, such as years.

yearly = pd.DataFrame(
    {
        'Year': [2021, 2022, 2023, 2024],
        'Amount': [100, 140, 180, 220],
    }
)

hv.Donut(yearly, kdims='Year', vdims='Amount')

Rows with a missing category or missing value are skipped when the donut is rendered.

missing = pd.DataFrame(
    {
        'Category': ['Rent', 'Food', 'Transport', None, 'Other'],
        'Amount': [1200, 400, 200, 300, pd.NA],
    }
)

hv.Donut(missing, kdims='Category', vdims='Amount')

Use start_angle to rotate the first wedge.

d.opts(start_angle=1.2)

Text labels can be placed next to each wedge with show_labels=True.

d = hv.Donut(df, kdims='Category', vdims='Amount')
d.opts(show_labels=True, show_legend=False, inner_radius=0.2)

Alternatively, show_labels can be a template string using dimension names and fraction. Zoom out using padding to prevent labels from being clipped.

(
    d.clone().opts(show_labels='{Category}: {fraction:.1%}',).relabel('Category and share')
    + d.clone().opts(show_labels='${Amount:,.0f}').relabel('Value only')
).cols(3)

Label styling can be controlled with label_radius, label_text_align, label_text_font_size, label_text_baseline, and label_text_color.

d.opts(
    show_labels="{Category}\n{fraction:.1%}",
    label_radius=1.1,
    label_text_align='right',
    label_text_baseline='middle',
    label_text_font_size='8px',
    label_text_color='firebrick',
)

A center label can display the total or any custom text using center_label.

Set it to 'total' to compute and format the sum of all values.

d = d.opts(center_label='total', center_text_font_size='10pt', inner_radius=0.9)
d

center_label also accepts template strings. Available placeholders include {total}, {total_formatted}, and the value-dimension name.

(
    d.clone().opts(center_label='Budget mix').relabel('Static label')
    + d.clone().opts(center_label='The total was\n{total:,.0f}').relabel('Template')
    + d.clone().opts(
        center_label="Monthly total\n${total:,.0f}",
        center_text_color='red',
    ).relabel('Styled center label')
).cols(3)

Datetime categories are also supported and can be formatted in label templates.

dated = pd.DataFrame(
    {
        'date': pd.to_datetime(['2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01']),
        'Amount': [1200, 900, 600, 300],
    }
)

hv.Donut(dated, kdims='date', vdims='Amount').opts(
    show_labels="{date:%b %Y} {fraction:.1%}",
    show_legend=False,
    label_text_font_size='8px',
)

The inner_radius option controls the size of the hole. Setting it to 0 produces a pie chart while a larger inner_radius creates a thinner ring. outer_radius can be used to scale the donut itself.

d = d.opts(show_legend=False, center_label=None, label_text_color="black")
(
    d.clone().opts(inner_radius=0).relabel('Pie') +
    d.clone().opts(inner_radius=0.2).relabel('Default')
    + d.clone().opts(inner_radius=0.4).relabel('Thin ring')
    + d.clone().opts(inner_radius=0.7, outer_radius=1.2).relabel('Thin ring, larger radius')
).cols(4)

The color palette can be changed using the cmap style option.

(
    d.clone().opts(cmap='Set2').relabel('Named palette')
    + d.clone().opts(cmap=['black', 'brown', 'tan', 'gray']).relabel('Custom list')
    + d.clone().opts(
        cmap={
            'Rent': '#1d4ed8',
            'Food': '#16a34a',
            'Transport': '#f59e0b',
            'Entertainment': '#ef4444',
        }
    ).relabel('Category map')
).cols(3)

Border styling uses wedge_-prefixed Bokeh line properties.

To make borders visible, set wedge_line_alpha=1 in addition to wedge_line_color and wedge_line_width.

d.opts(
    wedge_line_color='white',
    wedge_line_width=4,
    wedge_line_alpha=1,
)

For full documentation and the available style and plot options, use hv.help(hv.Donut).

This web page was generated from a Jupyter notebook and not all interactivity will work on this website. Right click to download and run locally for full Python-backed interactivity.

Download this notebook from GitHub (right-click to download).