Activity Rings Progress Chart — Makie.jl

An activity rings chart displays goal completion for several metrics at once using concentric radial progress arcs, in the style popularized by fitness trackers. Each ring is a partial arc with rounded end caps that starts at the top (12 o'clock) and sweeps clockwise in proportion to how close the metric is to its goal, with a distinct color per metric and a faint background track showing the full circle. It is instantly recognizable and ideal for at-a-glance comparison of progress across a small set of related goals, filling the general radial-progress gap that needle gauges (`gauge-basic`) and polar bars (`polar-bar`) do not cover.

Activity Rings Progress Chart rendered with Makie.jl

Julia source (Makie.jl)

# anyplot.ai
# gauge-activity-rings: Activity Rings Progress Chart
# Library: makie 0.22.10 | Julia 1.11.9
# Quality: 89/100 | Created: 2026-06-14

using CairoMakie
using Colors
using Statistics

# 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 INK_MUTED = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F"

const IMPRINT_PALETTE = [
    colorant"#009E73",  # 1 — brand green (first series, always)
    colorant"#C475FD",  # 2 — lavender
    colorant"#4467A3",  # 3 — blue
    colorant"#BD8233",  # 4 — ochre
    colorant"#AE3030",  # 5 — matte red
    colorant"#2ABCCD",  # 6 — cyan
    colorant"#954477",  # 7 — rose
    colorant"#99B314",  # 8 — lime
]

# Data — daily fitness tracker: Move, Exercise, Stand
const metrics     = ["Move",  "Exercise", "Stand"]
const val_current = [420,      25,          9]
const val_goal    = [600,      30,         12]
const val_unit    = ["kcal",  "min",       "hr"]
const ring_radii  = [0.68,    0.50,        0.32]
const ring_colors = IMPRINT_PALETTE[1:3]

const fractions = val_current ./ val_goal     # [0.70, 0.833, 0.75]
const avg_pct   = round(Int, mean(fractions) * 100)   # 76

# Figure — square canvas → 2400 × 2400 output
fig = Figure(
    size            = (1200, 1200),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = "gauge-activity-rings · julia · makie · anyplot.ai",
    titlesize          = 20,
    titlecolor         = INK,
    backgroundcolor    = PAGE_BG,
    aspect             = DataAspect(),
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinevisible   = false,
    bottomspinevisible = false,
    xgridvisible       = false,
    ygridvisible       = false,
    xticksvisible      = false,
    yticksvisible      = false,
    xticklabelsvisible = false,
    yticklabelsvisible = false,
)

limits!(ax, -1.10, 1.10, -1.10, 1.10)

# Ring drawing parameters
const N_PTS = 360
const LW    = 46   # device px → ×2 = 92 output px on 2400-px canvas

# Draw rings outer → inner
for (r, frac, col) in zip(ring_radii, fractions, ring_colors)
    # Faint background track — full circle; higher opacity in dark for contrast
    bg_alpha = THEME == "dark" ? 0.22f0 : 0.12f0
    θ_bg = range(0.0, 2π, length = N_PTS + 1)
    lines!(ax, r .* cos.(θ_bg), r .* sin.(θ_bg);
           linewidth = LW, color = (col, bg_alpha))

    # Progress arc — starts at 12 o'clock (π/2), sweeps clockwise
    sweep = min(frac, 1.0) * 2π
    n_arc = max(4, round(Int, frac * N_PTS))
    θ_arc = range(π / 2, π / 2 - sweep; length = n_arc)
    x_arc = r .* cos.(θ_arc)
    y_arc = r .* sin.(θ_arc)
    lines!(ax, x_arc, y_arc; linewidth = LW, color = col)

    # Circular end caps that match the line width for a rounded look
    scatter!(ax, [x_arc[1]],   [y_arc[1]];   color = col, markersize = LW, strokewidth = 0)
    scatter!(ax, [x_arc[end]], [y_arc[end]]; color = col, markersize = LW, strokewidth = 0)
end

# Center summary text
text!(ax, 0.0, 0.10;
      text     = "$(avg_pct)%",
      fontsize = 60,
      color    = INK,
      align    = (:center, :center))
text!(ax, 0.0, -0.12;
      text     = "COMPLETE",
      fontsize = 18,
      color    = INK_MUTED,
      align    = (:center, :center))

# Bottom legend strip — color dot + metric name + value/goal
const x_pos = [-0.52, 0.0, 0.52]
const y_lbl = -0.84
const y_val = -0.97

for (i, (name, cur, goal, unit, col)) in
        enumerate(zip(metrics, val_current, val_goal, val_unit, ring_colors))
    xc = x_pos[i]
    scatter!(ax, [xc - 0.14], [(y_lbl + y_val) / 2];
             color = col, markersize = 22, strokewidth = 0)
    text!(ax, xc - 0.04, y_lbl;
          text = name, fontsize = 16, color = INK, align = (:left, :baseline))
    text!(ax, xc - 0.04, y_val;
          text = "$(cur) / $(goal) $(unit)", fontsize = 13, color = INK_SOFT,
          align = (:left, :baseline))
end

# Save — px_per_unit = 2 doubles the resolution to 2400 × 2400
save("plot-$(THEME).png", fig; px_per_unit = 2)

Part of Activity Rings Progress Chart on anyplot.ai.

Other implementations