Time Series Forecast with Uncertainty Band — Makie.jl

A time series plot that displays historical observed data followed by a forecast projection with confidence intervals or uncertainty bands. The plot clearly distinguishes between the historical period and the forecast period using a vertical line marker, with shaded bands representing different confidence levels (typically 80% and 95%). This visualization is essential for communicating prediction uncertainty in forecasting applications, helping stakeholders understand both the expected values and the range of possible outcomes.

Time Series Forecast with Uncertainty Band rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# timeseries-forecast-uncertainty: Time Series Forecast with Uncertainty Band
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 85/100 | Created: 2026-09-02

using CairoMakie
using Colors
using Random
using Dates

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 --------------------------------------------------------------------
# Monthly sales, 3 years of history plus a 6-month forecast with 80/95% bands.
n_hist = 36
n_fcst = 6
n_total = n_hist + n_fcst

months = collect(0:(n_total - 1))
trend = 1000.0 .+ 12.0 .* months
seasonal = 120.0 .* sin.(2π .* months ./ 12)
noise = randn(n_total) .* 35.0
series = trend .+ seasonal .+ noise

actual = fill(NaN, n_total)
actual[1:n_hist] .= series[1:n_hist]

forecast = fill(NaN, n_total)
forecast[n_hist:n_total] .= series[n_hist:n_total]

horizon = 1:n_fcst
spread_80 = 40.0 .+ 14.0 .* horizon
spread_95 = 65.0 .+ 22.0 .* horizon

lower_80 = fill(NaN, n_total)
upper_80 = fill(NaN, n_total)
lower_95 = fill(NaN, n_total)
upper_95 = fill(NaN, n_total)
lower_80[n_hist:n_total] .= [series[n_hist]; series[(n_hist + 1):n_total] .- spread_80]
upper_80[n_hist:n_total] .= [series[n_hist]; series[(n_hist + 1):n_total] .+ spread_80]
lower_95[n_hist:n_total] .= [series[n_hist]; series[(n_hist + 1):n_total] .- spread_95]
upper_95[n_hist:n_total] .= [series[n_hist]; series[(n_hist + 1):n_total] .+ spread_95]

start_date = Date(2023, 1, 1)
dates = [start_date + Month(m) for m in months]
x = Float64.(months)
date_labels = Dict(m => Dates.format(start_date + Month(m), "yyyy-mm") for m in months)
tick_positions = collect(0:6:(n_total - 1))
tick_labels = [date_labels[m] for m in tick_positions]

# --- Plot ---------------------------------------------------------------------
title_str = "timeseries-forecast-uncertainty · julia · makie · anyplot.ai"

fig = Figure(
    size            = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = title_str,
    titlesize          = 20,
    titlecolor         = INK,
    xlabel             = "Month",
    ylabel             = "Sales (units)",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    xtickcolor         = INK_SOFT,
    ytickcolor         = 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),
    xminorgridvisible  = false,
    yminorgridvisible  = false,
    xticks             = (tick_positions, tick_labels),
    xticklabelrotation = π / 6,
)

# Forecast start marker
vlines!(ax, [Float64(n_hist - 1)]; color = INK_SOFT, linestyle = :dot, linewidth = 1.5)

# 95% band (lighter), then 80% band (darker) nested on top
band!(ax, x, lower_95, upper_95; color = (IMPRINT_PALETTE[3], 0.20), label = "95% interval")
band!(ax, x, lower_80, upper_80; color = (IMPRINT_PALETTE[3], 0.38), label = "80% interval")

# Thin edge strokes distinguish the 80%/95% band boundaries from each other
lines!(ax, x, lower_95; color = (IMPRINT_PALETTE[3], 0.45), linewidth = 1, linestyle = :dash)
lines!(ax, x, upper_95; color = (IMPRINT_PALETTE[3], 0.45), linewidth = 1, linestyle = :dash)
lines!(ax, x, lower_80; color = (IMPRINT_PALETTE[3], 0.75), linewidth = 1)
lines!(ax, x, upper_80; color = (IMPRINT_PALETTE[3], 0.75), linewidth = 1)

lines!(ax, x, actual; color = IMPRINT_PALETTE[1], linewidth = 3, label = "Historical")
lines!(ax, x, forecast; color = IMPRINT_PALETTE[3], linewidth = 3, linestyle = :dash, label = "Forecast")

# Callout labeling the historical/forecast transition point
y_max = maximum(filter(!isnan, vcat(actual, forecast, upper_95)))
text!(
    ax, Float64(n_hist - 1), y_max;
    text     = "Forecast start",
    color    = INK_SOFT,
    fontsize = 11,
    align    = (:left, :top),
    offset   = (6, -2),
)

axislegend(ax; position = :lt, framevisible = false, labelcolor = INK_SOFT, labelsize = 12)

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

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/timeseries-forecast-uncertainty/makie/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": "timeseries-forecast-uncertainty",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/timeseries-forecast-uncertainty/julia/makie",
  "hub": "https://anyplot.ai/timeseries-forecast-uncertainty",
  "code_json": "https://api.anyplot.ai/specs/timeseries-forecast-uncertainty/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/timeseries-forecast-uncertainty",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/timeseries-forecast-uncertainty/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/timeseries-forecast-uncertainty/julia/makie/plot-dark.png",
  "quality_score": 85.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Time Series Forecast with Uncertainty Band on anyplot.ai.

Other implementations