A fundamental 2D scatter plot that displays the relationship between two numeric variables by plotting points on a Cartesian coordinate system. This visualization is essential for exploring correlations, identifying patterns, detecting outliers, and understanding the distribution of paired data points.

""" anyplot.ai
scatter-basic: Basic Scatter Plot
Library: altair 6.2.2 | Python 3.13.14
Quality: 91/100 | Updated: 2026-06-25
"""
import os
import sys
# Remove script directory from sys.path so the altair package resolves, not this file
sys.path = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))]
import altair as alt
import numpy as np
import pandas as pd
from PIL import Image
# Theme tokens — Imprint palette
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"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
BRAND = "#009E73" # Imprint palette position 1
# Data — study hours vs. exam scores (r ~ 0.70, moderate positive correlation)
np.random.seed(42)
n = 180
study_hours = np.random.uniform(1, 12, n)
exam_scores = np.clip(40 + study_hours * 4.2 + np.random.normal(0, 12.0, n), 30, 100)
df = pd.DataFrame({"hours": study_hours, "score": exam_scores})
pearson_r = float(np.corrcoef(df["hours"], df["score"])[0, 1])
# Scatter layer
points = (
alt.Chart(df)
.mark_circle(size=140, opacity=0.7, color=BRAND, stroke=PAGE_BG, strokeWidth=0.8)
.encode(
x=alt.X(
"hours:Q",
title="Study Hours per Week",
scale=alt.Scale(domain=[0, 13], nice=False),
axis=alt.Axis(tickCount=6, ticks=False, labelPadding=10, titlePadding=14, domain=False),
),
y=alt.Y(
"score:Q",
title="Exam Score (%)",
scale=alt.Scale(domain=[25, 105], nice=False),
axis=alt.Axis(tickCount=8, ticks=False, labelPadding=10, titlePadding=14, domain=False),
),
tooltip=[
alt.Tooltip("hours:Q", title="Study hrs / wk", format=".1f"),
alt.Tooltip("score:Q", title="Exam %", format=".1f"),
],
)
)
# Regression line — Altair's transform_regression showcases its declarative layering grammar
regression = (
alt.Chart(df)
.mark_line(color=INK_SOFT, strokeWidth=2.0, strokeDash=[6, 4], opacity=0.75)
.transform_regression("hours", "score")
.encode(x=alt.X("hours:Q"), y=alt.Y("score:Q"))
)
chart = (
alt.layer(points, regression)
.properties(
width=620,
height=320,
background=PAGE_BG,
title=alt.Title(
"scatter-basic · python · altair · anyplot.ai",
subtitle=f"n = {n} · Pearson r = {pearson_r:.2f}",
fontSize=16,
fontWeight="normal",
color=INK,
subtitleFontSize=10,
subtitleColor=INK_MUTED,
subtitlePadding=4,
anchor="start",
offset=16,
),
)
.configure_view(fill=PAGE_BG, stroke=None)
.configure_axis(
labelFontSize=10,
titleFontSize=12,
titleFontWeight="normal",
gridColor=INK,
gridOpacity=0.10,
gridWidth=0.8,
labelColor=INK_SOFT,
titleColor=INK,
)
)
# Save PNG
chart.save(f"plot-{THEME}.png", scale_factor=4.0)
# Pad to exact target 3200 × 1800 (vl-convert pads outside width/height)
TW, TH = 3200, 1800
_img = Image.open(f"plot-{THEME}.png").convert("RGB")
_w, _h = _img.size
if _w > TW or _h > TH:
raise SystemExit(
f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. "
f"Shrink chart .properties(width=, height=) values and re-render."
)
if _w < TW or _h < TH:
_canvas = Image.new("RGB", (TW, TH), PAGE_BG)
_canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))
_canvas.save(f"plot-{THEME}.png")
chart.save(f"plot-{THEME}.html")
Part of Basic Scatter Plot on anyplot.ai.