Signal Integrity Eye Diagram — Plotly

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 Plotly

Python source (Plotly)

""" anyplot.ai
eye-diagram-basic: Signal Integrity Eye Diagram
Library: plotly 6.8.0 | Python 3.13.14
Quality: 89/100 | Updated: 2026-06-18
"""

import os

import numpy as np
import plotly.graph_objects as go


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
GRID = "rgba(26,26,23,0.15)" if THEME == "light" else "rgba(240,239,232,0.15)"

# Imprint sequential colorscale for density — page bg (zero density) → green → blue
imprint_seq = [[0.0, PAGE_BG], [0.35, "#009E73"], [1.0, "#4467A3"]]

# Data
np.random.seed(42)

n_traces = 400
samples_per_ui = 150
n_ui = 2
total_samples = n_ui * samples_per_ui

# Time axis normalized to unit intervals
time = np.linspace(0, n_ui, total_samples)

# Generate random NRZ bit sequences (4 bits to cover 2 UI with transitions)
n_bits = 4
bit_sequences = np.random.randint(0, 2, size=(n_traces, n_bits))

# Build smooth NRZ waveforms with raised-cosine transitions (vectorized)
rolloff = 0.15  # transition sharpness (fraction of UI)
noise_sigma = 0.05  # 5% of amplitude
jitter_sigma = 0.03  # 3% of UI

jitter = np.random.normal(0, jitter_sigma, (n_traces, n_bits))

# Broadcast time across traces: shape (n_traces, total_samples)
time_broadcast = np.broadcast_to(time, (n_traces, total_samples))

# Start from first bit level
voltage = np.full((n_traces, total_samples), bit_sequences[:, 0:1], dtype=float)

# Apply transitions for each bit boundary using vectorized tanh
for b in range(1, n_bits):
    transition_time = (b - 1.0) + jitter[:, b : b + 1]  # shape (n_traces, 1)
    transition = 0.5 * (1 + np.tanh((time_broadcast - transition_time) / rolloff))
    voltage = voltage * (1 - transition) + bit_sequences[:, b : b + 1] * transition

# Add Gaussian noise
voltage += np.random.normal(0, noise_sigma, voltage.shape)

all_time = np.tile(time, n_traces)
all_voltage = voltage.ravel()

# Compute eye height at first eye center (t ≈ 0.5 UI)
center_mask = (all_time > 0.35) & (all_time < 0.65)
high_at_center = all_voltage[center_mask & (all_voltage > 0.5)]
low_at_center = all_voltage[center_mask & (all_voltage < 0.5)]
mean_high = float(np.mean(high_at_center))
mean_low = float(np.mean(low_at_center))
eye_height = mean_high - mean_low

# Compute eye width: largest time gap where no traces cross the 0.5 V midpoint
# (first eye only, 0–1 UI)
midband_mask = (all_voltage > 0.4) & (all_voltage < 0.6) & (all_time > 0.0) & (all_time < 1.0)
transition_times = np.sort(all_time[midband_mask])
if len(transition_times) > 1:
    gaps = np.diff(transition_times)
    max_gap_idx = int(np.argmax(gaps))
    eye_open_start = float(transition_times[max_gap_idx])
    eye_open_end = float(transition_times[max_gap_idx + 1])
    eye_width_ui = eye_open_end - eye_open_start
else:
    eye_open_start, eye_open_end = 0.2, 0.8
    eye_width_ui = 0.6

# Plot — density heatmap with smoothing to reduce graininess
fig = go.Figure(
    data=go.Histogram2d(
        x=all_time,
        y=all_voltage,
        nbinsx=500,
        nbinsy=350,
        zsmooth="best",
        colorscale=imprint_seq,
        colorbar={
            "title": {"text": "Trace Density", "font": {"size": 12, "color": INK_SOFT}},
            "tickfont": {"size": 10, "color": INK_SOFT},
            "thickness": 14,
            "len": 0.7,
            "outlinewidth": 0,
            "bgcolor": "rgba(0,0,0,0)",
            "borderwidth": 0,
            "y": 0.5,
        },
        hovertemplate="Time: %{x:.2f} UI<br>Voltage: %{y:.3f} V<br>Density: %{z}<extra></extra>",
    )
)

ACCENT = "#009E73"

# Faint horizontal reference lines at NRZ nominal logic levels (0 V and 1 V)
for logic_v in [0.0, 1.0]:
    fig.add_shape(
        type="line",
        x0=0,
        x1=n_ui,
        y0=logic_v,
        y1=logic_v,
        line={"color": INK_SOFT, "width": 1, "dash": "dash"},
        opacity=0.35,
    )

# Vertical bracket line marking the eye height span at t=0.5 UI
fig.add_shape(type="line", x0=0.5, x1=0.5, y0=mean_low, y1=mean_high, line={"color": ACCENT, "width": 2, "dash": "dot"})

# Horizontal bracket line marking the eye width span at y=0.5 V
fig.add_shape(
    type="line", x0=eye_open_start, x1=eye_open_end, y0=0.5, y1=0.5, line={"color": ACCENT, "width": 2, "dash": "dot"}
)

# Combined signal quality annotation at center of first eye
fig.add_annotation(
    x=0.5,
    y=0.5,
    text=f"Eye Height: {eye_height:.2f} V<br>Eye Width:  {eye_width_ui:.2f} UI",
    showarrow=False,
    font={"size": 10, "color": ACCENT, "family": "monospace"},
    bgcolor=ELEVATED_BG,
    bordercolor=ACCENT,
    borderwidth=1,
    borderpad=6,
    align="left",
)

# Title
title = "eye-diagram-basic · python · plotly · anyplot.ai"

fig.update_layout(
    autosize=False,
    title={
        "text": title,
        "font": {"size": 16, "color": INK},
        "x": 0.5,
        "xanchor": "center",
        "y": 0.97,
        "yanchor": "top",
    },
    xaxis={
        "title": {"text": "Time (UI)", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "tickvals": [0, 0.5, 1.0, 1.5, 2.0],
        "gridcolor": GRID,
        "gridwidth": 1,
        "zeroline": False,
        "showline": True,
        "linecolor": INK_SOFT,
        "linewidth": 1,
        "mirror": False,
    },
    yaxis={
        "title": {"text": "Voltage (V)", "font": {"size": 12, "color": INK}},
        "tickfont": {"size": 10, "color": INK_SOFT},
        "gridcolor": GRID,
        "gridwidth": 1,
        "zeroline": False,
        "showline": True,
        "linecolor": INK_SOFT,
        "linewidth": 1,
        "mirror": False,
    },
    paper_bgcolor=PAGE_BG,
    plot_bgcolor=PAGE_BG,
    font={"color": INK},
    legend={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "borderwidth": 1, "font": {"color": INK_SOFT}},
    margin={"l": 80, "r": 60, "t": 80, "b": 65},
)

# Save — landscape 3200×1800 (800×450 scale=4)
fig.write_image(f"plot-{THEME}.png", width=800, height=450, scale=4)
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

Part of Signal Integrity Eye Diagram on anyplot.ai.

Other implementations