Signal Integrity Eye Diagram — ggplot2

An eye diagram visualizes signal integrity by overlaying many periods of a digital signal onto a single time window spanning 1-2 unit intervals (UI). The overlapping traces form a characteristic eye-shaped opening whose height, width, and clarity reveal signal quality metrics such as jitter, noise, and inter-symbol interference (ISI). A wide-open eye indicates clean signal transmission, while a closed or blurred eye signals degradation.

Signal Integrity Eye Diagram rendered with ggplot2

R source (ggplot2)

#' anyplot.ai
#' eye-diagram-basic: Signal Integrity Eye Diagram
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 92/100 | Created: 2026-06-18

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"
INK_MUTED   <- if (THEME == "light") "#6B6A63" else "#A8A79F"

# NRZ simulation parameters
n_bits       <- 600
samp_per_ui  <- 100
noise_sigma  <- 0.05   # Gaussian noise (5% of amplitude)
jitter_sigma <- 0.03   # Timing jitter std dev (3% of UI)
sigmoid_k    <- 18     # Bandwidth-limiting sharpness

# Random bit stream and per-transition jitter
bits       <- sample(c(0L, 1L), n_bits, replace = TRUE)
jitter_raw <- rnorm(n_bits - 1L, 0.0, jitter_sigma)
j_arr      <- c(0.0, jitter_raw)

# Vectorized waveform generation with sigmoid transitions and jitter
n_total   <- n_bits * samp_per_ui
t_all     <- (seq_len(n_total) - 0.5) / samp_per_ui
bit_idx   <- pmin(as.integer(floor(t_all)) + 1L, n_bits)
t_within  <- t_all - floor(t_all)
prev_bits <- c(bits[1L], bits[-n_bits])
prev_at_t <- prev_bits[bit_idx]
curr_at_t <- bits[bit_idx]
j_vals    <- j_arr[bit_idx]

dt    <- (t_within - j_vals) * sigmoid_k
sig_s <- 1.0 / (1.0 + exp(-dt))
same  <- (prev_at_t == curr_at_t)
rise  <- (!same & curr_at_t > prev_at_t)
v_sig <- ifelse(same, curr_at_t * 1.0, ifelse(rise, sig_s, 1.0 - sig_s))
v_sig <- v_sig + rnorm(n_total, 0.0, noise_sigma)

# Fold waveform into 2-UI eye traces (start at each bit boundary)
window_sz <- 2L * samp_per_ui
n_traces  <- n_bits - 2L
t_axis    <- seq(0.0, 2.0, length.out = window_sz + 1L)[-1L]
start_idx <- (seq_len(n_traces) - 1L) * samp_per_ui + 1L
samp_mat  <- outer(0L:(window_sz - 1L), start_idx, `+`)
eye_df    <- data.frame(
    time    = rep(t_axis, n_traces),
    voltage = v_sig[as.vector(samp_mat)]
)

# Pre-compute 2D density grid: vectorised findInterval + tabulate → geom_raster
n_bins_x <- 200
n_bins_y <- 150
t_breaks <- seq(0.0,  2.0,  length.out = n_bins_x + 1L)
v_breaks <- seq(-0.18, 1.18, length.out = n_bins_y + 1L)
t_mids   <- (t_breaks[-1L] + t_breaks[-length(t_breaks)]) / 2
v_mids   <- (v_breaks[-1L] + v_breaks[-length(v_breaks)]) / 2

t_bin_idx <- pmin(pmax(findInterval(eye_df$time,    t_breaks), 1L), n_bins_x)
v_bin_idx <- pmin(pmax(findInterval(eye_df$voltage, v_breaks), 1L), n_bins_y)
lin_idx   <- (t_bin_idx - 1L) * n_bins_y + v_bin_idx
count_vec <- tabulate(lin_idx, nbins = n_bins_x * n_bins_y)

# Full rectangular grid required for geom_raster interpolation;
# zero-count cells → NA so scale maps them to PAGE_BG (eye opening shows through)
raster_df <- data.frame(
    time    = rep(t_mids, each = n_bins_y),
    voltage = rep(v_mids, times = n_bins_x),
    density = ifelse(count_vec > 0L, sqrt(count_vec), NA_real_)
)

# Estimate eye opening at optimal sampling point (t ≈ 1.5 UI, centre of second eye)
at_center  <- eye_df$time >= 1.40 & eye_df$time <= 1.60
center_v   <- eye_df$voltage[at_center]
low_level  <- center_v[center_v < 0.5]
high_level <- center_v[center_v > 0.5]
eye_lo     <- if (length(low_level)  > 10L) as.numeric(quantile(low_level,  0.995)) else 0.15
eye_hi     <- if (length(high_level) > 10L) as.numeric(quantile(high_level, 0.005)) else 0.85
eye_ht_mv  <- max(0L, round((eye_hi - eye_lo) * 1000L))

plot_title <- "eye-diagram-basic · r · ggplot2 · anyplot.ai"

p <- ggplot(raster_df, aes(x = time, y = voltage)) +
    # Smooth density heatmap via bilinear-interpolated raster (publication-quality)
    geom_raster(aes(fill = density), interpolate = TRUE) +
    # Optimal sampling point (t=0.5, centre of first eye) — vertical dashed reference
    annotate("segment", x = 0.5, xend = 0.5,
             y = min(v_breaks), yend = max(v_breaks),
             color = INK_MUTED, linewidth = 0.45, linetype = "dashed") +
    # Eye height bracket: bracket bar + serif ticks + measurement label
    annotate("segment", x = 1.72, xend = 1.72, y = eye_lo, yend = eye_hi,
             color = "#AE3030", linewidth = 0.6) +
    annotate("segment", x = 1.70, xend = 1.74, y = eye_lo, yend = eye_lo,
             color = "#AE3030", linewidth = 0.5) +
    annotate("segment", x = 1.70, xend = 1.74, y = eye_hi, yend = eye_hi,
             color = "#AE3030", linewidth = 0.5) +
    annotate("text", x = 1.76, y = (eye_lo + eye_hi) / 2,
             label = paste0(eye_ht_mv, " mV"),
             color = "#AE3030", size = 2.5, hjust = 0.0, fontface = "bold") +
    scale_fill_gradient(
        name     = "Density",
        low      = "#009E73",
        high     = "#4467A3",
        na.value = PAGE_BG,
        guide    = guide_colorbar(barwidth = 0.5, barheight = 8)
    ) +
    scale_x_continuous(
        name   = "Time (UI)",
        breaks = c(0.0, 0.5, 1.0, 1.5, 2.0),
        expand = c(0.04, 0.0)
    ) +
    scale_y_continuous(
        name   = "Voltage (V)",
        breaks = c(0.0, 0.25, 0.5, 0.75, 1.0),
        expand = c(0.06, 0.0)
    ) +
    coord_cartesian(clip = "off") +
    labs(title = plot_title) +
    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_line(color = INK_MUTED,  linewidth = 0.15),
        panel.grid.minor  = element_blank(),
        panel.border      = element_rect(fill = NA,          color = INK_SOFT, linewidth = 0.4),
        axis.title        = element_text(color = INK,        size = 10),
        axis.text         = element_text(color = INK_SOFT,   size = 8),
        plot.title        = element_text(color = INK,        size = 12, hjust = 0.0),
        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 = 9),
        legend.position   = "right",
        plot.margin       = margin(12, 20, 12, 12, "pt")
    )

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

Part of Signal Integrity Eye Diagram on anyplot.ai.

Other implementations