Correlation Matrix Heatmap — Plotly

A heatmap specifically designed to display correlation coefficients between variables, using a diverging color scheme centered at zero. The symmetric matrix visualization makes it easy to identify positive correlations, negative correlations, and independent variables at a glance. Essential for exploratory data analysis, feature engineering, and multicollinearity detection in statistical and machine learning workflows.

Correlation Matrix Heatmap rendered with Plotly

Renders

Python source (Plotly)

""" anyplot.ai
heatmap-correlation: Correlation Matrix Heatmap
Library: plotly 6.9.0 | Python 3.13.15
Quality: 94/100 | Updated: 2026-08-18
"""

import os

import numpy as np
import plotly.graph_objects as go


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Imprint diverging colormap — matte red (negative) -> theme midpoint -> blue (positive)
midpoint = PAGE_BG
imprint_div = [[0.0, "#AE3030"], [0.5, midpoint], [1.0, "#4467A3"]]


def hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip("#")
    return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))


def cell_text_color(r, mid_rgb):
    # Re-derive the interpolated cell color to pick a legibly-contrasting text
    # color — the midpoint stop is theme-adaptive, so a fixed threshold on |r|
    # would pick the wrong tone for one of the two themes.
    red_rgb, blue_rgb = hex_to_rgb("#AE3030"), hex_to_rgb("#4467A3")
    position = (r + 1) / 2
    lo, hi, t = (red_rgb, mid_rgb, position / 0.5) if position <= 0.5 else (mid_rgb, blue_rgb, (position - 0.5) / 0.5)
    cell_rgb = [lo[k] + (hi[k] - lo[k]) * t for k in range(3)]
    luminance = 0.299 * cell_rgb[0] + 0.587 * cell_rgb[1] + 0.114 * cell_rgb[2]
    return "#1A1A17" if luminance > 140 else "#FAF8F1"


# Data - Healthcare metrics correlation matrix
np.random.seed(42)
variables = [
    "Heart Rate",
    "Blood Pressure",
    "Cholesterol",
    "BMI",
    "Sleep Hours",
    "Exercise (hrs)",
    "Stress Level",
    "Resting O2",
]

# Create realistic correlation matrix with meaningful health relationships
n_vars = len(variables)
base = np.random.randn(200, n_vars)

# Add realistic correlations based on health domain knowledge
base[:, 1] = base[:, 0] * 0.65 + np.random.randn(200) * 0.4  # BP ~ Heart Rate
base[:, 2] = base[:, 0] * 0.5 + base[:, 3] * 0.6 + np.random.randn(200) * 0.4  # Cholesterol
base[:, 3] = np.random.randn(200)  # BMI (independent)
base[:, 4] = -base[:, 6] * 0.7 + np.random.randn(200) * 0.4  # Sleep ~ -Stress
base[:, 5] = (
    -base[:, 0] * 0.5 - base[:, 6] * 0.4 + np.random.randn(200) * 0.5
)  # Exercise inversely related to HR and Stress
base[:, 6] = np.random.randn(200)  # Stress (independent)
base[:, 7] = base[:, 4] * 0.6 + np.random.randn(200) * 0.5  # O2 ~ Sleep

# Calculate correlation matrix
correlation_matrix = np.corrcoef(base.T)

# Mask the strict upper triangle so each unique pair is shown once
mask = np.triu(np.ones_like(correlation_matrix, dtype=bool), k=1)
masked_corr = np.where(mask, np.nan, correlation_matrix)

# Numeric axis positions keep shape placement simple (see strong-pair highlight below)
positions = list(range(n_vars))
mid_rgb = hex_to_rgb(midpoint)

# Cell annotations — text color follows the actual interpolated cell color so it
# stays legible whether that cell renders light (near the theme midpoint) or a
# fully saturated red/blue (near the fixed scale endpoints).
annotations = []
for i in range(n_vars):
    for j in range(n_vars):
        if mask[i, j]:
            continue
        r = correlation_matrix[i, j]
        annotations.append(
            {
                "x": j,
                "y": i,
                "text": f"{r:.2f}",
                "showarrow": False,
                "font": {"size": 11, "color": cell_text_color(r, mid_rgb)},
            }
        )

# Rich hover text interpreting correlation strength and direction
hover_text = []
for i in range(n_vars):
    row = []
    for j in range(n_vars):
        if mask[i, j]:
            row.append("")
        else:
            r = correlation_matrix[i, j]
            if abs(r) >= 0.7:
                strength = "Strong"
            elif abs(r) >= 0.4:
                strength = "Moderate"
            else:
                strength = "Weak"
            direction = "positive" if r > 0 else "negative" if r < 0 else "none"
            row.append(
                f"<b>{variables[i]}</b> vs <b>{variables[j]}</b><br>"
                f"Correlation: <b>{r:.3f}</b><br>"
                f"Strength: {strength} {direction}"
            )
    hover_text.append(row)

# Heatmap — Imprint diverging scale, thin page-background gaps replace axis gridlines
fig = go.Figure(
    data=go.Heatmap(
        z=masked_corr,
        x=positions,
        y=positions,
        colorscale=imprint_div,
        zmin=-1,
        zmax=1,
        xgap=2,
        ygap=2,
        colorbar={
            "title": {"text": "Pearson r", "font": {"size": 11, "color": INK}},
            "tickfont": {"size": 9, "color": INK_SOFT},
            "thickness": 15,
            "len": 0.8,
            "tickvals": [-1, -0.5, 0, 0.5, 1],
            "outlinewidth": 0,
        },
        hoverongaps=False,
        hovertemplate="%{customdata}<extra></extra>",
        customdata=hover_text,
    )
)

# Highlight strong pairs (|r| >= 0.7, off-diagonal) with an outlined cell border —
# a real data-driven emphasis, not a simulated interaction. The stroke color
# contrasts against PAGE_BG (the xgap/ygap color the border sits on top of), not
# against the cell fill — otherwise a fill that interpolates near PAGE_BG makes
# the border blend into the surrounding gap and disappear.
for i in range(n_vars):
    for j in range(i):
        r = correlation_matrix[i, j]
        if abs(r) >= 0.7:
            fig.add_shape(
                type="rect",
                x0=j - 0.5,
                x1=j + 0.5,
                y0=i - 0.5,
                y1=i + 0.5,
                line={"color": INK, "width": 2.5},
                fillcolor="rgba(0,0,0,0)",
                layer="above",
            )

# Layout for 2400x2400 px (square — symmetric matrix)
fig.update_layout(
    autosize=False,
    title={
        "text": "heatmap-correlation · python · plotly · anyplot.ai",
        "font": {"size": 18, "color": INK},
        "x": 0.5,
        "xanchor": "center",
    },
    xaxis={
        "title": {"text": "Health Metrics", "font": {"size": 13, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "tickmode": "array",
        "tickvals": positions,
        "ticktext": variables,
        "side": "bottom",
        "tickangle": 45,
        "showgrid": False,
        "zeroline": False,
        "showline": False,
    },
    yaxis={
        "title": {"text": "Health Metrics", "font": {"size": 13, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "tickmode": "array",
        "tickvals": positions,
        "ticktext": variables,
        "autorange": "reversed",
        "showgrid": False,
        "zeroline": False,
        "showline": False,
    },
    annotations=annotations,
    paper_bgcolor=PAGE_BG,
    plot_bgcolor=PAGE_BG,
    font={"color": INK},
    margin={"l": 70, "r": 30, "t": 60, "b": 90},
    width=600,
    height=600,
)

# Save as PNG and HTML with theme-suffixed filenames
fig.write_image(f"plot-{THEME}.png", width=600, height=600, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/heatmap-correlation/plotly/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "heatmap-correlation",
  "language": "python",
  "library": "plotly",
  "page": "https://anyplot.ai/heatmap-correlation/python/plotly",
  "hub": "https://anyplot.ai/heatmap-correlation",
  "code_json": "https://api.anyplot.ai/specs/heatmap-correlation/plotly/code",
  "spec_json": "https://api.anyplot.ai/specs/heatmap-correlation",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/heatmap-correlation/python/plotly/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/heatmap-correlation/python/plotly/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/heatmap-correlation/python/plotly/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/heatmap-correlation/python/plotly/plot-dark.html",
  "quality_score": 94.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Correlation Matrix Heatmap on anyplot.ai.

Other implementations