Scatter Plot with Linear Regression — Makie.jl

A scatter plot that displays the relationship between two numeric variables with a fitted linear regression line and confidence interval band. This visualization extends the basic scatter plot by adding statistical modeling elements, making it ideal for understanding linear relationships, assessing model fit, and communicating the strength of correlations with visual uncertainty quantification.

Scatter Plot with Linear Regression rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# scatter-regression-linear: Scatter Plot with Linear Regression
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 88/100 | Created: 2026-08-05

using CairoMakie
using Colors
using Random
using Statistics

Random.seed!(42)

# --- Theme tokens ------------------------------------------------------------
const THEME    = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG  = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK      = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const IMPRINT_PALETTE = [
    colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233",
    colorant"#AE3030", colorant"#2ABCCD", colorant"#954477", colorant"#99B314",
]

# --- Data ---------------------------------------------------------------------
n = 150
temperature = rand(15.0:0.1:35.0, n)
energy_consumption = 12.0 .+ 3.4 .* temperature .+ randn(n) .* 15.0

# --- Regression -----------------------------------------------------------
x_mean = mean(temperature)
y_mean = mean(energy_consumption)
sum_xx = sum((temperature .- x_mean) .^ 2)
sum_xy = sum((temperature .- x_mean) .* (energy_consumption .- y_mean))
slope = sum_xy / sum_xx
intercept = y_mean - slope * x_mean

fitted = intercept .+ slope .* temperature
residuals = energy_consumption .- fitted
ss_res = sum(residuals .^ 2)
ss_tot = sum((energy_consumption .- y_mean) .^ 2)
r_squared = 1 - ss_res / ss_tot

dof = n - 2
residual_se = sqrt(ss_res / dof)
t_critical = 1.96  # normal approximation for 95% CI, valid at dof=148

x_line = range(minimum(temperature), maximum(temperature), length = 200)
y_line = intercept .+ slope .* x_line
se_fit = residual_se .* sqrt.(1 / n .+ (x_line .- x_mean) .^ 2 ./ sum_xx)
y_lower = y_line .- t_critical .* se_fit
y_upper = y_line .+ t_critical .* se_fit

# --- Plot -----------------------------------------------------------------
fig = Figure(
    resolution      = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = "scatter-regression-linear · julia · makie · anyplot.ai",
    titlesize          = 20,
    titlecolor         = INK,
    xlabel             = "Average Temperature (°C)",
    ylabel             = "Daily Energy Consumption (kWh)",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
)

band_plot = band!(ax, x_line, y_lower, y_upper; color = (IMPRINT_PALETTE[3], 0.16))
scatter_plot = scatter!(ax, temperature, energy_consumption;
    color = (IMPRINT_PALETTE[1], 0.55), markersize = 10, strokewidth = 0)
line_plot = lines!(ax, x_line, y_line; color = IMPRINT_PALETTE[3], linewidth = 3)

# --- Equation callout card ---------------------------------------------------
# A layered card (drop-shadow rect + bordered panel) instead of bare text-on-plot,
# giving the annotation a distinct focal point rather than a floating label.
card_bg = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
poly!(ax, Point2f[(0.022, 0.975), (0.335, 0.975), (0.335, 0.815), (0.022, 0.815)];
    space = :relative, color = (INK, 0.06), strokewidth = 0)
poly!(ax, Point2f[(0.015, 0.985), (0.328, 0.985), (0.328, 0.825), (0.015, 0.825)];
    space = :relative, color = (card_bg, 0.92), strokecolor = INK_SOFT, strokewidth = 1)

equation_sign = intercept >= 0 ? "+" : "-"
equation = "y = $(round(slope, digits = 2))x $equation_sign $(round(abs(intercept), digits = 1))"
stats_label = "$equation\nR² = $(round(r_squared, digits = 3))"

text!(ax, 0.035, 0.955; text = stats_label, space = :relative,
    align = (:left, :top), fontsize = 16, color = INK)

# --- Legend (Makie layout composition, identifies the 95% CI band) ----------
legend_elements = [
    MarkerElement(color = (IMPRINT_PALETTE[1], 0.55), marker = :circle, markersize = 10),
    LineElement(color = IMPRINT_PALETTE[3], linewidth = 3),
    PolyElement(color = (IMPRINT_PALETTE[3], 0.16)),
]
Legend(fig[1, 2], legend_elements, ["Observed data", "Linear fit", "95% CI band"];
    framevisible   = false,
    labelcolor     = INK,
    labelsize      = 13,
    backgroundcolor = PAGE_BG,
    tellheight     = false,
    valign         = :top,
)
colsize!(fig.layout, 2, Relative(0.13))
colgap!(fig.layout, 1, 18)

# --- Save -------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Part of Scatter Plot with Linear Regression on anyplot.ai.

Other implementations