How to Create a Sunburst Chart in Plotly

Sunburst charts represent hierarchical data as concentric rings radiating from a center point. Each ring represents a level in the hierarchy, with segments sized proportionally to their values. Think...

Key Insights

  • Sunburst charts excel at visualizing hierarchical data with proportional relationships—use them when you need to show both hierarchy depth and relative sizes simultaneously, unlike treemaps which sacrifice radial intuition or pie charts which can’t handle multiple levels
  • Plotly’s sunburst implementation requires just three arrays (labels, parents, values) but the parent-child relationship structure is critical—each label must have exactly one parent except the root, and all parent references must exist as labels
  • For production use, always set branchvalues='total' when your data represents aggregated metrics, customize hover templates for clarity, and limit visible depth with maxdepth for datasets exceeding 4-5 levels to prevent visual clutter

Understanding Sunburst Charts

Sunburst charts represent hierarchical data as concentric rings radiating from a center point. Each ring represents a level in the hierarchy, with segments sized proportionally to their values. Think of them as multi-level pie charts where each slice can contain sub-slices.

Use sunburst charts when you need to answer questions like “What proportion of our total revenue comes from each region, and how does that break down by product category?” or “How is our disk space distributed across directories and subdirectories?” They shine when showing both the part-to-whole relationship and the hierarchical structure matters equally.

Compared to treemaps, sunburst charts offer better visual flow for exploring paths from root to leaf. Treemaps pack more information into less space but lose the intuitive radial navigation. Standard pie charts can’t handle hierarchy at all—they’re limited to a single level.

Setting Up Your Environment

Install Plotly using pip:

pip install plotly

For working with real-world data, you’ll also want pandas:

pip install pandas

Import the necessary modules:

import plotly.graph_objects as go
import plotly.express as px
import pandas as pd

Plotly offers two approaches: plotly.graph_objects for granular control and plotly.express for quick visualizations. We’ll focus on graph_objects because it provides the flexibility you need for production work.

Creating a Basic Sunburst Chart

The data structure for sunburst charts follows a parent-child model. You need three parallel lists:

  • labels: Unique identifiers for each segment
  • parents: The parent of each label (empty string for root)
  • values: Numeric values determining segment size

Here’s a straightforward company hierarchy example:

import plotly.graph_objects as go

labels = [
    "Company",
    "Engineering", "Sales", "Marketing",
    "Backend", "Frontend", "Enterprise", "SMB", "Content", "Digital"
]

parents = [
    "",
    "Company", "Company", "Company",
    "Engineering", "Engineering", "Sales", "Sales", "Marketing", "Marketing"
]

values = [
    0,  # Root has no value
    45, 30, 25,  # Departments
    25, 20, 18, 12, 15, 10  # Teams
]

fig = go.Figure(go.Sunburst(
    labels=labels,
    parents=parents,
    values=values,
))

fig.update_layout(
    title="Company Headcount Distribution",
    width=600,
    height=600
)

fig.show()

Notice the root node (“Company”) has a value of 0. Plotly automatically calculates it as the sum of its children. This is important—don’t double-count by adding explicit values to parent nodes when using branchvalues='total'.

Customizing Visual Appearance

Default colors work for exploration, but production visualizations need branding and clarity. Control colors through the marker parameter:

fig = go.Figure(go.Sunburst(
    labels=labels,
    parents=parents,
    values=values,
    marker=dict(
        colors=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', 
                '#9467bd', '#8c564b', '#e377c2', '#7f7f7f',
                '#bcbd22', '#17becf'],
        line=dict(color='white', width=2)
    ),
    textfont=dict(size=14, family='Arial, sans-serif'),
))

fig.update_layout(
    title=dict(
        text="Company Headcount Distribution",
        font=dict(size=20, family='Arial, sans-serif')
    ),
    width=700,
    height=700,
    paper_bgcolor='#f8f9fa',
)

fig.show()

The line parameter adds white borders between segments, improving readability. For categorical data, use distinct colors for top-level categories and shades for subcategories.

Better yet, use a colorscale for continuous values:

marker=dict(
    colorscale='Blues',
    cmid=values.mean(),
    line=dict(color='white', width=2)
)

Advanced Features and Interactivity

The branchvalues parameter controls how parent values are calculated. Set it to 'total' when parents should sum their children, or 'remainder' when the parent has its own value separate from children:

# File system example showing disk usage
labels = ["Root", "Documents", "Photos", "Videos", 
          "Work", "Personal", "2023", "2024", "Projects", "Archive"]
parents = ["", "Root", "Root", "Root",
           "Documents", "Documents", "Photos", "Photos", "Videos", "Videos"]
values = [0, 0, 0, 0,  # Directories with no direct content
          2.5, 1.8, 15.3, 22.1, 45.2, 38.7]  # File sizes in GB

fig = go.Figure(go.Sunburst(
    labels=labels,
    parents=parents,
    values=values,
    branchvalues='total',
    hovertemplate='<b>%{label}</b><br>Size: %{value:.1f} GB<br>Percentage: %{percentRoot:.1%}<extra></extra>',
    maxdepth=2,  # Show only 2 levels initially
))

fig.update_layout(title="Disk Space Usage")
fig.show()

The hovertemplate parameter accepts HTML and special tokens like %{label}, %{value}, %{percentRoot}, and %{percentParent}. The <extra></extra> removes the default trace name from hover boxes.

Setting maxdepth=2 initially displays only two levels, but users can click to drill down. This prevents overwhelming visualizations when you have deep hierarchies.

Real-World Use Case: Sales Analysis

Let’s build a complete example analyzing sales data across regions, categories, and products:

import pandas as pd
import plotly.graph_objects as go

# Sample sales data
data = {
    'region': ['North', 'North', 'North', 'North', 'South', 'South', 'South', 'South'],
    'category': ['Electronics', 'Electronics', 'Clothing', 'Clothing', 
                 'Electronics', 'Electronics', 'Clothing', 'Clothing'],
    'product': ['Laptops', 'Phones', 'Shirts', 'Pants', 
                'Laptops', 'Phones', 'Shirts', 'Pants'],
    'sales': [125000, 89000, 45000, 38000, 
              98000, 112000, 52000, 41000]
}

df = pd.DataFrame(data)

# Build hierarchical structure
labels = ['Total Sales']
parents = ['']
values = [0]

# Add regions
for region in df['region'].unique():
    labels.append(region)
    parents.append('Total Sales')
    values.append(0)

# Add categories within regions
for _, row in df.groupby(['region', 'category']).sum().reset_index().iterrows():
    label = f"{row['region']}-{row['category']}"
    labels.append(label)
    parents.append(row['region'])
    values.append(0)

# Add products (leaf nodes with actual values)
for _, row in df.iterrows():
    label = f"{row['region']}-{row['category']}-{row['product']}"
    parent = f"{row['region']}-{row['category']}"
    labels.append(label)
    parents.append(parent)
    values.append(row['sales'])

# Create sunburst
fig = go.Figure(go.Sunburst(
    labels=labels,
    parents=parents,
    values=values,
    branchvalues='total',
    hovertemplate='<b>%{label}</b><br>Sales: $%{value:,.0f}<br>Share: %{percentRoot:.1%}<extra></extra>',
    marker=dict(
        colorscale='RdYlGn',
        cmid=df['sales'].mean(),
        line=dict(color='white', width=2)
    )
))

fig.update_layout(
    title="Sales Performance by Region, Category, and Product",
    width=800,
    height=800,
    font=dict(size=12)
)

fig.show()

This example demonstrates data preparation from a DataFrame. The key is building labels systematically—use composite keys like “Region-Category-Product” to ensure uniqueness. Parent references must match these composite labels exactly.

For large datasets, consider filtering to top N segments or aggregating small categories into “Other” to maintain visual clarity. Sunburst charts become cluttered beyond 50-60 segments.

Performance Considerations and Best Practices

Sunburst charts perform well up to a few hundred segments. Beyond that, consider:

  1. Aggregation: Group small segments under “Other” categories
  2. Depth limiting: Use maxdepth to show only 2-3 levels initially
  3. Filtering: Show top performers by default with an option to expand

For data updates, regenerate the entire figure rather than trying to update individual segments. Plotly’s rendering is fast enough that this approach works fine for interactive dashboards.

Choose sunburst charts when hierarchy matters as much as proportion. If you only care about proportions, use a treemap. If you only care about hierarchy, use a dendrogram or tree diagram. Sunburst charts occupy the sweet spot where both dimensions carry equal weight.

The Plotly documentation provides extensive examples and API references at plotly.com/python/sunburst-charts/. For complex hierarchies, consider the go.Icicle chart, which displays the same data in a rectangular format that some users find easier to read.

Sunburst charts transform hierarchical data into intuitive, interactive visualizations. Master the parent-child structure, customize thoughtfully, and you’ll create compelling data stories that reveal patterns invisible in tables or simple charts.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.