Density Contour Plot — Plotly

A density contour plot (also known as a 2D KDE contour plot) displays the concentration of points in a 2D scatter plot using contour lines. The contours connect points of equal density, revealing clusters, patterns, and the overall bivariate distribution shape.

Density Contour Plot rendered with Plotly

Python source (Plotly)

""" anyplot.ai
contour-density: Density Contour Plot
Library: plotly 6.7.0 | Python 3.13.13
Quality: 95/100 | Updated: 2026-05-16
"""

import os

import numpy as np
import plotly.graph_objects as go


# Theme tokens (see prompts/default-style-guide.md)
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"

# Data - bivariate distribution with three clusters
np.random.seed(42)

# Create three clusters with realistic domain context (height vs weight measurements)
n_points = 500

# Cluster 1: Lighter individuals
cluster1_x = np.random.normal(160, 8, n_points // 3)  # Height in cm
cluster1_y = np.random.normal(60, 6, n_points // 3)  # Weight in kg

# Cluster 2: Heavier individuals
cluster2_x = np.random.normal(175, 10, n_points // 3)
cluster2_y = np.random.normal(80, 8, n_points // 3)

# Cluster 3: Tall but lighter individuals
cluster3_x = np.random.normal(180, 7, n_points // 3)
cluster3_y = np.random.normal(70, 7, n_points // 3)

x = np.concatenate([cluster1_x, cluster2_x, cluster3_x])
y = np.concatenate([cluster1_y, cluster2_y, cluster3_y])

# Define theme-aware colorscale for density (continuous data)
# Use viridis-like progression that works on both light and dark backgrounds
colorscale = [
    [0, "rgba(255,255,255,0)"],  # Transparent at low density
    [0.2, "#FDB462"],  # Light orange (visible on both themes)
    [0.5, "#4467A3"],  # Blue (Okabe-Ito position 3)
    [1, "#005073"],  # Dark blue (increased contrast)
]

# Create figure with density contour
fig = go.Figure()

# Add density contour plot with interactive hover
fig.add_trace(
    go.Histogram2dContour(
        x=x,
        y=y,
        colorscale=colorscale,
        contours=dict(showlabels=False, coloring="fill"),
        ncontours=14,
        showscale=True,
        colorbar=dict(
            title=dict(text="Density", font=dict(size=20, color=INK)),
            tickfont=dict(size=16, color=INK_SOFT),
            len=0.8,
            bgcolor=ELEVATED_BG,
            bordercolor=INK_SOFT,
            borderwidth=1,
        ),
        line=dict(width=2, color="rgba(0,0,0,0.2)"),
        hovertemplate="<b>Density Region</b><br>Height: %{x:.1f} cm<br>Weight: %{y:.1f} kg<extra></extra>",
    )
)

# Add scatter points for context (semi-transparent, interactive)
fig.add_trace(
    go.Scatter(
        x=x,
        y=y,
        mode="markers",
        marker=dict(
            size=6,
            color="#009E73",  # Okabe-Ito position 1 (brand color, theme-independent)
            opacity=0.25,
            line=dict(width=0),
        ),
        showlegend=False,
        name="Individual measurements",
        hovertemplate="<b>Measurement</b><br>Height: %{x:.1f} cm<br>Weight: %{y:.1f} kg<extra></extra>",
    )
)

# Update layout with theme-adaptive styling
fig.update_layout(
    title=dict(text="contour-density · plotly · pyplots.ai", font=dict(size=32, color=INK), x=0.5, xanchor="center"),
    xaxis=dict(
        title=dict(text="Height (cm)", font=dict(size=24, color=INK)),
        tickfont=dict(size=18, color=INK_SOFT),
        showgrid=True,
        gridcolor=GRID,
        gridwidth=1,
        linecolor=INK_SOFT,
        zerolinecolor=INK_SOFT,
    ),
    yaxis=dict(
        title=dict(text="Weight (kg)", font=dict(size=24, color=INK)),
        tickfont=dict(size=18, color=INK_SOFT),
        showgrid=True,
        gridcolor=GRID,
        gridwidth=1,
        linecolor=INK_SOFT,
        zerolinecolor=INK_SOFT,
    ),
    paper_bgcolor=PAGE_BG,
    plot_bgcolor=PAGE_BG,
    font=dict(color=INK),
    width=1600,
    height=900,
    margin=dict(l=100, r=120, t=100, b=100),
    hovermode="closest",
)

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

Part of Density Contour Plot on anyplot.ai.

Other implementations