Recurrence Plot for Nonlinear Time Series — ggplot2

A recurrence plot is a binary or distance-based matrix visualization that reveals when states in a dynamical system recur over time. Both axes represent time indices, and a point is plotted at position (i, j) when the system state at time i is sufficiently similar to the state at time j (distance below a threshold). The resulting symmetric matrix exposes hidden structure in complex, nonlinear dynamics — diagonal lines indicate determinism, vertical/horizontal lines reveal laminar states, and block structures signal regime changes.

Recurrence Plot for Nonlinear Time Series rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' recurrence-basic: Recurrence Plot for Nonlinear Time Series
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 88/100 | Created: 2026-06-10

library(ggplot2)
library(ragg)

set.seed(42)

# Theme tokens — Imprint palette, theme-adaptive chrome
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"
IMPRINT_PALETTE <- c(
  "#009E73",  # 1 — brand green (first categorical series)
  "#C475FD",  # 2 — lavender
  "#4467A3",  # 3 — blue
  "#BD8233",  # 4 — ochre
  "#AE3030",  # 5 — matte red
  "#2ABCCD",  # 6 — cyan
  "#954477",  # 7 — rose
  "#99B314"   # 8 — lime
)

# Data: logistic map in chaotic regime (r = 3.9, 300 steps)
n_steps <- 300
r_param <- 3.9
x_series <- numeric(n_steps)
x_series[1] <- 0.4
for (i in 2:n_steps) {
  x_series[i] <- r_param * x_series[i - 1] * (1 - x_series[i - 1])
}

# Time-delay embedding (Takens' theorem): dim = 2, delay = 5
emb_dim <- 2
delay   <- 5
n_emb   <- n_steps - (emb_dim - 1) * delay
states  <- matrix(0.0, nrow = n_emb, ncol = emb_dim)
for (d in seq_len(emb_dim)) {
  start_idx  <- (d - 1) * delay + 1
  states[, d] <- x_series[start_idx:(start_idx + n_emb - 1)]
}

# Pairwise Euclidean distances; binary threshold at 5th percentile
dist_mat      <- as.matrix(dist(states))
nonzero_dists <- dist_mat[dist_mat > 0]
epsilon       <- quantile(nonzero_dists, 0.05)
recurrence    <- as.integer(dist_mat <= epsilon)

# Long data frame for geom_raster
n_pts  <- nrow(states)
rec_df <- data.frame(
  i         = rep(seq_len(n_pts), times = n_pts),
  j         = rep(seq_len(n_pts), each  = n_pts),
  recurrent = recurrence
)

plot_title    <- "Logistic Map · recurrence-basic · r · ggplot2 · anyplot.ai"
plot_subtitle <- "r = 3.9 (chaotic regime) · short diagonal bands signal deterministic recurrence"

# Plot: binary recurrence matrix with diagonal reference + storytelling annotation
p <- ggplot(rec_df, aes(x = i, y = j, fill = factor(recurrent))) +
  geom_raster(interpolate = FALSE) +
  # Diagonal reference line emphasising the main recurrence axis (LOI)
  geom_abline(
    slope = 1, intercept = 0,
    color = IMPRINT_PALETTE[3], linewidth = 0.5, alpha = 0.55
  ) +
  # Arrow annotation pointing to off-diagonal band structure
  annotate(
    "segment",
    x = n_pts * 0.16, xend = n_pts * 0.26,
    y = n_pts * 0.96, yend = n_pts * 0.81,
    arrow = arrow(length = unit(0.12, "cm"), type = "closed"),
    color = INK_SOFT, linewidth = 0.55
  ) +
  annotate(
    "text",
    x = n_pts * 0.10, y = n_pts * 0.98,
    label = "off-diagonal bands\n= determinism",
    color = INK_SOFT, size = 2.7, hjust = 0.5, lineheight = 0.9
  ) +
  scale_fill_manual(
    values = c("0" = PAGE_BG, "1" = IMPRINT_PALETTE[1]),
    guide  = "none"
  ) +
  scale_x_continuous(expand = c(0, 0), breaks = seq(50, n_pts, by = 50)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(50, n_pts, by = 50)) +
  coord_fixed(ratio = 1) +
  labs(
    title    = plot_title,
    subtitle = plot_subtitle,
    x        = "Time Index",
    y        = "Time Index"
  ) +
  theme_minimal(base_size = 8) +
  theme(
    plot.background     = element_rect(fill = PAGE_BG, color = PAGE_BG),
    panel.background    = element_rect(fill = PAGE_BG, color = NA),
    panel.grid.major    = element_blank(),
    panel.grid.minor    = element_blank(),
    panel.border        = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.5),
    axis.title          = element_text(color = INK,      size = 10),
    axis.text           = element_text(color = INK_SOFT, size = 9),
    plot.title          = element_text(color = INK,      size = 12, hjust = 0.5),
    plot.subtitle       = element_text(color = INK_SOFT, size = 9,  hjust = 0.5),
    plot.title.position = "plot",
    plot.margin         = margin(16, 16, 12, 12, unit = "pt")
  )

# Save — square canvas: 2400 × 2400 px (width=6, height=6, dpi=400)
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/recurrence-basic/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": "recurrence-basic",
  "language": "r",
  "library": "ggplot2",
  "page": "https://anyplot.ai/recurrence-basic/r/ggplot2",
  "hub": "https://anyplot.ai/recurrence-basic",
  "code_json": "https://api.anyplot.ai/specs/recurrence-basic/ggplot2/code",
  "spec_json": "https://api.anyplot.ai/specs/recurrence-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/recurrence-basic/r/ggplot2/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/recurrence-basic/r/ggplot2/plot-dark.png",
  "quality_score": 88.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Recurrence Plot for Nonlinear Time Series on anyplot.ai.

Other implementations