Box Plot with Strip Overlay — Altair

A combined visualization that overlays individual data points (strip plot) on top of a box plot. This provides both summary statistics (median, quartiles, whiskers) and visibility of the actual data distribution.

Box Plot with Strip Overlay rendered with Altair

Python source (Altair)

""" anyplot.ai
cat-box-strip: Box Plot with Strip Overlay
Library: altair 6.1.0 | Python 3.13.13
Quality: 88/100 | Updated: 2026-05-13
"""

import os

import altair as alt
import numpy as np
import pandas as pd


# Theme tokens
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"
BRAND = "#009E73"  # Okabe-Ito position 1

# Data: Product quality scores across departments
np.random.seed(42)

departments = ["Engineering", "Marketing", "Sales", "Support"]
data = []

# Create varied distributions per department
for dept in departments:
    if dept == "Engineering":
        # Higher scores, tight distribution
        values = np.random.normal(85, 6, 40)
    elif dept == "Marketing":
        # Medium scores, wider spread
        values = np.random.normal(72, 12, 35)
        # Add some outliers
        values = np.append(values, [45, 48, 98])
    elif dept == "Sales":
        # Lower scores, moderate spread
        values = np.random.normal(65, 10, 45)
        # Add outliers
        values = np.append(values, [35, 92, 95])
    else:  # Support
        # Bimodal distribution
        values = np.concatenate([np.random.normal(60, 8, 20), np.random.normal(80, 5, 25)])

    for v in values:
        data.append({"Department": dept, "Quality Score": np.clip(v, 30, 100), "Series": "Data Point"})

df = pd.DataFrame(data)

# Box plot layer
boxplot = (
    alt.Chart(df)
    .mark_boxplot(size=60, color=BRAND, median={"color": "#954477", "strokeWidth": 3}, opacity=0.8)
    .encode(
        x=alt.X("Department:N", title="Department", axis=alt.Axis(labelFontSize=18, titleFontSize=22, labelAngle=0)),
        y=alt.Y(
            "Quality Score:Q",
            title="Quality Score",
            scale=alt.Scale(domain=[25, 105]),
            axis=alt.Axis(labelFontSize=18, titleFontSize=22),
        ),
    )
)

# Strip plot layer with jitter
strip = (
    alt.Chart(df)
    .mark_circle(size=100, color=BRAND, opacity=0.6)
    .encode(
        x=alt.X("Department:N"),
        y=alt.Y("Quality Score:Q"),
        xOffset="jitter:Q",
        tooltip=["Department:N", alt.Tooltip("Quality Score:Q", format=".1f")],
    )
    .transform_calculate(jitter="sqrt(-2*log(random()))*cos(2*PI*random())*15")
)

# Combine layers
chart = (
    alt.layer(boxplot, strip)
    .properties(
        width=1600,
        height=900,
        background=PAGE_BG,
        title=alt.Title("cat-box-strip · altair · anyplot.ai", fontSize=28, anchor="middle"),
    )
    .configure_title(color=INK)
    .configure_axis(
        labelFontSize=18,
        titleFontSize=22,
        gridOpacity=0.10,
        labelColor=INK_SOFT,
        titleColor=INK,
        domainColor=INK_SOFT,
        tickColor=INK_SOFT,
        gridColor=INK,
    )
    .configure_view(strokeWidth=0, fill=PAGE_BG)
)

# Save as PNG and HTML
chart.save(f"plot-{THEME}.png", scale_factor=3.0)
chart.save(f"plot-{THEME}.html")

Part of Box Plot with Strip Overlay on anyplot.ai.

Other implementations