How to Create a Heatmap in Plotly

Heatmaps are matrix visualizations where individual values are represented as colors. They excel at revealing patterns in multi-dimensional data that would be invisible in tables. You'll use them for...

Key Insights

  • Plotly heatmaps offer interactivity that static libraries can’t match—hover tooltips, zooming, and click events make data exploration significantly more effective than matplotlib alternatives
  • The choice between plotly.graph_objects and plotly.express matters: use graph_objects for fine-grained control over annotations and styling, use express for quick exploratory analysis
  • Proper data normalization and colorscale selection are critical—a poorly chosen colorscale can hide patterns or create false impressions in your data

Introduction to Heatmaps and Plotly

Heatmaps are matrix visualizations where individual values are represented as colors. They excel at revealing patterns in multi-dimensional data that would be invisible in tables. You’ll use them for correlation matrices in exploratory data analysis, visualizing confusion matrices in machine learning, displaying time-series patterns (like website traffic by hour and day), and showing geographic density data.

Plotly stands out for heatmap creation because of its native interactivity. Users can hover over cells to see exact values, zoom into specific regions, and export visualizations as standalone HTML files. This interactivity is built-in, not bolted on. For data science teams sharing insights with non-technical stakeholders, this matters immensely.

In this article, you’ll learn to create basic heatmaps, customize their appearance for maximum clarity, build correlation matrices from real datasets, and implement advanced features like custom hover templates.

Setting Up Your Environment

Install Plotly with pip. The library has minimal dependencies and works in Jupyter notebooks, standalone Python scripts, and web applications.

pip install plotly pandas numpy

Here’s your standard import block:

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

# Verify installation
print(f"Plotly version: {plotly.__version__}")

Plotly 5.x is the current major version. Anything above 5.0 will work for these examples.

Creating a Basic Heatmap

Start with plotly.graph_objects.Heatmap. This gives you full control over every aspect of the visualization.

# Generate sample data
z_data = np.random.randn(10, 10)

# Create heatmap
fig = go.Figure(data=go.Heatmap(
    z=z_data,
    x=[f'Col{i}' for i in range(10)],
    y=[f'Row{i}' for i in range(10)],
    colorscale='Viridis'
))

fig.update_layout(
    title='Basic Heatmap Example',
    xaxis_title='X Axis',
    yaxis_title='Y Axis',
    width=700,
    height=600
)

fig.show()

The z parameter takes your 2D data. The x and y parameters are optional but recommended—they label your axes and appear in hover tooltips. Without them, Plotly uses integer indices.

The data structure must be a 2D array-like object: NumPy arrays, lists of lists, or pandas DataFrames all work. Plotly interprets the first dimension as rows (y-axis) and the second as columns (x-axis).

Customizing Heatmap Appearance

Default settings rarely tell the best story. Customize colorscales, add annotations, and refine hover text to make your heatmap communicate effectively.

# Create a correlation-style matrix
data = np.array([
    [1.0, 0.8, -0.3, 0.5],
    [0.8, 1.0, -0.2, 0.6],
    [-0.3, -0.2, 1.0, -0.4],
    [0.5, 0.6, -0.4, 1.0]
])

labels = ['Feature A', 'Feature B', 'Feature C', 'Feature D']

# Create annotations for each cell
annotations = []
for i, row in enumerate(data):
    for j, value in enumerate(row):
        annotations.append(
            dict(
                x=labels[j],
                y=labels[i],
                text=f'{value:.2f}',
                showarrow=False,
                font=dict(color='white' if abs(value) > 0.5 else 'black')
            )
        )

fig = go.Figure(data=go.Heatmap(
    z=data,
    x=labels,
    y=labels,
    colorscale='RdBu',
    zmid=0,  # Center colorscale at zero
    text=data,
    texttemplate='%{text:.2f}',
    textfont={"size": 12},
    colorbar=dict(title='Correlation')
))

fig.update_layout(
    title='Correlation Matrix with Annotations',
    width=600,
    height=600,
    annotations=annotations
)

fig.show()

Key customization points:

  • colorscale: Choose from built-in options (‘Viridis’, ‘RdBu’, ‘Plasma’, ‘Hot’) or define custom scales as lists of [position, color] tuples
  • zmid: Centers the colorscale at a specific value, essential for diverging data like correlations
  • texttemplate: Controls how values appear on cells using Plotly’s template syntax
  • Annotations: Give you pixel-perfect control over text placement and styling

For hover text customization:

fig = go.Figure(data=go.Heatmap(
    z=data,
    x=labels,
    y=labels,
    hovertemplate='%{y} vs %{x}<br>Correlation: %{z:.3f}<extra></extra>',
    colorscale='RdBu',
    zmid=0
))

The <extra></extra> tag removes the default trace name from the tooltip.

Real-World Example: Correlation Matrix

Let’s build a complete correlation heatmap from a real dataset. This is one of the most common use cases in data science.

# Create sample dataset
np.random.seed(42)
df = pd.DataFrame({
    'Sales': np.random.normal(100, 15, 100),
    'Marketing_Spend': np.random.normal(50, 10, 100),
    'Website_Traffic': np.random.normal(1000, 200, 100),
    'Customer_Satisfaction': np.random.normal(4.0, 0.5, 100),
    'Employee_Count': np.random.normal(25, 5, 100)
})

# Add some correlations
df['Sales'] = df['Sales'] + 0.7 * df['Marketing_Spend']
df['Website_Traffic'] = df['Website_Traffic'] + 0.5 * df['Marketing_Spend']

# Compute correlation matrix
corr_matrix = df.corr()

# Create heatmap
fig = px.imshow(
    corr_matrix,
    labels=dict(x='Features', y='Features', color='Correlation'),
    x=corr_matrix.columns,
    y=corr_matrix.columns,
    color_continuous_scale='RdBu',
    zmin=-1,
    zmax=1,
    aspect='auto',
    text_auto='.2f'
)

fig.update_layout(
    title='Feature Correlation Matrix',
    width=700,
    height=600
)

fig.show()

Notice the switch to plotly.express.imshow. For quick correlation matrices, px.imshow is cleaner than go.Heatmap. It automatically handles aspect ratios and provides sensible defaults. The text_auto parameter adds cell values with minimal code.

Use go.Heatmap when you need custom annotations or complex hover templates. Use px.imshow for rapid exploratory analysis.

Advanced Features

Custom Hover Templates

Default hover text is functional but generic. Custom templates let you add context:

# Time-series heatmap example
hours = [f'{i:02d}:00' for i in range(24)]
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
traffic_data = np.random.poisson(100, (7, 24)) + np.random.randint(0, 50, (7, 24))

fig = go.Figure(data=go.Heatmap(
    z=traffic_data,
    x=hours,
    y=days,
    colorscale='YlOrRd',
    hovertemplate='<b>%{y} at %{x}</b><br>' +
                  'Traffic: %{z} visitors<br>' +
                  '<extra></extra>'
))

fig.update_layout(
    title='Website Traffic by Day and Hour',
    xaxis_title='Hour of Day',
    yaxis_title='Day of Week',
    width=900,
    height=400
)

fig.show()

Multiple Heatmaps with Subplots

Compare multiple heatmaps side-by-side using Plotly’s subplot functionality:

from plotly.subplots import make_subplots

# Generate two related datasets
data1 = np.random.randn(8, 8)
data2 = data1 + np.random.randn(8, 8) * 0.5

fig = make_subplots(
    rows=1, cols=2,
    subplot_titles=('Dataset 1', 'Dataset 2'),
    horizontal_spacing=0.15
)

fig.add_trace(
    go.Heatmap(z=data1, colorscale='Viridis', showscale=False),
    row=1, col=1
)

fig.add_trace(
    go.Heatmap(z=data2, colorscale='Viridis'),
    row=1, col=2
)

fig.update_layout(
    title='Comparative Heatmap Analysis',
    width=900,
    height=400
)

fig.show()

Handling Large Datasets

For datasets with thousands of cells, performance degrades. Apply these strategies:

# Downsample large matrices
def downsample_matrix(matrix, target_size=100):
    """Reduce matrix size while preserving patterns"""
    from scipy.ndimage import zoom
    factor_x = target_size / matrix.shape[0]
    factor_y = target_size / matrix.shape[1]
    return zoom(matrix, (factor_x, factor_y), order=1)

# For very large datasets
large_data = np.random.randn(1000, 1000)
downsampled = downsample_matrix(large_data, target_size=100)

fig = go.Figure(data=go.Heatmap(
    z=downsampled,
    colorscale='Viridis'
))
fig.show()

Alternatively, use aggregation before visualization. If you’re showing hourly data for a year, aggregate to daily or weekly before creating the heatmap.

Conclusion and Best Practices

Heatmaps work best for data with two categorical or ordinal dimensions and a continuous value. Don’t use them for more than 50-100 categories per dimension—the cells become too small to interpret. For high-cardinality data, aggregate first or use alternative visualizations like scatter plots.

Choose colorscales deliberately. Sequential scales (Viridis, Plasma) work for data with a natural order. Diverging scales (RdBu, RdYlGn) work for data centered around a meaningful midpoint. Never use rainbow colorscales—they create false boundaries and are problematic for colorblind users.

Always include a colorbar with a clear label. Your audience shouldn’t guess what the colors mean. For correlation matrices, explicitly show that the range is -1 to 1.

Performance matters. Plotly generates HTML/JavaScript, which means large heatmaps can create multi-megabyte files. If your visualization exceeds 2MB, you’re probably showing too much detail. Downsample or aggregate.

For further learning, explore Plotly’s documentation on figure factories for specialized heatmaps, and investigate Dash for building interactive heatmap dashboards. The combination of Plotly’s visualization capabilities with Dash’s callback system enables powerful analytical tools.

Liked this? There's more.

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