Activity Rings Progress Chart — ggplot2

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 ggplot2

R source (ggplot2)

#' anyplot.ai
#' gauge-activity-rings: Activity Rings Progress Chart
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 88/100 | Created: 2026-06-14

library(ggplot2)
library(ragg)

# Theme tokens
THEME       <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG     <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420"
INK         <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT    <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
INK_MUTED   <- if (THEME == "light") "#6B6A63" else "#A8A79F"

# Imprint palette — first 3 positions for 3 rings
IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3")

# Data — daily fitness tracker goals
rings <- data.frame(
  metric = c("Move", "Exercise", "Stand"),
  value  = c(420, 25, 9),
  goal   = c(600, 30, 12),
  unit   = c("kcal", "min", "hr"),
  radius = c(2.8, 2.0, 1.2),
  color  = IMPRINT_PALETTE,
  stringsAsFactors = FALSE
)
rings$fraction <- pmin(rings$value / rings$goal, 1.0)

# Arc path helpers — clockwise from top (π/2)
n_pts <- 360

make_track <- function(r) {
  a <- seq(pi / 2, pi / 2 - 2 * pi, length.out = n_pts + 1)
  data.frame(x = r * cos(a), y = r * sin(a))
}

make_arc <- function(r, frac) {
  n <- max(3, round(n_pts * frac) + 1)
  a <- seq(pi / 2, pi / 2 - frac * 2 * pi, length.out = n)
  data.frame(x = r * cos(a), y = r * sin(a))
}

# Build track (full-circle) and arc data frames
track_df <- do.call(rbind, lapply(seq_len(nrow(rings)), function(i) {
  d       <- make_track(rings$radius[i])
  d$group <- rings$metric[i]
  d$color <- rings$color[i]
  d
}))

arc_df <- do.call(rbind, lapply(seq_len(nrow(rings)), function(i) {
  d       <- make_arc(rings$radius[i], rings$fraction[i])
  d$group <- rings$metric[i]
  d$color <- rings$color[i]
  d
}))

# Label block — right side of rings, one group per metric
label_df <- data.frame(
  lx    = 3.35,
  ly    = c(1.5, 0.0, -1.5),
  label = paste0(
    rings$metric, "\n",
    rings$value, "/", rings$goal, " ", rings$unit, "\n",
    round(rings$fraction * 100), "%"
  ),
  color = rings$color,
  stringsAsFactors = FALSE
)

# Center annotation — average completion
avg_pct <- round(mean(rings$fraction) * 100)

# Title (47 chars — no scaling needed)
title_str <- "gauge-activity-rings · r · ggplot2 · anyplot.ai"

p <- ggplot() +
  # Faint background tracks (full-circle behind each arc)
  geom_path(
    data     = track_df,
    aes(x = x, y = y, group = group, color = color),
    linewidth = 9, alpha = 0.15, lineend = "round"
  ) +
  # Progress arcs
  geom_path(
    data     = arc_df,
    aes(x = x, y = y, group = group, color = color),
    linewidth = 9, lineend = "round"
  ) +
  scale_color_identity() +
  # Right-side metric labels (3 rows each)
  geom_text(
    data = label_df,
    aes(x = lx, y = ly, label = label, color = color),
    hjust = 0, vjust = 0.5, size = 4.2,
    lineheight = 1.35, fontface = "bold"
  ) +
  # Center: average percentage
  annotate(
    "text", x = 0, y = 0.28,
    label = paste0(avg_pct, "%"),
    color = INK, size = 7.5, fontface = "bold", hjust = 0.5, vjust = 0.5
  ) +
  annotate(
    "text", x = 0, y = -0.42,
    label = "avg",
    color = INK_MUTED, size = 4.5, hjust = 0.5, vjust = 0.5
  ) +
  coord_fixed(
    xlim = c(-3.5, 5.6),
    ylim = c(-3.5, 3.5)
  ) +
  labs(title = title_str) +
  theme_void() +
  theme(
    plot.background  = element_rect(fill = PAGE_BG, color = PAGE_BG),
    panel.background = element_rect(fill = PAGE_BG, color = NA),
    plot.title       = element_text(
      color  = INK, size = 12, hjust = 0.5,
      margin = margin(t = 18, b = 10)
    ),
    plot.margin = margin(20, 20, 20, 20)
  )

ggsave(
  filename = sprintf("plot-%s.png", THEME),
  plot     = p,
  device   = ragg::agg_png,
  width    = 6,
  height   = 6,
  units    = "in",
  dpi      = 400
)

Part of Activity Rings Progress Chart on anyplot.ai.

Other implementations