Signal Integrity Eye Diagram — Altair

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 Altair

Python source (Altair)

""" anyplot.ai
eye-diagram-basic: Signal Integrity Eye Diagram
Library: altair 6.2.1 | Python 3.13.13
Quality: 90/100 | Updated: 2026-06-18
"""

import os
import sys


# Prevent this file from shadowing the installed altair package on sys.path
_here = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p) != _here]
del _here

import altair as alt
import numpy as np
import pandas as pd
from PIL import Image


# 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"
ANNOTATION_COLOR = "#AE3030"  # Imprint matte-red for measurement indicators

# Data
np.random.seed(42)

n_traces = 300
samples_per_ui = 200
amplitude = 1.0
noise_sigma = 0.05 * amplitude
jitter_sigma = 0.03

n_bits = n_traces + 4
bits = np.random.randint(0, 2, n_bits)

samples_per_bit = samples_per_ui
total_signal_len = n_bits * samples_per_bit
t_full = np.arange(total_signal_len) / samples_per_bit

signal_full = np.zeros(total_signal_len)
for i in range(total_signal_len):
    bit_idx = int(t_full[i])
    if bit_idx >= n_bits:
        bit_idx = n_bits - 1
    frac = t_full[i] - bit_idx

    current_level = bits[bit_idx] * amplitude
    prev_level = bits[bit_idx - 1] * amplitude if bit_idx > 0 else bits[0] * amplitude

    transition_width = 0.12
    blend = 1.0 / (1.0 + np.exp(-14 * (frac - transition_width) / transition_width))
    signal_full[i] = prev_level + (current_level - prev_level) * blend

signal_full += np.random.normal(0, noise_sigma, total_signal_len)

all_time = []
all_voltage = []
window_samples = 2 * samples_per_ui

for trace in range(n_traces):
    start_bit = trace + 1
    start_sample = start_bit * samples_per_bit
    end_sample = start_sample + window_samples

    if end_sample > total_signal_len:
        break

    jitter_offset = np.random.normal(0, jitter_sigma)
    trace_time = np.linspace(0, 2, window_samples) + jitter_offset
    trace_voltage = signal_full[start_sample:end_sample]

    all_time.extend(trace_time.tolist())
    all_voltage.extend(trace_voltage.tolist())

# Pre-bin into 2D histogram for density heatmap
time_bins = 350
voltage_bins = 225
time_edges = np.linspace(-0.05, 2.05, time_bins + 1)
voltage_edges = np.linspace(-0.2, 1.2, voltage_bins + 1)

hist, _, _ = np.histogram2d(all_time, all_voltage, bins=[time_edges, voltage_edges])

# Use exact bin edges (x/x2/y/y2) for seamless tiling — no pixel arithmetic needed
rows = []
for i in range(time_bins):
    for j in range(voltage_bins):
        if hist[i, j] > 0:
            rows.append(
                {
                    "t_left": round(float(time_edges[i]), 5),
                    "t_right": round(float(time_edges[i + 1]), 5),
                    "v_bottom": round(float(voltage_edges[j]), 5),
                    "v_top": round(float(voltage_edges[j + 1]), 5),
                    "density": float(hist[i, j]),
                }
            )

df = pd.DataFrame(rows)
df["log_density"] = np.log1p(df["density"])

# Eye measurements for annotations
all_time_arr = np.array(all_time)
all_voltage_arr = np.array(all_voltage)
mid_time = 1.0
eye_center_v = amplitude / 2

mid_mask = (all_time_arr > 0.9) & (all_time_arr < 1.1)
mid_voltages = all_voltage_arr[mid_mask]
high_voltages = mid_voltages[mid_voltages > eye_center_v]
low_voltages = mid_voltages[mid_voltages <= eye_center_v]
eye_top = float(np.percentile(high_voltages, 5)) if len(high_voltages) > 0 else 0.9
eye_bottom = float(np.percentile(low_voltages, 95)) if len(low_voltages) > 0 else 0.1
eye_height_val = eye_top - eye_bottom

mid_v_mask = (all_voltage_arr > 0.4) & (all_voltage_arr < 0.6)
transition_times = all_time_arr[mid_v_mask]
left_transitions = transition_times[transition_times < 1.0]
right_transitions = transition_times[transition_times >= 1.0]
eye_left = float(np.percentile(left_transitions, 95)) if len(left_transitions) > 0 else 0.3
eye_right = float(np.percentile(right_transitions, 5)) if len(right_transitions) > 0 else 1.7
eye_width_val = eye_right - eye_left

# Title scaling: 67-char baseline at 16px default for altair
title_str = "eye-diagram-basic · python · altair · anyplot.ai"
title_fontsize = max(11, round(16 * 67 / max(len(title_str), 67)))

# Heatmap using exact bin edges (x2/y2) — eliminates tiling gaps
heatmap = (
    alt.Chart(df)
    .mark_rect()
    .encode(
        x=alt.X("t_left:Q", title="Time (UI)", scale=alt.Scale(domain=[0, 2])),
        x2=alt.X2("t_right:Q"),
        y=alt.Y("v_bottom:Q", title="Voltage (V)", scale=alt.Scale(domain=[-0.15, 1.15])),
        y2=alt.Y2("v_top:Q"),
        color=alt.Color(
            "log_density:Q",
            scale=alt.Scale(range=["#009E73", "#4467A3"]),  # Imprint sequential
            legend=alt.Legend(title="Log Density", gradientLength=200, orient="right"),
        ),
        tooltip=[
            alt.Tooltip("t_left:Q", title="Time (UI)", format=".3f"),
            alt.Tooltip("v_bottom:Q", title="Voltage (V)", format=".3f"),
            alt.Tooltip("density:Q", title="Trace Count", format=".0f"),
        ],
    )
)

# Eye height annotation (vertical dashed line)
height_rule = (
    alt.Chart(pd.DataFrame([{"x": mid_time + 0.05, "y": eye_bottom, "y2": eye_top}]))
    .mark_rule(color=ANNOTATION_COLOR, strokeWidth=2.5, strokeDash=[8, 4])
    .encode(x="x:Q", y="y:Q", y2="y2:Q")
)

# Eye width annotation (horizontal dashed line)
width_rule = (
    alt.Chart(pd.DataFrame([{"x": eye_left, "x2": eye_right, "y": eye_center_v}]))
    .mark_rule(color=ANNOTATION_COLOR, strokeWidth=2.5, strokeDash=[8, 4])
    .encode(x="x:Q", x2="x2:Q", y="y:Q")
)

# Measurement labels
ann_labels = pd.DataFrame(
    [
        {"x": mid_time + 0.22, "y": eye_center_v + 0.19, "text": f"Eye Height: {eye_height_val:.3f} V"},
        {"x": (eye_left + eye_right) / 2, "y": eye_center_v - 0.16, "text": f"Eye Width: {eye_width_val:.2f} UI"},
    ]
)
labels = (
    alt.Chart(ann_labels)
    .mark_text(color=ANNOTATION_COLOR, fontSize=12, fontWeight="bold", align="center", baseline="middle")
    .encode(x="x:Q", y="y:Q", text="text:N")
)

# Diamond markers at eye opening corners
eye_markers = pd.DataFrame(
    [
        {"x": mid_time, "y": eye_top},
        {"x": mid_time, "y": eye_bottom},
        {"x": eye_left, "y": eye_center_v},
        {"x": eye_right, "y": eye_center_v},
    ]
)
markers = (
    alt.Chart(eye_markers)
    .mark_point(shape="diamond", color=ANNOTATION_COLOR, size=120, filled=True, opacity=0.9)
    .encode(x="x:Q", y="y:Q")
)

# Compose layers and configure
chart = (
    (heatmap + height_rule + width_rule + labels + markers)
    .properties(
        width=620,
        height=320,
        background=PAGE_BG,
        title=alt.Title(
            title_str,
            fontSize=title_fontsize,
            fontWeight=500,
            color=INK,
            subtitle="NRZ signal — 300 traces · 5% noise · 3% jitter",
            subtitleFontSize=13,
            subtitleColor=INK_SOFT,
        ),
    )
    .configure_view(fill=PAGE_BG, stroke=INK_SOFT)
    .configure_axis(
        labelFontSize=10,
        titleFontSize=12,
        labelColor=INK_SOFT,
        titleColor=INK,
        tickColor=INK_SOFT,
        domainColor=INK_SOFT,
        grid=False,
    )
    .configure_legend(
        fillColor=ELEVATED_BG,
        strokeColor=INK_SOFT,
        labelColor=INK_SOFT,
        titleColor=INK,
        labelFontSize=12,
        titleFontSize=12,
    )
    .configure_title(color=INK)
)

# Save PNG
chart.save(f"plot-{THEME}.png", scale_factor=4.0)

# Pad to exact 3200×1800
TW, TH = 3200, 1800
_img = Image.open(f"plot-{THEME}.png").convert("RGB")
_w, _h = _img.size
if _w > TW or _h > TH:
    raise SystemExit(
        f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. "
        f"Shrink chart .properties(width=, height=) values and re-render."
    )
if _w < TW or _h < TH:
    _canvas = Image.new("RGB", (TW, TH), PAGE_BG)
    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))
    _canvas.save(f"plot-{THEME}.png")

# Save HTML
chart.save(f"plot-{THEME}.html")

Part of Signal Integrity Eye Diagram on anyplot.ai.

Other implementations