Innovation Radar with Time-Horizon Rings — ggplot2

A radial chart that maps innovations, technologies, or trends onto concentric rings representing time horizons and angular sectors representing thematic categories. Inner rings represent near-term items (e.g., "Now", "Next 6 months") while outer rings represent longer-term or emerging trends (e.g., "2-5 years", "Future"). Each item is placed as a labeled point within its sector and ring, with distinct markers or colors encoding categories. Inspired by ThoughtWorks Technology Radar and similar strategic planning visualizations.

Innovation Radar with Time-Horizon Rings rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' radar-innovation-timeline: Innovation Radar with Time-Horizon Rings
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 80/100 | Created: 2026-05-29

library(ggplot2)
library(dplyr)
library(ragg)

set.seed(42)

# Theme tokens — Imprint palette
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 <- c(
  "#009E73",  # 1 — brand green → AI & ML
  "#C475FD",  # 2 — lavender    → Cloud & Infra
  "#4467A3",  # 3 — blue        → Security
  "#BD8233"   # 4 — ochre       → Dev Tools
)

sector_names  <- c("AI & ML", "Cloud & Infra", "Security", "Dev Tools")
ring_labels_v <- c("Now", "Near-term", "Mid-term", "Future")
color_map     <- setNames(IMPRINT_PALETTE, sector_names)
sector_starts <- setNames(c(0, 90, 180, 270), sector_names)

# 16 technology items: one per (sector × ring) cell
items <- tibble::tibble(
  name = c(
    "LLM Deployment", "Agentic AI", "Foundation Models", "Neuromorphic Chips",
    "Kubernetes Automation", "FinOps Tooling", "Edge ML Platforms", "Quantum Cloud",
    "Zero-Trust Network", "AI-Powered SIEM", "Homomorphic Encryption", "Post-Quantum Crypto",
    "AI Code Assistance", "Platform Engineering", "WebAssembly", "Autonomous Testing"
  ),
  sector = rep(sector_names, each = 4),
  ring_r = rep(1:4, times = 4),
  # Spread angles evenly within each 90-degree sector (15/40/62/78 → 25°+ gaps)
  angle_offset = rep(c(15, 40, 62, 78), times = 4)
)

items$angle      <- sector_starts[items$sector] + items$angle_offset
items$color      <- color_map[items$sector]
# Larger points for more-immediate rings to convey urgency/immediacy
items$point_size <- c(6.5, 5.5, 4.5, 3.5)[items$ring_r]

# Helper: annular polygon for filled ring bands
make_ring_poly <- function(r_inner, r_outer, grp, n = 300) {
  theta <- seq(0, 360, length.out = n + 1)
  data.frame(
    angle = c(theta, rev(theta)),
    r     = c(rep(r_outer, n + 1), rep(r_inner, n + 1)),
    grp   = grp,
    stringsAsFactors = FALSE
  )
}

# Rings 1 and 3 get a subtle tinted fill (alternating)
RING_FILL    <- if (THEME == "light") "#E6E3DB" else "#232320"
ring_band_df <- rbind(
  make_ring_poly(0.5, 1.5, grp = "r1"),
  make_ring_poly(2.5, 3.5, grp = "r3")
)

# Ring boundary circles
n_circ     <- 301
angles_seq <- seq(0, 360, length.out = n_circ)
circle_df  <- do.call(rbind, lapply(c(0.5, 1.5, 2.5, 3.5, 4.5), function(r) {
  data.frame(angle = angles_seq, r = r, grp = as.character(r))
}))

# Sector divider spokes (radial lines at 0, 90, 180, 270 degrees)
spoke_df <- do.call(rbind, lapply(c(0, 90, 180, 270), function(a) {
  data.frame(angle = c(a, a), r = c(0.5, 4.5), grp = as.character(a))
}))

# Ring annotation labels — placed just inside each ring at a small angle
ring_ann_df <- tibble::tibble(
  angle = 5,
  r     = 1:4,
  label = ring_labels_v
)

# Sector headers outside the outermost ring boundary
sector_hdr_df <- tibble::tibble(
  angle = c(45, 135, 225, 315),
  r     = 5.05,
  label = sector_names
)

title_str <- "radar-innovation-timeline · r · ggplot2 · anyplot.ai"
title_sz  <- max(8L, round(12 * 67 / nchar(title_str)))

p <- ggplot() +
  # Alternating ring fills (rings 1 and 3)
  geom_polygon(
    data = ring_band_df,
    aes(x = angle, y = r, group = grp),
    fill = RING_FILL, color = NA, alpha = 0.75
  ) +
  # Ring boundary circles
  geom_path(
    data = circle_df,
    aes(x = angle, y = r, group = grp),
    color = INK_SOFT, linewidth = 0.25, alpha = 0.5
  ) +
  # Sector divider spokes
  geom_path(
    data = spoke_df,
    aes(x = angle, y = r, group = grp),
    color = INK_SOFT, linewidth = 0.45, alpha = 0.6
  ) +
  # Sector header labels
  geom_text(
    data = sector_hdr_df,
    aes(x = angle, y = r, label = label),
    size = 3.0, fontface = "bold", color = INK, hjust = 0.5
  ) +
  # Ring annotation labels (ring names near the 0-degree spoke)
  geom_text(
    data = ring_ann_df,
    aes(x = angle, y = r, label = label),
    size = 2.3, color = INK_MUTED, fontface = "bold", hjust = 0
  ) +
  # Technology item points — larger for more-immediate rings
  geom_point(
    data = items,
    aes(x = angle, y = ring_r, color = sector, size = point_size),
    alpha = 0.95
  ) +
  scale_size_identity(guide = "none") +
  # Technology item labels (offset outward; angles are spread 25°+ so no overlap)
  geom_text(
    data = items,
    aes(x = angle, y = ring_r + 0.42, label = name, color = sector),
    size = 2.5, hjust = 0.5, vjust = 0
  ) +
  coord_polar(theta = "x", start = 0) +
  scale_x_continuous(limits = c(0, 360), breaks = NULL, expand = c(0, 0)) +
  scale_y_continuous(limits = c(0, 5.8), breaks = NULL) +
  scale_color_manual(values = color_map) +
  labs(title = title_str, color = "Sector") +
  theme_void(base_size = 8) +
  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 = title_sz, hjust = 0.5,
      margin = margin(b = 14)
    ),
    legend.position   = "bottom",
    legend.direction  = "horizontal",
    legend.background = element_rect(
      fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3
    ),
    legend.text     = element_text(color = INK_SOFT, size = 8),
    legend.title    = element_text(color = INK, size = 10),
    legend.key.size = unit(0.45, "cm"),
    plot.margin     = margin(15, 30, 30, 30)
  )

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

Retrieve this implementation

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

Part of Innovation Radar with Time-Horizon Rings on anyplot.ai.

Other implementations