How to Create a Pie Chart in Plotly

Plotly offers two approaches for creating pie charts: Plotly Express for rapid prototyping and Graph Objects for detailed customization. Both generate interactive, publication-quality visualizations...

Key Insights

  • Plotly Express provides the fastest path to creating pie charts with px.pie(), while Graph Objects offers granular control for complex customizations through go.Pie()
  • Donut charts (pie charts with a hole) are often more effective than traditional pie charts because they reduce the emphasis on area comparison and allow for center annotations
  • Always consolidate categories representing less than 5% of your data into an “Other” category to maintain readability—pie charts with more than 7-8 slices become difficult to interpret

Introduction to Plotly Pie Charts

Plotly offers two approaches for creating pie charts: Plotly Express for rapid prototyping and Graph Objects for detailed customization. Both generate interactive, publication-quality visualizations that work seamlessly in Jupyter notebooks, web applications, and exported formats.

Before reaching for a pie chart, consider whether your data truly warrants one. Pie charts excel at showing parts of a whole when you have fewer than seven categories and want to emphasize proportional relationships. For comparing values across categories or showing trends over time, bar charts or line graphs perform better. The human eye struggles to accurately compare angles and areas, making pie charts a poor choice for precise comparisons.

Plotly Express (px.pie()) handles most use cases with minimal code, automatically generating legends, hover information, and color schemes. Graph Objects (go.Pie()) becomes necessary when you need fine-grained control over individual slices, complex annotations, or integration with multi-trace figures.

Basic Pie Chart Creation

Creating a pie chart in Plotly requires just two data components: labels and values. Here’s the simplest implementation:

import plotly.express as px

# Sample data: market share by company
labels = ['Company A', 'Company B', 'Company C', 'Company D']
values = [35, 28, 22, 15]

fig = px.pie(values=values, names=labels, title='Market Share Distribution')
fig.show()

This generates an interactive pie chart where users can hover over slices for details, click legend items to toggle visibility, and zoom or pan. Plotly automatically assigns colors from its default palette and positions labels intelligently to avoid overlap.

For data already in a pandas DataFrame, the syntax becomes even cleaner:

import pandas as pd
import plotly.express as px

df = pd.DataFrame({
    'company': ['Company A', 'Company B', 'Company C', 'Company D'],
    'revenue': [3500000, 2800000, 2200000, 1500000]
})

fig = px.pie(df, values='revenue', names='company', 
             title='Revenue Distribution by Company')
fig.show()

Plotly Express automatically calculates percentages and formats hover text, displaying both absolute values and percentages by default.

Customizing Appearance

The default styling works for quick analysis, but production visualizations demand customization. Here’s how to create a polished pie chart with custom colors, pulled slices for emphasis, and formatted hover text:

import plotly.graph_objects as go

labels = ['Direct Sales', 'Online', 'Retail Partners', 'Distributors', 'Other']
values = [42, 28, 15, 10, 5]

# Custom color palette
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8']

fig = go.Figure(data=[go.Pie(
    labels=labels,
    values=values,
    hole=0.3,  # Creates donut chart
    marker=dict(colors=colors, line=dict(color='white', width=2)),
    pull=[0.1, 0, 0, 0, 0],  # Pull out first slice
    textinfo='label+percent',
    textposition='outside',
    hovertemplate='<b>%{label}</b><br>Revenue: %{value}%<br>Percentage: %{percent}<extra></extra>'
)])

fig.update_layout(
    title_text='Sales Channel Distribution',
    title_font_size=20,
    showlegend=True,
    legend=dict(orientation="v", yanchor="middle", y=0.5, xanchor="left", x=1.05)
)

fig.show()

The hole parameter transforms a pie chart into a donut chart—set it between 0.3 and 0.6 for optimal aesthetics. The pull parameter accepts a list of values (0 to 1) that determines how far each slice separates from the center, useful for highlighting specific segments.

The textinfo parameter controls what appears on slices. Options include ’label’, ‘percent’, ‘value’, ’text’, or combinations like ’label+percent’. Use textposition='outside' for cleaner layouts when dealing with small slices.

Advanced Features

Donut charts shine when you add center annotations to display summary statistics. Here’s a sophisticated implementation with formatted center text:

import plotly.graph_objects as go

categories = ['Engineering', 'Marketing', 'Sales', 'Operations', 'R&D']
budget = [450000, 280000, 320000, 180000, 270000]

total_budget = sum(budget)

fig = go.Figure(data=[go.Pie(
    labels=categories,
    values=budget,
    hole=0.5,
    marker=dict(colors=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']),
    texttemplate='%{label}<br>%{percent}',
    textposition='outside',
    textfont_size=12
)])

# Add center annotation
fig.add_annotation(
    text=f'Total<br><b>${total_budget:,.0f}</b>',
    x=0.5, y=0.5,
    font_size=20,
    showarrow=False
)

fig.update_layout(
    title='Annual Budget Allocation',
    showlegend=True,
    height=500,
    margin=dict(t=100, b=50, l=50, r=50)
)

fig.show()

For hierarchical data, consider using px.sunburst() instead of nested pie charts—sunburst charts handle multiple levels more effectively:

import plotly.express as px

df = pd.DataFrame({
    'labels': ['Total', 'Q1', 'Q2', 'Q3', 'Q4', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
    'parents': ['', 'Total', 'Total', 'Total', 'Total', 'Q1', 'Q1', 'Q1', 'Q2', 'Q2', 'Q2'],
    'values': [1000, 280, 250, 240, 230, 95, 90, 95, 85, 80, 85]
})

fig = px.sunburst(df, names='labels', parents='parents', values='values')
fig.show()

Real-World Application

Here’s a complete workflow analyzing survey results, from data preparation through export:

import pandas as pd
import plotly.graph_objects as go

# Simulate survey data
survey_data = {
    'response': ['Very Satisfied', 'Satisfied', 'Neutral', 'Dissatisfied', 'Very Dissatisfied'] * 100,
    'count': [1] * 500
}

df = pd.DataFrame(survey_data)

# Aggregate responses
response_counts = df.groupby('response').size().reset_index(name='count')
response_counts = response_counts.sort_values('count', ascending=False)

# Define sentiment colors
color_map = {
    'Very Satisfied': '#2ecc71',
    'Satisfied': '#a8e6cf',
    'Neutral': '#ffd93d',
    'Dissatisfied': '#ffb347',
    'Very Dissatisfied': '#e74c3c'
}

colors = [color_map[resp] for resp in response_counts['response']]

# Create visualization
fig = go.Figure(data=[go.Pie(
    labels=response_counts['response'],
    values=response_counts['count'],
    hole=0.4,
    marker=dict(colors=colors, line=dict(color='white', width=3)),
    textinfo='label+percent',
    textfont_size=14,
    hovertemplate='<b>%{label}</b><br>Responses: %{value}<br>Percentage: %{percent}<extra></extra>'
)])

# Calculate satisfaction rate
positive = response_counts[response_counts['response'].isin(['Very Satisfied', 'Satisfied'])]['count'].sum()
total = response_counts['count'].sum()
satisfaction_rate = (positive / total) * 100

fig.add_annotation(
    text=f'<b>{satisfaction_rate:.1f}%</b><br>Satisfied',
    x=0.5, y=0.5,
    font_size=24,
    showarrow=False
)

fig.update_layout(
    title='Customer Satisfaction Survey Results (n=500)',
    title_font_size=22,
    showlegend=True,
    height=600,
    legend=dict(orientation="h", yanchor="bottom", y=-0.15, xanchor="center", x=0.5)
)

# Export options
fig.write_html('survey_results.html')  # Interactive HTML
fig.write_image('survey_results.png', width=1200, height=800)  # Static image

fig.show()

For PNG/PDF export, install kaleido: pip install kaleido. This provides a lightweight engine for static image generation without browser dependencies.

Common Pitfalls and Solutions

The most frequent mistake is displaying too many categories. When slices become too small, labels overlap and the chart becomes unreadable. Consolidate minor categories into an “Other” group:

import pandas as pd
import plotly.express as px

def consolidate_small_values(df, value_col, label_col, threshold=5):
    """Group categories below threshold percentage into 'Other'"""
    total = df[value_col].sum()
    df['percentage'] = (df[value_col] / total) * 100
    
    # Separate major and minor categories
    major = df[df['percentage'] >= threshold].copy()
    minor = df[df['percentage'] < threshold].copy()
    
    # Create 'Other' category if minor categories exist
    if len(minor) > 0:
        other_row = pd.DataFrame({
            label_col: ['Other'],
            value_col: [minor[value_col].sum()],
            'percentage': [minor['percentage'].sum()]
        })
        result = pd.concat([major, other_row], ignore_index=True)
    else:
        result = major
    
    return result.drop('percentage', axis=1)

# Example usage
raw_data = pd.DataFrame({
    'category': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],
    'value': [45, 22, 15, 8, 4, 3, 2, 1]
})

cleaned_data = consolidate_small_values(raw_data, 'value', 'category', threshold=5)

fig = px.pie(cleaned_data, values='value', names='category', 
             title='Distribution (categories < 5% grouped as Other)')
fig.show()

This approach maintains chart readability while preserving data integrity. The threshold of 5% is a reasonable default, but adjust based on your specific use case and total number of categories.

For performance with large datasets, pre-aggregate your data before passing it to Plotly. Don’t let Plotly handle raw transaction-level data when you only need category summaries. This reduces rendering time and memory usage significantly.

Finally, remember that pie charts are inherently limited. When stakeholders struggle to interpret your pie chart, that’s a signal to switch to a bar chart or table instead.

Liked this? There's more.

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